@aztec/sequencer-client 0.0.1-commit.23b0eb0 → 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 -6
- package/dest/config.d.ts.map +1 -1
- package/dest/config.js +46 -28
- 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 +29 -13
- package/dest/publisher/sequencer-publisher.d.ts.map +1 -1
- package/dest/publisher/sequencer-publisher.js +106 -57
- package/dest/sequencer/checkpoint_proposal_job.d.ts +6 -4
- package/dest/sequencer/checkpoint_proposal_job.d.ts.map +1 -1
- package/dest/sequencer/checkpoint_proposal_job.js +139 -80
- 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 +17 -5
- package/dest/sequencer/metrics.d.ts.map +1 -1
- package/dest/sequencer/metrics.js +86 -15
- package/dest/sequencer/sequencer.d.ts +27 -13
- package/dest/sequencer/sequencer.d.ts.map +1 -1
- package/dest/sequencer/sequencer.js +42 -42
- package/dest/sequencer/timetable.d.ts +4 -6
- package/dest/sequencer/timetable.d.ts.map +1 -1
- package/dest/sequencer/timetable.js +7 -11
- 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 +11 -11
- package/dest/test/mock_checkpoint_builder.d.ts.map +1 -1
- package/dest/test/mock_checkpoint_builder.js +47 -34
- package/dest/test/utils.d.ts +3 -3
- package/dest/test/utils.d.ts.map +1 -1
- package/dest/test/utils.js +5 -4
- package/package.json +28 -28
- package/src/client/sequencer-client.ts +78 -21
- package/src/config.ts +61 -38
- 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 +123 -71
- package/src/sequencer/checkpoint_proposal_job.ts +213 -109
- package/src/sequencer/checkpoint_voter.ts +1 -12
- package/src/sequencer/metrics.ts +92 -18
- package/src/sequencer/sequencer.ts +53 -48
- package/src/sequencer/timetable.ts +13 -12
- package/src/sequencer/types.ts +1 -1
- package/src/test/index.ts +2 -4
- package/src/test/mock_checkpoint_builder.ts +65 -49
- package/src/test/utils.ts +5 -2
|
@@ -4,6 +4,7 @@ import type { EpochCache } from '@aztec/epoch-cache';
|
|
|
4
4
|
import type { L1ContractsConfig } from '@aztec/ethereum/config';
|
|
5
5
|
import {
|
|
6
6
|
type EmpireSlashingProposerContract,
|
|
7
|
+
FeeAssetPriceOracle,
|
|
7
8
|
type GovernanceProposerContract,
|
|
8
9
|
type IEmpireBase,
|
|
9
10
|
MULTI_CALL_3_ADDRESS,
|
|
@@ -18,26 +19,29 @@ import {
|
|
|
18
19
|
type L1BlobInputs,
|
|
19
20
|
type L1TxConfig,
|
|
20
21
|
type L1TxRequest,
|
|
22
|
+
type L1TxUtils,
|
|
21
23
|
MAX_L1_TX_LIMIT,
|
|
22
24
|
type TransactionStats,
|
|
23
25
|
WEI_CONST,
|
|
24
26
|
} from '@aztec/ethereum/l1-tx-utils';
|
|
25
|
-
import type { L1TxUtilsWithBlobs } from '@aztec/ethereum/l1-tx-utils-with-blobs';
|
|
26
27
|
import { FormattedViemError, formatViemError, mergeAbis, tryExtractEvent } from '@aztec/ethereum/utils';
|
|
27
28
|
import { sumBigint } from '@aztec/foundation/bigint';
|
|
28
29
|
import { toHex as toPaddedHex } from '@aztec/foundation/bigint-buffer';
|
|
29
30
|
import { CheckpointNumber, SlotNumber } from '@aztec/foundation/branded-types';
|
|
31
|
+
import { trimmedBytesLength } from '@aztec/foundation/buffer';
|
|
30
32
|
import { pick } from '@aztec/foundation/collection';
|
|
31
33
|
import type { Fr } from '@aztec/foundation/curves/bn254';
|
|
32
34
|
import { EthAddress } from '@aztec/foundation/eth-address';
|
|
33
35
|
import { Signature, type ViemSignature } from '@aztec/foundation/eth-signature';
|
|
34
36
|
import { type Logger, createLogger } from '@aztec/foundation/log';
|
|
37
|
+
import { makeBackoff, retry } from '@aztec/foundation/retry';
|
|
35
38
|
import { bufferToHex } from '@aztec/foundation/string';
|
|
36
39
|
import { DateProvider, Timer } from '@aztec/foundation/timer';
|
|
37
40
|
import { EmpireBaseAbi, ErrorsAbi, RollupAbi } from '@aztec/l1-artifacts';
|
|
38
41
|
import { type ProposerSlashAction, encodeSlashConsensusVotes } from '@aztec/slasher';
|
|
39
42
|
import { CommitteeAttestationsAndSigners, type ValidateCheckpointResult } from '@aztec/stdlib/block';
|
|
40
43
|
import type { Checkpoint } from '@aztec/stdlib/checkpoint';
|
|
44
|
+
import { getLastL1SlotTimestampForL2Slot, getNextL1SlotTimestamp } from '@aztec/stdlib/epoch-helpers';
|
|
41
45
|
import { SlashFactoryContract } from '@aztec/stdlib/l1-contracts';
|
|
42
46
|
import type { CheckpointHeader } from '@aztec/stdlib/rollup';
|
|
43
47
|
import type { L1PublishCheckpointStats } from '@aztec/stdlib/stats';
|
|
@@ -45,7 +49,7 @@ import { type TelemetryClient, type Tracer, getTelemetryClient, trackSpan } from
|
|
|
45
49
|
|
|
46
50
|
import { type StateOverride, type TransactionReceipt, type TypedDataDefinition, encodeFunctionData, toHex } from 'viem';
|
|
47
51
|
|
|
48
|
-
import type {
|
|
52
|
+
import type { SequencerPublisherConfig } from './config.js';
|
|
49
53
|
import { SequencerPublisherMetrics } from './sequencer-publisher-metrics.js';
|
|
50
54
|
|
|
51
55
|
/** Arguments to the process method of the rollup contract */
|
|
@@ -60,6 +64,8 @@ type L1ProcessArgs = {
|
|
|
60
64
|
attestationsAndSigners: CommitteeAttestationsAndSigners;
|
|
61
65
|
/** Attestations and signers signature */
|
|
62
66
|
attestationsAndSignersSignature: Signature;
|
|
67
|
+
/** The fee asset price modifier in basis points (from oracle) */
|
|
68
|
+
feeAssetPriceModifier: bigint;
|
|
63
69
|
};
|
|
64
70
|
|
|
65
71
|
export const Actions = [
|
|
@@ -112,9 +118,12 @@ export class SequencerPublisher {
|
|
|
112
118
|
protected lastActions: Partial<Record<Action, SlotNumber>> = {};
|
|
113
119
|
|
|
114
120
|
private isPayloadEmptyCache: Map<string, boolean> = new Map<string, boolean>();
|
|
121
|
+
private payloadProposedCache: Set<string> = new Set<string>();
|
|
115
122
|
|
|
116
123
|
protected log: Logger;
|
|
117
124
|
protected ethereumSlotDuration: bigint;
|
|
125
|
+
protected aztecSlotDuration: bigint;
|
|
126
|
+
private dateProvider: DateProvider;
|
|
118
127
|
|
|
119
128
|
private blobClient: BlobClientInterface;
|
|
120
129
|
|
|
@@ -123,13 +132,17 @@ export class SequencerPublisher {
|
|
|
123
132
|
|
|
124
133
|
/** L1 fee analyzer for fisherman mode */
|
|
125
134
|
private l1FeeAnalyzer?: L1FeeAnalyzer;
|
|
135
|
+
|
|
136
|
+
/** Fee asset price oracle for computing price modifiers from Uniswap V4 */
|
|
137
|
+
private feeAssetPriceOracle: FeeAssetPriceOracle;
|
|
138
|
+
|
|
126
139
|
// A CALL to a cold address is 2700 gas
|
|
127
140
|
public static MULTICALL_OVERHEAD_GAS_GUESS = 5000n;
|
|
128
141
|
|
|
129
142
|
// Gas report for VotingWithSigTest shows a max gas of 100k, but we've seen it cost 700k+ in testnet
|
|
130
143
|
public static VOTE_GAS_GUESS: bigint = 800_000n;
|
|
131
144
|
|
|
132
|
-
public l1TxUtils:
|
|
145
|
+
public l1TxUtils: L1TxUtils;
|
|
133
146
|
public rollupContract: RollupContract;
|
|
134
147
|
public govProposerContract: GovernanceProposerContract;
|
|
135
148
|
public slashingProposerContract: EmpireSlashingProposerContract | TallySlashingProposerContract | undefined;
|
|
@@ -140,11 +153,12 @@ export class SequencerPublisher {
|
|
|
140
153
|
protected requests: RequestWithExpiry[] = [];
|
|
141
154
|
|
|
142
155
|
constructor(
|
|
143
|
-
private config:
|
|
156
|
+
private config: Pick<SequencerPublisherConfig, 'fishermanMode'> &
|
|
157
|
+
Pick<L1ContractsConfig, 'ethereumSlotDuration' | 'aztecSlotDuration'> & { l1ChainId: number },
|
|
144
158
|
deps: {
|
|
145
159
|
telemetry?: TelemetryClient;
|
|
146
160
|
blobClient: BlobClientInterface;
|
|
147
|
-
l1TxUtils:
|
|
161
|
+
l1TxUtils: L1TxUtils;
|
|
148
162
|
rollupContract: RollupContract;
|
|
149
163
|
slashingProposerContract: EmpireSlashingProposerContract | TallySlashingProposerContract | undefined;
|
|
150
164
|
governanceProposerContract: GovernanceProposerContract;
|
|
@@ -158,6 +172,8 @@ export class SequencerPublisher {
|
|
|
158
172
|
) {
|
|
159
173
|
this.log = deps.log ?? createLogger('sequencer:publisher');
|
|
160
174
|
this.ethereumSlotDuration = BigInt(config.ethereumSlotDuration);
|
|
175
|
+
this.aztecSlotDuration = BigInt(config.aztecSlotDuration);
|
|
176
|
+
this.dateProvider = deps.dateProvider;
|
|
161
177
|
this.epochCache = deps.epochCache;
|
|
162
178
|
this.lastActions = deps.lastActions;
|
|
163
179
|
|
|
@@ -188,12 +204,27 @@ export class SequencerPublisher {
|
|
|
188
204
|
createLogger('sequencer:publisher:fee-analyzer'),
|
|
189
205
|
);
|
|
190
206
|
}
|
|
207
|
+
|
|
208
|
+
// Initialize fee asset price oracle
|
|
209
|
+
this.feeAssetPriceOracle = new FeeAssetPriceOracle(
|
|
210
|
+
this.l1TxUtils.client,
|
|
211
|
+
this.rollupContract,
|
|
212
|
+
createLogger('sequencer:publisher:price-oracle'),
|
|
213
|
+
);
|
|
191
214
|
}
|
|
192
215
|
|
|
193
216
|
public getRollupContract(): RollupContract {
|
|
194
217
|
return this.rollupContract;
|
|
195
218
|
}
|
|
196
219
|
|
|
220
|
+
/**
|
|
221
|
+
* Gets the fee asset price modifier from the oracle.
|
|
222
|
+
* Returns 0n if the oracle query fails.
|
|
223
|
+
*/
|
|
224
|
+
public getFeeAssetPriceModifier(): Promise<bigint> {
|
|
225
|
+
return this.feeAssetPriceOracle.computePriceModifier();
|
|
226
|
+
}
|
|
227
|
+
|
|
197
228
|
public getSenderAddress() {
|
|
198
229
|
return this.l1TxUtils.getSenderAddress();
|
|
199
230
|
}
|
|
@@ -331,8 +362,8 @@ export class SequencerPublisher {
|
|
|
331
362
|
// @note - we can only have one blob config per bundle
|
|
332
363
|
// find requests with gas and blob configs
|
|
333
364
|
// See https://github.com/AztecProtocol/aztec-packages/issues/11513
|
|
334
|
-
const gasConfigs =
|
|
335
|
-
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);
|
|
336
367
|
|
|
337
368
|
if (blobConfigs.length > 1) {
|
|
338
369
|
throw new Error('Multiple blob configs found');
|
|
@@ -400,7 +431,16 @@ export class SequencerPublisher {
|
|
|
400
431
|
this.log.error(`Failed to publish bundled transactions (${actionsListStr})`, result);
|
|
401
432
|
return { failedActions: requests.map(r => r.action) };
|
|
402
433
|
} else {
|
|
403
|
-
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
|
+
});
|
|
404
444
|
const successfulActions: Action[] = [];
|
|
405
445
|
const failedActions: Action[] = [];
|
|
406
446
|
for (const request of requests) {
|
|
@@ -415,20 +455,24 @@ export class SequencerPublisher {
|
|
|
415
455
|
}
|
|
416
456
|
|
|
417
457
|
/**
|
|
418
|
-
* @notice Will call `
|
|
458
|
+
* @notice Will call `canProposeAt` to make sure that it is possible to propose
|
|
419
459
|
* @param tipArchive - The archive to check
|
|
420
460
|
* @returns The slot and block number if it is possible to propose, undefined otherwise
|
|
421
461
|
*/
|
|
422
|
-
public
|
|
462
|
+
public canProposeAt(
|
|
423
463
|
tipArchive: Fr,
|
|
424
464
|
msgSender: EthAddress,
|
|
425
|
-
opts: { forcePendingCheckpointNumber?: CheckpointNumber } = {},
|
|
465
|
+
opts: { forcePendingCheckpointNumber?: CheckpointNumber; pipelined?: boolean } = {},
|
|
426
466
|
) {
|
|
427
467
|
// TODO: #14291 - should loop through multiple keys to check if any of them can propose
|
|
428
468
|
const ignoredErrors = ['SlotAlreadyInChain', 'InvalidProposer', 'InvalidArchive'];
|
|
429
469
|
|
|
470
|
+
const pipelined = opts.pipelined ?? false;
|
|
471
|
+
const slotOffset = pipelined ? this.aztecSlotDuration : 0n;
|
|
472
|
+
const nextL1SlotTs = this.getNextL1SlotTimestamp() + slotOffset;
|
|
473
|
+
|
|
430
474
|
return this.rollupContract
|
|
431
|
-
.
|
|
475
|
+
.canProposeAt(tipArchive.toBuffer(), msgSender.toString(), nextL1SlotTs, {
|
|
432
476
|
forcePendingCheckpointNumber: opts.forcePendingCheckpointNumber,
|
|
433
477
|
})
|
|
434
478
|
.catch(err => {
|
|
@@ -465,7 +509,7 @@ export class SequencerPublisher {
|
|
|
465
509
|
flags,
|
|
466
510
|
] as const;
|
|
467
511
|
|
|
468
|
-
const ts =
|
|
512
|
+
const ts = this.getSimulationTimestamp(header.slotNumber);
|
|
469
513
|
const stateOverrides = await this.rollupContract.makePendingCheckpointNumberOverride(
|
|
470
514
|
opts?.forcePendingCheckpointNumber,
|
|
471
515
|
);
|
|
@@ -488,7 +532,7 @@ export class SequencerPublisher {
|
|
|
488
532
|
data: encodeFunctionData({ abi: RollupAbi, functionName: 'validateHeaderWithAttestations', args }),
|
|
489
533
|
from: MULTI_CALL_3_ADDRESS,
|
|
490
534
|
},
|
|
491
|
-
{ time: ts
|
|
535
|
+
{ time: ts },
|
|
492
536
|
stateOverrides,
|
|
493
537
|
);
|
|
494
538
|
this.log.debug(`Simulated validateHeader`);
|
|
@@ -615,26 +659,9 @@ export class SequencerPublisher {
|
|
|
615
659
|
attestationsAndSigners: CommitteeAttestationsAndSigners,
|
|
616
660
|
attestationsAndSignersSignature: Signature,
|
|
617
661
|
options: { forcePendingCheckpointNumber?: CheckpointNumber },
|
|
618
|
-
): Promise<
|
|
619
|
-
const ts = BigInt((await this.l1TxUtils.getBlock()).timestamp + this.ethereumSlotDuration);
|
|
620
|
-
|
|
621
|
-
// TODO(palla/mbps): This should not be needed, there's no flow where we propose with zero attestations. Or is there?
|
|
622
|
-
// If we have no attestations, we still need to provide the empty attestations
|
|
623
|
-
// so that the committee is recalculated correctly
|
|
624
|
-
// const ignoreSignatures = attestationsAndSigners.attestations.length === 0;
|
|
625
|
-
// if (ignoreSignatures) {
|
|
626
|
-
// const { committee } = await this.epochCache.getCommittee(block.header.globalVariables.slotNumber);
|
|
627
|
-
// if (!committee) {
|
|
628
|
-
// this.log.warn(`No committee found for slot ${block.header.globalVariables.slotNumber}`);
|
|
629
|
-
// throw new Error(`No committee found for slot ${block.header.globalVariables.slotNumber}`);
|
|
630
|
-
// }
|
|
631
|
-
// attestationsAndSigners.attestations = committee.map(committeeMember =>
|
|
632
|
-
// CommitteeAttestation.fromAddress(committeeMember),
|
|
633
|
-
// );
|
|
634
|
-
// }
|
|
635
|
-
|
|
662
|
+
): Promise<void> {
|
|
636
663
|
const blobFields = checkpoint.toBlobFields();
|
|
637
|
-
const blobs = getBlobsPerL1Block(blobFields);
|
|
664
|
+
const blobs = await getBlobsPerL1Block(blobFields);
|
|
638
665
|
const blobInput = getPrefixedEthBlobCommitments(blobs);
|
|
639
666
|
|
|
640
667
|
const args = [
|
|
@@ -642,7 +669,7 @@ export class SequencerPublisher {
|
|
|
642
669
|
header: checkpoint.header.toViem(),
|
|
643
670
|
archive: toHex(checkpoint.archive.root.toBuffer()),
|
|
644
671
|
oracleInput: {
|
|
645
|
-
feeAssetPriceModifier:
|
|
672
|
+
feeAssetPriceModifier: checkpoint.feeAssetPriceModifier,
|
|
646
673
|
},
|
|
647
674
|
},
|
|
648
675
|
attestationsAndSigners.getPackedAttestations(),
|
|
@@ -651,13 +678,11 @@ export class SequencerPublisher {
|
|
|
651
678
|
blobInput,
|
|
652
679
|
] as const;
|
|
653
680
|
|
|
654
|
-
await this.simulateProposeTx(args,
|
|
655
|
-
return ts;
|
|
681
|
+
await this.simulateProposeTx(args, options);
|
|
656
682
|
}
|
|
657
683
|
|
|
658
684
|
private async enqueueCastSignalHelper(
|
|
659
685
|
slotNumber: SlotNumber,
|
|
660
|
-
timestamp: bigint,
|
|
661
686
|
signalType: GovernanceSignalAction,
|
|
662
687
|
payload: EthAddress,
|
|
663
688
|
base: IEmpireBase,
|
|
@@ -691,6 +716,32 @@ export class SequencerPublisher {
|
|
|
691
716
|
return false;
|
|
692
717
|
}
|
|
693
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
|
+
|
|
694
745
|
const cachedLastVote = this.lastActions[signalType];
|
|
695
746
|
this.lastActions[signalType] = slotNumber;
|
|
696
747
|
const action = signalType;
|
|
@@ -709,11 +760,16 @@ export class SequencerPublisher {
|
|
|
709
760
|
lastValidL2Slot: slotNumber,
|
|
710
761
|
});
|
|
711
762
|
|
|
763
|
+
const timestamp = this.getSimulationTimestamp(slotNumber);
|
|
764
|
+
|
|
712
765
|
try {
|
|
713
766
|
await this.l1TxUtils.simulate(request, { time: timestamp }, [], mergeAbis([request.abi ?? [], ErrorsAbi]));
|
|
714
767
|
this.log.debug(`Simulation for ${action} at slot ${slotNumber} succeeded`, { request });
|
|
715
768
|
} catch (err) {
|
|
716
|
-
|
|
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
|
+
});
|
|
717
773
|
// Yes, we enqueue the request anyway, in case there was a bug with the simulation itself
|
|
718
774
|
}
|
|
719
775
|
|
|
@@ -764,19 +820,16 @@ export class SequencerPublisher {
|
|
|
764
820
|
/**
|
|
765
821
|
* Enqueues a governance castSignal transaction to cast a signal for a given slot number.
|
|
766
822
|
* @param slotNumber - The slot number to cast a signal for.
|
|
767
|
-
* @param timestamp - The timestamp of the slot to cast a signal for.
|
|
768
823
|
* @returns True if the signal was successfully enqueued, false otherwise.
|
|
769
824
|
*/
|
|
770
825
|
public enqueueGovernanceCastSignal(
|
|
771
826
|
governancePayload: EthAddress,
|
|
772
827
|
slotNumber: SlotNumber,
|
|
773
|
-
timestamp: bigint,
|
|
774
828
|
signerAddress: EthAddress,
|
|
775
829
|
signer: (msg: TypedDataDefinition) => Promise<`0x${string}`>,
|
|
776
830
|
): Promise<boolean> {
|
|
777
831
|
return this.enqueueCastSignalHelper(
|
|
778
832
|
slotNumber,
|
|
779
|
-
timestamp,
|
|
780
833
|
'governance-signal',
|
|
781
834
|
governancePayload,
|
|
782
835
|
this.govProposerContract,
|
|
@@ -789,7 +842,6 @@ export class SequencerPublisher {
|
|
|
789
842
|
public async enqueueSlashingActions(
|
|
790
843
|
actions: ProposerSlashAction[],
|
|
791
844
|
slotNumber: SlotNumber,
|
|
792
|
-
timestamp: bigint,
|
|
793
845
|
signerAddress: EthAddress,
|
|
794
846
|
signer: (msg: TypedDataDefinition) => Promise<`0x${string}`>,
|
|
795
847
|
): Promise<boolean> {
|
|
@@ -810,7 +862,6 @@ export class SequencerPublisher {
|
|
|
810
862
|
});
|
|
811
863
|
await this.enqueueCastSignalHelper(
|
|
812
864
|
slotNumber,
|
|
813
|
-
timestamp,
|
|
814
865
|
'empire-slashing-signal',
|
|
815
866
|
action.payload,
|
|
816
867
|
this.slashingProposerContract,
|
|
@@ -829,7 +880,6 @@ export class SequencerPublisher {
|
|
|
829
880
|
(receipt: TransactionReceipt) =>
|
|
830
881
|
!!this.slashFactoryContract.tryExtractSlashPayloadCreatedEvent(receipt.logs),
|
|
831
882
|
slotNumber,
|
|
832
|
-
timestamp,
|
|
833
883
|
);
|
|
834
884
|
break;
|
|
835
885
|
}
|
|
@@ -847,7 +897,6 @@ export class SequencerPublisher {
|
|
|
847
897
|
request,
|
|
848
898
|
(receipt: TransactionReceipt) => !!empireSlashingProposer.tryExtractPayloadSubmittedEvent(receipt.logs),
|
|
849
899
|
slotNumber,
|
|
850
|
-
timestamp,
|
|
851
900
|
);
|
|
852
901
|
break;
|
|
853
902
|
}
|
|
@@ -871,7 +920,6 @@ export class SequencerPublisher {
|
|
|
871
920
|
request,
|
|
872
921
|
(receipt: TransactionReceipt) => !!tallySlashingProposer.tryExtractVoteCastEvent(receipt.logs),
|
|
873
922
|
slotNumber,
|
|
874
|
-
timestamp,
|
|
875
923
|
);
|
|
876
924
|
break;
|
|
877
925
|
}
|
|
@@ -893,7 +941,6 @@ export class SequencerPublisher {
|
|
|
893
941
|
request,
|
|
894
942
|
(receipt: TransactionReceipt) => !!tallySlashingProposer.tryExtractRoundExecutedEvent(receipt.logs),
|
|
895
943
|
slotNumber,
|
|
896
|
-
timestamp,
|
|
897
944
|
);
|
|
898
945
|
break;
|
|
899
946
|
}
|
|
@@ -918,25 +965,24 @@ export class SequencerPublisher {
|
|
|
918
965
|
const checkpointHeader = checkpoint.header;
|
|
919
966
|
|
|
920
967
|
const blobFields = checkpoint.toBlobFields();
|
|
921
|
-
const blobs = getBlobsPerL1Block(blobFields);
|
|
968
|
+
const blobs = await getBlobsPerL1Block(blobFields);
|
|
922
969
|
|
|
923
|
-
const proposeTxArgs = {
|
|
970
|
+
const proposeTxArgs: L1ProcessArgs = {
|
|
924
971
|
header: checkpointHeader,
|
|
925
972
|
archive: checkpoint.archive.root.toBuffer(),
|
|
926
973
|
blobs,
|
|
927
974
|
attestationsAndSigners,
|
|
928
975
|
attestationsAndSignersSignature,
|
|
976
|
+
feeAssetPriceModifier: checkpoint.feeAssetPriceModifier,
|
|
929
977
|
};
|
|
930
978
|
|
|
931
|
-
let ts: bigint;
|
|
932
|
-
|
|
933
979
|
try {
|
|
934
980
|
// @note This will make sure that we are passing the checks for our header ASSUMING that the data is also made available
|
|
935
981
|
// This means that we can avoid the simulation issues in later checks.
|
|
936
982
|
// By simulation issue, I mean the fact that the block.timestamp is equal to the last block, not the next, which
|
|
937
983
|
// make time consistency checks break.
|
|
938
984
|
// TODO(palla): Check whether we're validating twice, once here and once within addProposeTx, since we call simulateProposeTx in both places.
|
|
939
|
-
|
|
985
|
+
await this.validateCheckpointForSubmission(
|
|
940
986
|
checkpoint,
|
|
941
987
|
attestationsAndSigners,
|
|
942
988
|
attestationsAndSignersSignature,
|
|
@@ -952,7 +998,7 @@ export class SequencerPublisher {
|
|
|
952
998
|
}
|
|
953
999
|
|
|
954
1000
|
this.log.verbose(`Enqueuing checkpoint propose transaction`, { ...checkpoint.toCheckpointInfo(), ...opts });
|
|
955
|
-
await this.addProposeTx(checkpoint, proposeTxArgs, opts
|
|
1001
|
+
await this.addProposeTx(checkpoint, proposeTxArgs, opts);
|
|
956
1002
|
}
|
|
957
1003
|
|
|
958
1004
|
public enqueueInvalidateCheckpoint(
|
|
@@ -995,8 +1041,8 @@ export class SequencerPublisher {
|
|
|
995
1041
|
request: L1TxRequest,
|
|
996
1042
|
checkSuccess: (receipt: TransactionReceipt) => boolean | undefined,
|
|
997
1043
|
slotNumber: SlotNumber,
|
|
998
|
-
timestamp: bigint,
|
|
999
1044
|
) {
|
|
1045
|
+
const timestamp = this.getSimulationTimestamp(slotNumber);
|
|
1000
1046
|
const logData = { slotNumber, timestamp, gasLimit: undefined as bigint | undefined };
|
|
1001
1047
|
if (this.lastActions[action] && this.lastActions[action] === slotNumber) {
|
|
1002
1048
|
this.log.debug(`Skipping duplicate action ${action} for slot ${slotNumber}`);
|
|
@@ -1010,8 +1056,9 @@ export class SequencerPublisher {
|
|
|
1010
1056
|
|
|
1011
1057
|
let gasUsed: bigint;
|
|
1012
1058
|
const simulateAbi = mergeAbis([request.abi ?? [], ErrorsAbi]);
|
|
1059
|
+
|
|
1013
1060
|
try {
|
|
1014
|
-
({ gasUsed } = await this.l1TxUtils.simulate(request, { time: timestamp }, [], simulateAbi));
|
|
1061
|
+
({ gasUsed } = await this.l1TxUtils.simulate(request, { time: timestamp }, [], simulateAbi));
|
|
1015
1062
|
this.log.verbose(`Simulation for ${action} succeeded`, { ...logData, request, gasUsed });
|
|
1016
1063
|
} catch (err) {
|
|
1017
1064
|
const viemError = formatViemError(err, simulateAbi);
|
|
@@ -1067,7 +1114,6 @@ export class SequencerPublisher {
|
|
|
1067
1114
|
|
|
1068
1115
|
private async prepareProposeTx(
|
|
1069
1116
|
encodedData: L1ProcessArgs,
|
|
1070
|
-
timestamp: bigint,
|
|
1071
1117
|
options: { forcePendingCheckpointNumber?: CheckpointNumber },
|
|
1072
1118
|
) {
|
|
1073
1119
|
const kzg = Blob.getViemKzgInstance();
|
|
@@ -1113,8 +1159,7 @@ export class SequencerPublisher {
|
|
|
1113
1159
|
header: encodedData.header.toViem(),
|
|
1114
1160
|
archive: toHex(encodedData.archive),
|
|
1115
1161
|
oracleInput: {
|
|
1116
|
-
|
|
1117
|
-
feeAssetPriceModifier: 0n,
|
|
1162
|
+
feeAssetPriceModifier: encodedData.feeAssetPriceModifier,
|
|
1118
1163
|
},
|
|
1119
1164
|
},
|
|
1120
1165
|
encodedData.attestationsAndSigners.getPackedAttestations(),
|
|
@@ -1123,7 +1168,7 @@ export class SequencerPublisher {
|
|
|
1123
1168
|
blobInput,
|
|
1124
1169
|
] as const;
|
|
1125
1170
|
|
|
1126
|
-
const { rollupData, simulationResult } = await this.simulateProposeTx(args,
|
|
1171
|
+
const { rollupData, simulationResult } = await this.simulateProposeTx(args, options);
|
|
1127
1172
|
|
|
1128
1173
|
return { args, blobEvaluationGas, rollupData, simulationResult };
|
|
1129
1174
|
}
|
|
@@ -1131,7 +1176,6 @@ export class SequencerPublisher {
|
|
|
1131
1176
|
/**
|
|
1132
1177
|
* Simulates the propose tx with eth_simulateV1
|
|
1133
1178
|
* @param args - The propose tx args
|
|
1134
|
-
* @param timestamp - The timestamp to simulate proposal at
|
|
1135
1179
|
* @returns The simulation result
|
|
1136
1180
|
*/
|
|
1137
1181
|
private async simulateProposeTx(
|
|
@@ -1140,7 +1184,7 @@ export class SequencerPublisher {
|
|
|
1140
1184
|
readonly header: ViemHeader;
|
|
1141
1185
|
readonly archive: `0x${string}`;
|
|
1142
1186
|
readonly oracleInput: {
|
|
1143
|
-
readonly feeAssetPriceModifier:
|
|
1187
|
+
readonly feeAssetPriceModifier: bigint;
|
|
1144
1188
|
};
|
|
1145
1189
|
},
|
|
1146
1190
|
ViemCommitteeAttestations,
|
|
@@ -1148,7 +1192,6 @@ export class SequencerPublisher {
|
|
|
1148
1192
|
ViemSignature,
|
|
1149
1193
|
`0x${string}`,
|
|
1150
1194
|
],
|
|
1151
|
-
timestamp: bigint,
|
|
1152
1195
|
options: { forcePendingCheckpointNumber?: CheckpointNumber },
|
|
1153
1196
|
) {
|
|
1154
1197
|
const rollupData = encodeFunctionData({
|
|
@@ -1182,6 +1225,8 @@ export class SequencerPublisher {
|
|
|
1182
1225
|
});
|
|
1183
1226
|
}
|
|
1184
1227
|
|
|
1228
|
+
const simTs = this.getSimulationTimestamp(SlotNumber.fromBigInt(args[0].header.slotNumber));
|
|
1229
|
+
|
|
1185
1230
|
const simulationResult = await this.l1TxUtils
|
|
1186
1231
|
.simulate(
|
|
1187
1232
|
{
|
|
@@ -1191,8 +1236,7 @@ export class SequencerPublisher {
|
|
|
1191
1236
|
...(this.proposerAddressForSimulation && { from: this.proposerAddressForSimulation.toString() }),
|
|
1192
1237
|
},
|
|
1193
1238
|
{
|
|
1194
|
-
|
|
1195
|
-
time: timestamp + 1n,
|
|
1239
|
+
time: simTs,
|
|
1196
1240
|
// @note reth should have a 30m gas limit per block but throws errors that this tx is beyond limit so we increase here
|
|
1197
1241
|
gasLimit: MAX_L1_TX_LIMIT * 2n,
|
|
1198
1242
|
},
|
|
@@ -1214,7 +1258,7 @@ export class SequencerPublisher {
|
|
|
1214
1258
|
logs: [],
|
|
1215
1259
|
};
|
|
1216
1260
|
}
|
|
1217
|
-
this.log.error(`Failed to simulate propose tx`, viemError);
|
|
1261
|
+
this.log.error(`Failed to simulate propose tx`, viemError, { simulationTimestamp: simTs });
|
|
1218
1262
|
throw err;
|
|
1219
1263
|
});
|
|
1220
1264
|
|
|
@@ -1225,16 +1269,11 @@ export class SequencerPublisher {
|
|
|
1225
1269
|
checkpoint: Checkpoint,
|
|
1226
1270
|
encodedData: L1ProcessArgs,
|
|
1227
1271
|
opts: { txTimeoutAt?: Date; forcePendingCheckpointNumber?: CheckpointNumber } = {},
|
|
1228
|
-
timestamp: bigint,
|
|
1229
1272
|
): Promise<void> {
|
|
1230
1273
|
const slot = checkpoint.header.slotNumber;
|
|
1231
1274
|
const timer = new Timer();
|
|
1232
1275
|
const kzg = Blob.getViemKzgInstance();
|
|
1233
|
-
const { rollupData, simulationResult, blobEvaluationGas } = await this.prepareProposeTx(
|
|
1234
|
-
encodedData,
|
|
1235
|
-
timestamp,
|
|
1236
|
-
opts,
|
|
1237
|
-
);
|
|
1276
|
+
const { rollupData, simulationResult, blobEvaluationGas } = await this.prepareProposeTx(encodedData, opts);
|
|
1238
1277
|
const startBlock = await this.l1TxUtils.getBlockNumber();
|
|
1239
1278
|
const gasLimit = this.l1TxUtils.bumpGasLimit(
|
|
1240
1279
|
BigInt(Math.ceil((Number(simulationResult.gasUsed) * 64) / 63)) +
|
|
@@ -1310,4 +1349,17 @@ export class SequencerPublisher {
|
|
|
1310
1349
|
},
|
|
1311
1350
|
});
|
|
1312
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
|
+
}
|
|
1313
1365
|
}
|