@aztec/sequencer-client 0.0.1-commit.21ecf947b → 0.0.1-commit.2448fdb
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 +16 -7
- package/dest/client/sequencer-client.d.ts.map +1 -1
- package/dest/client/sequencer-client.js +56 -23
- package/dest/config.d.ts +25 -5
- package/dest/config.d.ts.map +1 -1
- package/dest/config.js +42 -20
- package/dest/global_variable_builder/global_builder.d.ts +14 -10
- 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/config.d.ts +31 -17
- package/dest/publisher/config.d.ts.map +1 -1
- package/dest/publisher/config.js +101 -42
- package/dest/publisher/sequencer-publisher-factory.d.ts +11 -3
- package/dest/publisher/sequencer-publisher-factory.d.ts.map +1 -1
- package/dest/publisher/sequencer-publisher-factory.js +13 -2
- package/dest/publisher/sequencer-publisher.d.ts +22 -13
- package/dest/publisher/sequencer-publisher.d.ts.map +1 -1
- package/dest/publisher/sequencer-publisher.js +92 -52
- package/dest/sequencer/checkpoint_proposal_job.d.ts +4 -4
- package/dest/sequencer/checkpoint_proposal_job.d.ts.map +1 -1
- package/dest/sequencer/checkpoint_proposal_job.js +129 -75
- 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/metrics.d.ts +13 -5
- package/dest/sequencer/metrics.d.ts.map +1 -1
- package/dest/sequencer/metrics.js +32 -10
- package/dest/sequencer/sequencer.d.ts +27 -13
- package/dest/sequencer/sequencer.d.ts.map +1 -1
- package/dest/sequencer/sequencer.js +41 -42
- package/dest/sequencer/timetable.d.ts +4 -3
- package/dest/sequencer/timetable.d.ts.map +1 -1
- package/dest/sequencer/timetable.js +6 -7
- package/dest/sequencer/types.d.ts +2 -2
- package/dest/sequencer/types.d.ts.map +1 -1
- package/dest/test/index.d.ts +3 -5
- package/dest/test/index.d.ts.map +1 -1
- package/dest/test/mock_checkpoint_builder.d.ts +7 -9
- package/dest/test/mock_checkpoint_builder.d.ts.map +1 -1
- package/dest/test/mock_checkpoint_builder.js +41 -30
- package/package.json +28 -28
- package/src/client/sequencer-client.ts +78 -21
- package/src/config.ts +52 -27
- package/src/global_variable_builder/global_builder.ts +23 -24
- package/src/global_variable_builder/index.ts +1 -1
- package/src/publisher/config.ts +112 -43
- package/src/publisher/sequencer-publisher-factory.ts +23 -6
- package/src/publisher/sequencer-publisher.ts +96 -66
- package/src/sequencer/checkpoint_proposal_job.ts +200 -106
- package/src/sequencer/checkpoint_voter.ts +1 -12
- package/src/sequencer/metrics.ts +39 -13
- package/src/sequencer/sequencer.ts +52 -48
- package/src/sequencer/timetable.ts +7 -7
- package/src/sequencer/types.ts +1 -1
- package/src/test/index.ts +2 -4
- package/src/test/mock_checkpoint_builder.ts +53 -48
|
@@ -19,26 +19,29 @@ import {
|
|
|
19
19
|
type L1BlobInputs,
|
|
20
20
|
type L1TxConfig,
|
|
21
21
|
type L1TxRequest,
|
|
22
|
+
type L1TxUtils,
|
|
22
23
|
MAX_L1_TX_LIMIT,
|
|
23
24
|
type TransactionStats,
|
|
24
25
|
WEI_CONST,
|
|
25
26
|
} from '@aztec/ethereum/l1-tx-utils';
|
|
26
|
-
import type { L1TxUtilsWithBlobs } from '@aztec/ethereum/l1-tx-utils-with-blobs';
|
|
27
27
|
import { FormattedViemError, formatViemError, mergeAbis, tryExtractEvent } from '@aztec/ethereum/utils';
|
|
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';
|
|
34
35
|
import { Signature, type ViemSignature } from '@aztec/foundation/eth-signature';
|
|
35
36
|
import { type Logger, createLogger } from '@aztec/foundation/log';
|
|
37
|
+
import { makeBackoff, retry } from '@aztec/foundation/retry';
|
|
36
38
|
import { bufferToHex } from '@aztec/foundation/string';
|
|
37
39
|
import { DateProvider, Timer } from '@aztec/foundation/timer';
|
|
38
40
|
import { EmpireBaseAbi, ErrorsAbi, RollupAbi } from '@aztec/l1-artifacts';
|
|
39
41
|
import { type ProposerSlashAction, encodeSlashConsensusVotes } from '@aztec/slasher';
|
|
40
42
|
import { CommitteeAttestationsAndSigners, type ValidateCheckpointResult } from '@aztec/stdlib/block';
|
|
41
43
|
import type { Checkpoint } from '@aztec/stdlib/checkpoint';
|
|
44
|
+
import { getLastL1SlotTimestampForL2Slot, getNextL1SlotTimestamp } from '@aztec/stdlib/epoch-helpers';
|
|
42
45
|
import { SlashFactoryContract } from '@aztec/stdlib/l1-contracts';
|
|
43
46
|
import type { CheckpointHeader } from '@aztec/stdlib/rollup';
|
|
44
47
|
import type { L1PublishCheckpointStats } from '@aztec/stdlib/stats';
|
|
@@ -46,7 +49,7 @@ import { type TelemetryClient, type Tracer, getTelemetryClient, trackSpan } from
|
|
|
46
49
|
|
|
47
50
|
import { type StateOverride, type TransactionReceipt, type TypedDataDefinition, encodeFunctionData, toHex } from 'viem';
|
|
48
51
|
|
|
49
|
-
import type {
|
|
52
|
+
import type { SequencerPublisherConfig } from './config.js';
|
|
50
53
|
import { SequencerPublisherMetrics } from './sequencer-publisher-metrics.js';
|
|
51
54
|
|
|
52
55
|
/** Arguments to the process method of the rollup contract */
|
|
@@ -115,9 +118,12 @@ export class SequencerPublisher {
|
|
|
115
118
|
protected lastActions: Partial<Record<Action, SlotNumber>> = {};
|
|
116
119
|
|
|
117
120
|
private isPayloadEmptyCache: Map<string, boolean> = new Map<string, boolean>();
|
|
121
|
+
private payloadProposedCache: Set<string> = new Set<string>();
|
|
118
122
|
|
|
119
123
|
protected log: Logger;
|
|
120
124
|
protected ethereumSlotDuration: bigint;
|
|
125
|
+
protected aztecSlotDuration: bigint;
|
|
126
|
+
private dateProvider: DateProvider;
|
|
121
127
|
|
|
122
128
|
private blobClient: BlobClientInterface;
|
|
123
129
|
|
|
@@ -136,7 +142,7 @@ export class SequencerPublisher {
|
|
|
136
142
|
// Gas report for VotingWithSigTest shows a max gas of 100k, but we've seen it cost 700k+ in testnet
|
|
137
143
|
public static VOTE_GAS_GUESS: bigint = 800_000n;
|
|
138
144
|
|
|
139
|
-
public l1TxUtils:
|
|
145
|
+
public l1TxUtils: L1TxUtils;
|
|
140
146
|
public rollupContract: RollupContract;
|
|
141
147
|
public govProposerContract: GovernanceProposerContract;
|
|
142
148
|
public slashingProposerContract: EmpireSlashingProposerContract | TallySlashingProposerContract | undefined;
|
|
@@ -147,11 +153,12 @@ export class SequencerPublisher {
|
|
|
147
153
|
protected requests: RequestWithExpiry[] = [];
|
|
148
154
|
|
|
149
155
|
constructor(
|
|
150
|
-
private config:
|
|
156
|
+
private config: Pick<SequencerPublisherConfig, 'fishermanMode'> &
|
|
157
|
+
Pick<L1ContractsConfig, 'ethereumSlotDuration' | 'aztecSlotDuration'> & { l1ChainId: number },
|
|
151
158
|
deps: {
|
|
152
159
|
telemetry?: TelemetryClient;
|
|
153
160
|
blobClient: BlobClientInterface;
|
|
154
|
-
l1TxUtils:
|
|
161
|
+
l1TxUtils: L1TxUtils;
|
|
155
162
|
rollupContract: RollupContract;
|
|
156
163
|
slashingProposerContract: EmpireSlashingProposerContract | TallySlashingProposerContract | undefined;
|
|
157
164
|
governanceProposerContract: GovernanceProposerContract;
|
|
@@ -165,6 +172,8 @@ export class SequencerPublisher {
|
|
|
165
172
|
) {
|
|
166
173
|
this.log = deps.log ?? createLogger('sequencer:publisher');
|
|
167
174
|
this.ethereumSlotDuration = BigInt(config.ethereumSlotDuration);
|
|
175
|
+
this.aztecSlotDuration = BigInt(config.aztecSlotDuration);
|
|
176
|
+
this.dateProvider = deps.dateProvider;
|
|
168
177
|
this.epochCache = deps.epochCache;
|
|
169
178
|
this.lastActions = deps.lastActions;
|
|
170
179
|
|
|
@@ -353,8 +362,8 @@ export class SequencerPublisher {
|
|
|
353
362
|
// @note - we can only have one blob config per bundle
|
|
354
363
|
// find requests with gas and blob configs
|
|
355
364
|
// See https://github.com/AztecProtocol/aztec-packages/issues/11513
|
|
356
|
-
const gasConfigs =
|
|
357
|
-
const blobConfigs =
|
|
365
|
+
const gasConfigs = validRequests.filter(request => request.gasConfig).map(request => request.gasConfig);
|
|
366
|
+
const blobConfigs = validRequests.filter(request => request.blobConfig).map(request => request.blobConfig);
|
|
358
367
|
|
|
359
368
|
if (blobConfigs.length > 1) {
|
|
360
369
|
throw new Error('Multiple blob configs found');
|
|
@@ -422,7 +431,16 @@ export class SequencerPublisher {
|
|
|
422
431
|
this.log.error(`Failed to publish bundled transactions (${actionsListStr})`, result);
|
|
423
432
|
return { failedActions: requests.map(r => r.action) };
|
|
424
433
|
} else {
|
|
425
|
-
this.log.verbose(`Published bundled transactions (${actionsListStr})`, {
|
|
434
|
+
this.log.verbose(`Published bundled transactions (${actionsListStr})`, {
|
|
435
|
+
result,
|
|
436
|
+
requests: requests.map(r => ({
|
|
437
|
+
...r,
|
|
438
|
+
// Avoid logging large blob data
|
|
439
|
+
blobConfig: r.blobConfig
|
|
440
|
+
? { ...r.blobConfig, blobs: r.blobConfig.blobs.map(b => ({ size: trimmedBytesLength(b) })) }
|
|
441
|
+
: undefined,
|
|
442
|
+
})),
|
|
443
|
+
});
|
|
426
444
|
const successfulActions: Action[] = [];
|
|
427
445
|
const failedActions: Action[] = [];
|
|
428
446
|
for (const request of requests) {
|
|
@@ -437,20 +455,24 @@ export class SequencerPublisher {
|
|
|
437
455
|
}
|
|
438
456
|
|
|
439
457
|
/**
|
|
440
|
-
* @notice Will call `
|
|
458
|
+
* @notice Will call `canProposeAt` to make sure that it is possible to propose
|
|
441
459
|
* @param tipArchive - The archive to check
|
|
442
460
|
* @returns The slot and block number if it is possible to propose, undefined otherwise
|
|
443
461
|
*/
|
|
444
|
-
public
|
|
462
|
+
public canProposeAt(
|
|
445
463
|
tipArchive: Fr,
|
|
446
464
|
msgSender: EthAddress,
|
|
447
|
-
opts: { forcePendingCheckpointNumber?: CheckpointNumber } = {},
|
|
465
|
+
opts: { forcePendingCheckpointNumber?: CheckpointNumber; pipelined?: boolean } = {},
|
|
448
466
|
) {
|
|
449
467
|
// TODO: #14291 - should loop through multiple keys to check if any of them can propose
|
|
450
468
|
const ignoredErrors = ['SlotAlreadyInChain', 'InvalidProposer', 'InvalidArchive'];
|
|
451
469
|
|
|
470
|
+
const pipelined = opts.pipelined ?? false;
|
|
471
|
+
const slotOffset = pipelined ? this.aztecSlotDuration : 0n;
|
|
472
|
+
const nextL1SlotTs = this.getNextL1SlotTimestamp() + slotOffset;
|
|
473
|
+
|
|
452
474
|
return this.rollupContract
|
|
453
|
-
.
|
|
475
|
+
.canProposeAt(tipArchive.toBuffer(), msgSender.toString(), nextL1SlotTs, {
|
|
454
476
|
forcePendingCheckpointNumber: opts.forcePendingCheckpointNumber,
|
|
455
477
|
})
|
|
456
478
|
.catch(err => {
|
|
@@ -487,7 +509,7 @@ export class SequencerPublisher {
|
|
|
487
509
|
flags,
|
|
488
510
|
] as const;
|
|
489
511
|
|
|
490
|
-
const ts =
|
|
512
|
+
const ts = this.getSimulationTimestamp(header.slotNumber);
|
|
491
513
|
const stateOverrides = await this.rollupContract.makePendingCheckpointNumberOverride(
|
|
492
514
|
opts?.forcePendingCheckpointNumber,
|
|
493
515
|
);
|
|
@@ -510,7 +532,7 @@ export class SequencerPublisher {
|
|
|
510
532
|
data: encodeFunctionData({ abi: RollupAbi, functionName: 'validateHeaderWithAttestations', args }),
|
|
511
533
|
from: MULTI_CALL_3_ADDRESS,
|
|
512
534
|
},
|
|
513
|
-
{ time: ts
|
|
535
|
+
{ time: ts },
|
|
514
536
|
stateOverrides,
|
|
515
537
|
);
|
|
516
538
|
this.log.debug(`Simulated validateHeader`);
|
|
@@ -637,26 +659,9 @@ export class SequencerPublisher {
|
|
|
637
659
|
attestationsAndSigners: CommitteeAttestationsAndSigners,
|
|
638
660
|
attestationsAndSignersSignature: Signature,
|
|
639
661
|
options: { forcePendingCheckpointNumber?: CheckpointNumber },
|
|
640
|
-
): Promise<
|
|
641
|
-
const ts = BigInt((await this.l1TxUtils.getBlock()).timestamp + this.ethereumSlotDuration);
|
|
642
|
-
|
|
643
|
-
// TODO(palla/mbps): This should not be needed, there's no flow where we propose with zero attestations. Or is there?
|
|
644
|
-
// If we have no attestations, we still need to provide the empty attestations
|
|
645
|
-
// so that the committee is recalculated correctly
|
|
646
|
-
// const ignoreSignatures = attestationsAndSigners.attestations.length === 0;
|
|
647
|
-
// if (ignoreSignatures) {
|
|
648
|
-
// const { committee } = await this.epochCache.getCommittee(block.header.globalVariables.slotNumber);
|
|
649
|
-
// if (!committee) {
|
|
650
|
-
// this.log.warn(`No committee found for slot ${block.header.globalVariables.slotNumber}`);
|
|
651
|
-
// throw new Error(`No committee found for slot ${block.header.globalVariables.slotNumber}`);
|
|
652
|
-
// }
|
|
653
|
-
// attestationsAndSigners.attestations = committee.map(committeeMember =>
|
|
654
|
-
// CommitteeAttestation.fromAddress(committeeMember),
|
|
655
|
-
// );
|
|
656
|
-
// }
|
|
657
|
-
|
|
662
|
+
): Promise<void> {
|
|
658
663
|
const blobFields = checkpoint.toBlobFields();
|
|
659
|
-
const blobs = getBlobsPerL1Block(blobFields);
|
|
664
|
+
const blobs = await getBlobsPerL1Block(blobFields);
|
|
660
665
|
const blobInput = getPrefixedEthBlobCommitments(blobs);
|
|
661
666
|
|
|
662
667
|
const args = [
|
|
@@ -673,13 +678,11 @@ export class SequencerPublisher {
|
|
|
673
678
|
blobInput,
|
|
674
679
|
] as const;
|
|
675
680
|
|
|
676
|
-
await this.simulateProposeTx(args,
|
|
677
|
-
return ts;
|
|
681
|
+
await this.simulateProposeTx(args, options);
|
|
678
682
|
}
|
|
679
683
|
|
|
680
684
|
private async enqueueCastSignalHelper(
|
|
681
685
|
slotNumber: SlotNumber,
|
|
682
|
-
timestamp: bigint,
|
|
683
686
|
signalType: GovernanceSignalAction,
|
|
684
687
|
payload: EthAddress,
|
|
685
688
|
base: IEmpireBase,
|
|
@@ -713,6 +716,32 @@ export class SequencerPublisher {
|
|
|
713
716
|
return false;
|
|
714
717
|
}
|
|
715
718
|
|
|
719
|
+
// Check if payload was already submitted to governance
|
|
720
|
+
const cacheKey = payload.toString();
|
|
721
|
+
if (!this.payloadProposedCache.has(cacheKey)) {
|
|
722
|
+
try {
|
|
723
|
+
const l1StartBlock = await this.rollupContract.getL1StartBlock();
|
|
724
|
+
const proposed = await retry(
|
|
725
|
+
() => base.hasPayloadBeenProposed(payload.toString(), l1StartBlock),
|
|
726
|
+
'Check if payload was proposed',
|
|
727
|
+
makeBackoff([0, 1, 2]),
|
|
728
|
+
this.log,
|
|
729
|
+
true,
|
|
730
|
+
);
|
|
731
|
+
if (proposed) {
|
|
732
|
+
this.payloadProposedCache.add(cacheKey);
|
|
733
|
+
}
|
|
734
|
+
} catch (err) {
|
|
735
|
+
this.log.warn(`Failed to check if payload ${payload} was proposed after retries, skipping signal`, err);
|
|
736
|
+
return false;
|
|
737
|
+
}
|
|
738
|
+
}
|
|
739
|
+
|
|
740
|
+
if (this.payloadProposedCache.has(cacheKey)) {
|
|
741
|
+
this.log.info(`Payload ${payload} was already proposed to governance, stopping signals`);
|
|
742
|
+
return false;
|
|
743
|
+
}
|
|
744
|
+
|
|
716
745
|
const cachedLastVote = this.lastActions[signalType];
|
|
717
746
|
this.lastActions[signalType] = slotNumber;
|
|
718
747
|
const action = signalType;
|
|
@@ -731,11 +760,16 @@ export class SequencerPublisher {
|
|
|
731
760
|
lastValidL2Slot: slotNumber,
|
|
732
761
|
});
|
|
733
762
|
|
|
763
|
+
const timestamp = this.getSimulationTimestamp(slotNumber);
|
|
764
|
+
|
|
734
765
|
try {
|
|
735
766
|
await this.l1TxUtils.simulate(request, { time: timestamp }, [], mergeAbis([request.abi ?? [], ErrorsAbi]));
|
|
736
767
|
this.log.debug(`Simulation for ${action} at slot ${slotNumber} succeeded`, { request });
|
|
737
768
|
} catch (err) {
|
|
738
|
-
|
|
769
|
+
const viemError = formatViemError(err);
|
|
770
|
+
this.log.error(`Failed simulation for ${action} at slot ${slotNumber} (enqueuing the action anyway)`, viemError, {
|
|
771
|
+
simulationTimestamp: timestamp,
|
|
772
|
+
});
|
|
739
773
|
// Yes, we enqueue the request anyway, in case there was a bug with the simulation itself
|
|
740
774
|
}
|
|
741
775
|
|
|
@@ -786,19 +820,16 @@ export class SequencerPublisher {
|
|
|
786
820
|
/**
|
|
787
821
|
* Enqueues a governance castSignal transaction to cast a signal for a given slot number.
|
|
788
822
|
* @param slotNumber - The slot number to cast a signal for.
|
|
789
|
-
* @param timestamp - The timestamp of the slot to cast a signal for.
|
|
790
823
|
* @returns True if the signal was successfully enqueued, false otherwise.
|
|
791
824
|
*/
|
|
792
825
|
public enqueueGovernanceCastSignal(
|
|
793
826
|
governancePayload: EthAddress,
|
|
794
827
|
slotNumber: SlotNumber,
|
|
795
|
-
timestamp: bigint,
|
|
796
828
|
signerAddress: EthAddress,
|
|
797
829
|
signer: (msg: TypedDataDefinition) => Promise<`0x${string}`>,
|
|
798
830
|
): Promise<boolean> {
|
|
799
831
|
return this.enqueueCastSignalHelper(
|
|
800
832
|
slotNumber,
|
|
801
|
-
timestamp,
|
|
802
833
|
'governance-signal',
|
|
803
834
|
governancePayload,
|
|
804
835
|
this.govProposerContract,
|
|
@@ -811,7 +842,6 @@ export class SequencerPublisher {
|
|
|
811
842
|
public async enqueueSlashingActions(
|
|
812
843
|
actions: ProposerSlashAction[],
|
|
813
844
|
slotNumber: SlotNumber,
|
|
814
|
-
timestamp: bigint,
|
|
815
845
|
signerAddress: EthAddress,
|
|
816
846
|
signer: (msg: TypedDataDefinition) => Promise<`0x${string}`>,
|
|
817
847
|
): Promise<boolean> {
|
|
@@ -832,7 +862,6 @@ export class SequencerPublisher {
|
|
|
832
862
|
});
|
|
833
863
|
await this.enqueueCastSignalHelper(
|
|
834
864
|
slotNumber,
|
|
835
|
-
timestamp,
|
|
836
865
|
'empire-slashing-signal',
|
|
837
866
|
action.payload,
|
|
838
867
|
this.slashingProposerContract,
|
|
@@ -851,7 +880,6 @@ export class SequencerPublisher {
|
|
|
851
880
|
(receipt: TransactionReceipt) =>
|
|
852
881
|
!!this.slashFactoryContract.tryExtractSlashPayloadCreatedEvent(receipt.logs),
|
|
853
882
|
slotNumber,
|
|
854
|
-
timestamp,
|
|
855
883
|
);
|
|
856
884
|
break;
|
|
857
885
|
}
|
|
@@ -869,7 +897,6 @@ export class SequencerPublisher {
|
|
|
869
897
|
request,
|
|
870
898
|
(receipt: TransactionReceipt) => !!empireSlashingProposer.tryExtractPayloadSubmittedEvent(receipt.logs),
|
|
871
899
|
slotNumber,
|
|
872
|
-
timestamp,
|
|
873
900
|
);
|
|
874
901
|
break;
|
|
875
902
|
}
|
|
@@ -893,7 +920,6 @@ export class SequencerPublisher {
|
|
|
893
920
|
request,
|
|
894
921
|
(receipt: TransactionReceipt) => !!tallySlashingProposer.tryExtractVoteCastEvent(receipt.logs),
|
|
895
922
|
slotNumber,
|
|
896
|
-
timestamp,
|
|
897
923
|
);
|
|
898
924
|
break;
|
|
899
925
|
}
|
|
@@ -915,7 +941,6 @@ export class SequencerPublisher {
|
|
|
915
941
|
request,
|
|
916
942
|
(receipt: TransactionReceipt) => !!tallySlashingProposer.tryExtractRoundExecutedEvent(receipt.logs),
|
|
917
943
|
slotNumber,
|
|
918
|
-
timestamp,
|
|
919
944
|
);
|
|
920
945
|
break;
|
|
921
946
|
}
|
|
@@ -940,7 +965,7 @@ export class SequencerPublisher {
|
|
|
940
965
|
const checkpointHeader = checkpoint.header;
|
|
941
966
|
|
|
942
967
|
const blobFields = checkpoint.toBlobFields();
|
|
943
|
-
const blobs = getBlobsPerL1Block(blobFields);
|
|
968
|
+
const blobs = await getBlobsPerL1Block(blobFields);
|
|
944
969
|
|
|
945
970
|
const proposeTxArgs: L1ProcessArgs = {
|
|
946
971
|
header: checkpointHeader,
|
|
@@ -951,15 +976,13 @@ export class SequencerPublisher {
|
|
|
951
976
|
feeAssetPriceModifier: checkpoint.feeAssetPriceModifier,
|
|
952
977
|
};
|
|
953
978
|
|
|
954
|
-
let ts: bigint;
|
|
955
|
-
|
|
956
979
|
try {
|
|
957
980
|
// @note This will make sure that we are passing the checks for our header ASSUMING that the data is also made available
|
|
958
981
|
// This means that we can avoid the simulation issues in later checks.
|
|
959
982
|
// By simulation issue, I mean the fact that the block.timestamp is equal to the last block, not the next, which
|
|
960
983
|
// make time consistency checks break.
|
|
961
984
|
// TODO(palla): Check whether we're validating twice, once here and once within addProposeTx, since we call simulateProposeTx in both places.
|
|
962
|
-
|
|
985
|
+
await this.validateCheckpointForSubmission(
|
|
963
986
|
checkpoint,
|
|
964
987
|
attestationsAndSigners,
|
|
965
988
|
attestationsAndSignersSignature,
|
|
@@ -975,7 +998,7 @@ export class SequencerPublisher {
|
|
|
975
998
|
}
|
|
976
999
|
|
|
977
1000
|
this.log.verbose(`Enqueuing checkpoint propose transaction`, { ...checkpoint.toCheckpointInfo(), ...opts });
|
|
978
|
-
await this.addProposeTx(checkpoint, proposeTxArgs, opts
|
|
1001
|
+
await this.addProposeTx(checkpoint, proposeTxArgs, opts);
|
|
979
1002
|
}
|
|
980
1003
|
|
|
981
1004
|
public enqueueInvalidateCheckpoint(
|
|
@@ -1018,8 +1041,8 @@ export class SequencerPublisher {
|
|
|
1018
1041
|
request: L1TxRequest,
|
|
1019
1042
|
checkSuccess: (receipt: TransactionReceipt) => boolean | undefined,
|
|
1020
1043
|
slotNumber: SlotNumber,
|
|
1021
|
-
timestamp: bigint,
|
|
1022
1044
|
) {
|
|
1045
|
+
const timestamp = this.getSimulationTimestamp(slotNumber);
|
|
1023
1046
|
const logData = { slotNumber, timestamp, gasLimit: undefined as bigint | undefined };
|
|
1024
1047
|
if (this.lastActions[action] && this.lastActions[action] === slotNumber) {
|
|
1025
1048
|
this.log.debug(`Skipping duplicate action ${action} for slot ${slotNumber}`);
|
|
@@ -1033,8 +1056,9 @@ export class SequencerPublisher {
|
|
|
1033
1056
|
|
|
1034
1057
|
let gasUsed: bigint;
|
|
1035
1058
|
const simulateAbi = mergeAbis([request.abi ?? [], ErrorsAbi]);
|
|
1059
|
+
|
|
1036
1060
|
try {
|
|
1037
|
-
({ gasUsed } = await this.l1TxUtils.simulate(request, { time: timestamp }, [], simulateAbi));
|
|
1061
|
+
({ gasUsed } = await this.l1TxUtils.simulate(request, { time: timestamp }, [], simulateAbi));
|
|
1038
1062
|
this.log.verbose(`Simulation for ${action} succeeded`, { ...logData, request, gasUsed });
|
|
1039
1063
|
} catch (err) {
|
|
1040
1064
|
const viemError = formatViemError(err, simulateAbi);
|
|
@@ -1090,7 +1114,6 @@ export class SequencerPublisher {
|
|
|
1090
1114
|
|
|
1091
1115
|
private async prepareProposeTx(
|
|
1092
1116
|
encodedData: L1ProcessArgs,
|
|
1093
|
-
timestamp: bigint,
|
|
1094
1117
|
options: { forcePendingCheckpointNumber?: CheckpointNumber },
|
|
1095
1118
|
) {
|
|
1096
1119
|
const kzg = Blob.getViemKzgInstance();
|
|
@@ -1145,7 +1168,7 @@ export class SequencerPublisher {
|
|
|
1145
1168
|
blobInput,
|
|
1146
1169
|
] as const;
|
|
1147
1170
|
|
|
1148
|
-
const { rollupData, simulationResult } = await this.simulateProposeTx(args,
|
|
1171
|
+
const { rollupData, simulationResult } = await this.simulateProposeTx(args, options);
|
|
1149
1172
|
|
|
1150
1173
|
return { args, blobEvaluationGas, rollupData, simulationResult };
|
|
1151
1174
|
}
|
|
@@ -1153,7 +1176,6 @@ export class SequencerPublisher {
|
|
|
1153
1176
|
/**
|
|
1154
1177
|
* Simulates the propose tx with eth_simulateV1
|
|
1155
1178
|
* @param args - The propose tx args
|
|
1156
|
-
* @param timestamp - The timestamp to simulate proposal at
|
|
1157
1179
|
* @returns The simulation result
|
|
1158
1180
|
*/
|
|
1159
1181
|
private async simulateProposeTx(
|
|
@@ -1170,7 +1192,6 @@ export class SequencerPublisher {
|
|
|
1170
1192
|
ViemSignature,
|
|
1171
1193
|
`0x${string}`,
|
|
1172
1194
|
],
|
|
1173
|
-
timestamp: bigint,
|
|
1174
1195
|
options: { forcePendingCheckpointNumber?: CheckpointNumber },
|
|
1175
1196
|
) {
|
|
1176
1197
|
const rollupData = encodeFunctionData({
|
|
@@ -1204,6 +1225,8 @@ export class SequencerPublisher {
|
|
|
1204
1225
|
});
|
|
1205
1226
|
}
|
|
1206
1227
|
|
|
1228
|
+
const simTs = this.getSimulationTimestamp(SlotNumber.fromBigInt(args[0].header.slotNumber));
|
|
1229
|
+
|
|
1207
1230
|
const simulationResult = await this.l1TxUtils
|
|
1208
1231
|
.simulate(
|
|
1209
1232
|
{
|
|
@@ -1213,8 +1236,7 @@ export class SequencerPublisher {
|
|
|
1213
1236
|
...(this.proposerAddressForSimulation && { from: this.proposerAddressForSimulation.toString() }),
|
|
1214
1237
|
},
|
|
1215
1238
|
{
|
|
1216
|
-
|
|
1217
|
-
time: timestamp + 1n,
|
|
1239
|
+
time: simTs,
|
|
1218
1240
|
// @note reth should have a 30m gas limit per block but throws errors that this tx is beyond limit so we increase here
|
|
1219
1241
|
gasLimit: MAX_L1_TX_LIMIT * 2n,
|
|
1220
1242
|
},
|
|
@@ -1236,7 +1258,7 @@ export class SequencerPublisher {
|
|
|
1236
1258
|
logs: [],
|
|
1237
1259
|
};
|
|
1238
1260
|
}
|
|
1239
|
-
this.log.error(`Failed to simulate propose tx`, viemError);
|
|
1261
|
+
this.log.error(`Failed to simulate propose tx`, viemError, { simulationTimestamp: simTs });
|
|
1240
1262
|
throw err;
|
|
1241
1263
|
});
|
|
1242
1264
|
|
|
@@ -1247,16 +1269,11 @@ export class SequencerPublisher {
|
|
|
1247
1269
|
checkpoint: Checkpoint,
|
|
1248
1270
|
encodedData: L1ProcessArgs,
|
|
1249
1271
|
opts: { txTimeoutAt?: Date; forcePendingCheckpointNumber?: CheckpointNumber } = {},
|
|
1250
|
-
timestamp: bigint,
|
|
1251
1272
|
): Promise<void> {
|
|
1252
1273
|
const slot = checkpoint.header.slotNumber;
|
|
1253
1274
|
const timer = new Timer();
|
|
1254
1275
|
const kzg = Blob.getViemKzgInstance();
|
|
1255
|
-
const { rollupData, simulationResult, blobEvaluationGas } = await this.prepareProposeTx(
|
|
1256
|
-
encodedData,
|
|
1257
|
-
timestamp,
|
|
1258
|
-
opts,
|
|
1259
|
-
);
|
|
1276
|
+
const { rollupData, simulationResult, blobEvaluationGas } = await this.prepareProposeTx(encodedData, opts);
|
|
1260
1277
|
const startBlock = await this.l1TxUtils.getBlockNumber();
|
|
1261
1278
|
const gasLimit = this.l1TxUtils.bumpGasLimit(
|
|
1262
1279
|
BigInt(Math.ceil((Number(simulationResult.gasUsed) * 64) / 63)) +
|
|
@@ -1332,4 +1349,17 @@ export class SequencerPublisher {
|
|
|
1332
1349
|
},
|
|
1333
1350
|
});
|
|
1334
1351
|
}
|
|
1352
|
+
|
|
1353
|
+
/** Returns the timestamp of the last L1 slot within a given L2 slot. Used as the simulation timestamp
|
|
1354
|
+
* for eth_simulateV1 calls, since it's guaranteed to be greater than any L1 block produced during the slot. */
|
|
1355
|
+
private getSimulationTimestamp(slot: SlotNumber): bigint {
|
|
1356
|
+
const l1Constants = this.epochCache.getL1Constants();
|
|
1357
|
+
return getLastL1SlotTimestampForL2Slot(slot, l1Constants);
|
|
1358
|
+
}
|
|
1359
|
+
|
|
1360
|
+
/** Returns the timestamp of the next L1 slot boundary after now. */
|
|
1361
|
+
private getNextL1SlotTimestamp(): bigint {
|
|
1362
|
+
const l1Constants = this.epochCache.getL1Constants();
|
|
1363
|
+
return getNextL1SlotTimestamp(this.dateProvider.nowInSeconds(), l1Constants);
|
|
1364
|
+
}
|
|
1335
1365
|
}
|