@aztec/sequencer-client 0.0.1-commit.0c875d939 → 0.0.1-commit.0ec55a70b
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 +17 -7
- package/dest/client/sequencer-client.d.ts.map +1 -1
- package/dest/client/sequencer-client.js +63 -30
- package/dest/config.d.ts +27 -6
- package/dest/config.d.ts.map +1 -1
- package/dest/config.js +55 -30
- package/dest/global_variable_builder/fee_predictor.d.ts +37 -0
- package/dest/global_variable_builder/fee_predictor.d.ts.map +1 -0
- package/dest/global_variable_builder/fee_predictor.js +128 -0
- package/dest/global_variable_builder/fee_provider.d.ts +21 -0
- package/dest/global_variable_builder/fee_provider.d.ts.map +1 -0
- package/dest/global_variable_builder/fee_provider.js +58 -0
- package/dest/global_variable_builder/global_builder.d.ts +15 -16
- package/dest/global_variable_builder/global_builder.d.ts.map +1 -1
- package/dest/global_variable_builder/global_builder.js +16 -50
- package/dest/global_variable_builder/index.d.ts +4 -2
- package/dest/global_variable_builder/index.d.ts.map +1 -1
- package/dest/global_variable_builder/index.js +2 -0
- package/dest/publisher/config.d.ts +47 -17
- package/dest/publisher/config.d.ts.map +1 -1
- package/dest/publisher/config.js +121 -42
- package/dest/publisher/index.d.ts +2 -1
- package/dest/publisher/index.d.ts.map +1 -1
- package/dest/publisher/l1_tx_failed_store/factory.d.ts +11 -0
- package/dest/publisher/l1_tx_failed_store/factory.d.ts.map +1 -0
- package/dest/publisher/l1_tx_failed_store/factory.js +22 -0
- package/dest/publisher/l1_tx_failed_store/failed_tx_store.d.ts +58 -0
- package/dest/publisher/l1_tx_failed_store/failed_tx_store.d.ts.map +1 -0
- package/dest/publisher/l1_tx_failed_store/failed_tx_store.js +1 -0
- package/dest/publisher/l1_tx_failed_store/file_store_failed_tx_store.d.ts +15 -0
- package/dest/publisher/l1_tx_failed_store/file_store_failed_tx_store.d.ts.map +1 -0
- package/dest/publisher/l1_tx_failed_store/file_store_failed_tx_store.js +34 -0
- package/dest/publisher/l1_tx_failed_store/index.d.ts +4 -0
- package/dest/publisher/l1_tx_failed_store/index.d.ts.map +1 -0
- package/dest/publisher/l1_tx_failed_store/index.js +2 -0
- package/dest/publisher/sequencer-publisher-factory.d.ts +11 -5
- package/dest/publisher/sequencer-publisher-factory.d.ts.map +1 -1
- package/dest/publisher/sequencer-publisher-factory.js +27 -3
- package/dest/publisher/sequencer-publisher.d.ts +68 -48
- package/dest/publisher/sequencer-publisher.d.ts.map +1 -1
- package/dest/publisher/sequencer-publisher.js +421 -137
- package/dest/sequencer/chain_state_overrides.d.ts +25 -0
- package/dest/sequencer/chain_state_overrides.d.ts.map +1 -0
- package/dest/sequencer/chain_state_overrides.js +39 -0
- package/dest/sequencer/checkpoint_proposal_job.d.ts +46 -10
- package/dest/sequencer/checkpoint_proposal_job.d.ts.map +1 -1
- package/dest/sequencer/checkpoint_proposal_job.js +700 -220
- package/dest/sequencer/checkpoint_proposal_job_metrics.d.ts +34 -0
- package/dest/sequencer/checkpoint_proposal_job_metrics.d.ts.map +1 -0
- package/dest/sequencer/checkpoint_proposal_job_metrics.js +72 -0
- package/dest/sequencer/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/events.d.ts +7 -1
- package/dest/sequencer/events.d.ts.map +1 -1
- package/dest/sequencer/metrics.d.ts +20 -8
- package/dest/sequencer/metrics.d.ts.map +1 -1
- package/dest/sequencer/metrics.js +92 -21
- package/dest/sequencer/sequencer.d.ts +53 -19
- package/dest/sequencer/sequencer.d.ts.map +1 -1
- package/dest/sequencer/sequencer.js +188 -102
- package/dest/sequencer/timetable.d.ts +17 -3
- package/dest/sequencer/timetable.d.ts.map +1 -1
- package/dest/sequencer/timetable.js +51 -43
- 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 +39 -30
- package/dest/test/utils.d.ts +1 -1
- package/dest/test/utils.d.ts.map +1 -1
- package/dest/test/utils.js +7 -6
- package/package.json +27 -28
- package/src/client/sequencer-client.ts +81 -30
- package/src/config.ts +68 -35
- package/src/global_variable_builder/README.md +44 -0
- package/src/global_variable_builder/fee_predictor.ts +172 -0
- package/src/global_variable_builder/fee_provider.ts +75 -0
- package/src/global_variable_builder/global_builder.ts +27 -63
- package/src/global_variable_builder/index.ts +3 -1
- package/src/publisher/config.ts +157 -45
- package/src/publisher/index.ts +3 -0
- package/src/publisher/l1_tx_failed_store/factory.ts +32 -0
- package/src/publisher/l1_tx_failed_store/failed_tx_store.ts +57 -0
- package/src/publisher/l1_tx_failed_store/file_store_failed_tx_store.ts +46 -0
- package/src/publisher/l1_tx_failed_store/index.ts +3 -0
- package/src/publisher/sequencer-publisher-factory.ts +38 -9
- package/src/publisher/sequencer-publisher.ts +474 -197
- package/src/sequencer/README.md +82 -13
- package/src/sequencer/chain_state_overrides.ts +87 -0
- package/src/sequencer/checkpoint_proposal_job.ts +866 -241
- package/src/sequencer/checkpoint_proposal_job_metrics.ts +128 -0
- package/src/sequencer/checkpoint_voter.ts +1 -12
- package/src/sequencer/events.ts +6 -1
- package/src/sequencer/metrics.ts +108 -25
- package/src/sequencer/sequencer.ts +262 -116
- package/src/sequencer/timetable.ts +64 -52
- package/src/sequencer/types.ts +1 -1
- package/src/test/index.ts +2 -4
- package/src/test/mock_checkpoint_builder.ts +51 -48
- package/src/test/utils.ts +28 -10
|
@@ -3,52 +3,80 @@ import { Blob, getBlobsPerL1Block, getPrefixedEthBlobCommitments } from '@aztec/
|
|
|
3
3
|
import type { EpochCache } from '@aztec/epoch-cache';
|
|
4
4
|
import type { L1ContractsConfig } from '@aztec/ethereum/config';
|
|
5
5
|
import {
|
|
6
|
-
type EmpireSlashingProposerContract,
|
|
7
6
|
FeeAssetPriceOracle,
|
|
8
7
|
type GovernanceProposerContract,
|
|
9
8
|
type IEmpireBase,
|
|
10
9
|
MULTI_CALL_3_ADDRESS,
|
|
11
10
|
Multicall3,
|
|
12
11
|
RollupContract,
|
|
13
|
-
|
|
12
|
+
SimulationOverridesBuilder,
|
|
13
|
+
type SimulationOverridesPlan,
|
|
14
|
+
type SlashingProposerContract,
|
|
14
15
|
type ViemCommitteeAttestations,
|
|
15
16
|
type ViemHeader,
|
|
17
|
+
buildSimulationOverridesStateOverride,
|
|
16
18
|
} from '@aztec/ethereum/contracts';
|
|
17
19
|
import { type L1FeeAnalysisResult, L1FeeAnalyzer } from '@aztec/ethereum/l1-fee-analysis';
|
|
18
20
|
import {
|
|
19
21
|
type L1BlobInputs,
|
|
20
22
|
type L1TxConfig,
|
|
21
23
|
type L1TxRequest,
|
|
24
|
+
type L1TxUtils,
|
|
22
25
|
MAX_L1_TX_LIMIT,
|
|
23
26
|
type TransactionStats,
|
|
24
27
|
WEI_CONST,
|
|
25
28
|
} from '@aztec/ethereum/l1-tx-utils';
|
|
26
|
-
import type { L1TxUtilsWithBlobs } from '@aztec/ethereum/l1-tx-utils-with-blobs';
|
|
27
29
|
import { FormattedViemError, formatViemError, mergeAbis, tryExtractEvent } from '@aztec/ethereum/utils';
|
|
28
30
|
import { sumBigint } from '@aztec/foundation/bigint';
|
|
29
|
-
import { toHex as toPaddedHex } from '@aztec/foundation/bigint-buffer';
|
|
30
31
|
import { CheckpointNumber, SlotNumber } from '@aztec/foundation/branded-types';
|
|
32
|
+
import { trimmedBytesLength } from '@aztec/foundation/buffer';
|
|
31
33
|
import { pick } from '@aztec/foundation/collection';
|
|
32
34
|
import type { Fr } from '@aztec/foundation/curves/bn254';
|
|
35
|
+
import { TimeoutError } from '@aztec/foundation/error';
|
|
33
36
|
import { EthAddress } from '@aztec/foundation/eth-address';
|
|
34
37
|
import { Signature, type ViemSignature } from '@aztec/foundation/eth-signature';
|
|
35
38
|
import { type Logger, createLogger } from '@aztec/foundation/log';
|
|
39
|
+
import { makeBackoff, retry } from '@aztec/foundation/retry';
|
|
40
|
+
import { InterruptibleSleep } from '@aztec/foundation/sleep';
|
|
36
41
|
import { bufferToHex } from '@aztec/foundation/string';
|
|
37
|
-
import { DateProvider, Timer } from '@aztec/foundation/timer';
|
|
42
|
+
import { type DateProvider, Timer } from '@aztec/foundation/timer';
|
|
38
43
|
import { EmpireBaseAbi, ErrorsAbi, RollupAbi } from '@aztec/l1-artifacts';
|
|
39
44
|
import { type ProposerSlashAction, encodeSlashConsensusVotes } from '@aztec/slasher';
|
|
40
45
|
import { CommitteeAttestationsAndSigners, type ValidateCheckpointResult } from '@aztec/stdlib/block';
|
|
41
46
|
import type { Checkpoint } from '@aztec/stdlib/checkpoint';
|
|
42
|
-
import {
|
|
47
|
+
import { getLastL1SlotTimestampForL2Slot, getNextL1SlotTimestamp } from '@aztec/stdlib/epoch-helpers';
|
|
43
48
|
import type { CheckpointHeader } from '@aztec/stdlib/rollup';
|
|
44
49
|
import type { L1PublishCheckpointStats } from '@aztec/stdlib/stats';
|
|
45
50
|
import { type TelemetryClient, type Tracer, getTelemetryClient, trackSpan } from '@aztec/telemetry-client';
|
|
46
51
|
|
|
47
|
-
import {
|
|
48
|
-
|
|
49
|
-
|
|
52
|
+
import {
|
|
53
|
+
type Hex,
|
|
54
|
+
type TransactionReceipt,
|
|
55
|
+
type TypedDataDefinition,
|
|
56
|
+
encodeFunctionData,
|
|
57
|
+
keccak256,
|
|
58
|
+
multicall3Abi,
|
|
59
|
+
toHex,
|
|
60
|
+
} from 'viem';
|
|
61
|
+
|
|
62
|
+
import type { SequencerPublisherConfig } from './config.js';
|
|
63
|
+
import { type FailedL1Tx, type L1TxFailedStore, createL1TxFailedStore } from './l1_tx_failed_store/index.js';
|
|
50
64
|
import { SequencerPublisherMetrics } from './sequencer-publisher-metrics.js';
|
|
51
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
|
+
|
|
52
80
|
/** Arguments to the process method of the rollup contract */
|
|
53
81
|
type L1ProcessArgs = {
|
|
54
82
|
/** The L2 block header. */
|
|
@@ -70,16 +98,13 @@ export const Actions = [
|
|
|
70
98
|
'invalidate-by-insufficient-attestations',
|
|
71
99
|
'propose',
|
|
72
100
|
'governance-signal',
|
|
73
|
-
'empire-slashing-signal',
|
|
74
|
-
'create-empire-payload',
|
|
75
|
-
'execute-empire-payload',
|
|
76
101
|
'vote-offenses',
|
|
77
102
|
'execute-slash',
|
|
78
103
|
] as const;
|
|
79
104
|
|
|
80
105
|
export type Action = (typeof Actions)[number];
|
|
81
106
|
|
|
82
|
-
type GovernanceSignalAction = Extract<Action, 'governance-signal'
|
|
107
|
+
type GovernanceSignalAction = Extract<Action, 'governance-signal'>;
|
|
83
108
|
|
|
84
109
|
// Sorting for actions such that invalidations go before proposals, and proposals go before votes
|
|
85
110
|
export const compareActions = (a: Action, b: Action) => Actions.indexOf(a) - Actions.indexOf(b);
|
|
@@ -90,6 +115,13 @@ export type InvalidateCheckpointRequest = {
|
|
|
90
115
|
gasUsed: bigint;
|
|
91
116
|
checkpointNumber: CheckpointNumber;
|
|
92
117
|
forcePendingCheckpointNumber: CheckpointNumber;
|
|
118
|
+
/** Archive at the rollback target checkpoint (checkpoint N-1). */
|
|
119
|
+
lastArchive: Fr;
|
|
120
|
+
};
|
|
121
|
+
|
|
122
|
+
type EnqueueProposeCheckpointOpts = {
|
|
123
|
+
txTimeoutAt?: Date;
|
|
124
|
+
simulationOverridesPlan?: SimulationOverridesPlan;
|
|
93
125
|
};
|
|
94
126
|
|
|
95
127
|
interface RequestWithExpiry {
|
|
@@ -98,6 +130,8 @@ interface RequestWithExpiry {
|
|
|
98
130
|
lastValidL2Slot: SlotNumber;
|
|
99
131
|
gasConfig?: Pick<L1TxConfig, 'txTimeoutAt' | 'gasLimit'>;
|
|
100
132
|
blobConfig?: L1BlobInputs;
|
|
133
|
+
/** Optional pre-send validation. If it rejects, the request is discarded. */
|
|
134
|
+
preCheck?: () => Promise<void>;
|
|
101
135
|
checkSuccess: (
|
|
102
136
|
request: L1TxRequest,
|
|
103
137
|
result?: { receipt: TransactionReceipt; stats?: TransactionStats; errorMsg?: string },
|
|
@@ -108,6 +142,7 @@ export class SequencerPublisher {
|
|
|
108
142
|
private interrupted = false;
|
|
109
143
|
private metrics: SequencerPublisherMetrics;
|
|
110
144
|
public epochCache: EpochCache;
|
|
145
|
+
private failedTxStore?: Promise<L1TxFailedStore | undefined>;
|
|
111
146
|
|
|
112
147
|
protected governanceLog = createLogger('sequencer:publisher:governance');
|
|
113
148
|
protected slashingLog = createLogger('sequencer:publisher:slashing');
|
|
@@ -115,65 +150,80 @@ export class SequencerPublisher {
|
|
|
115
150
|
protected lastActions: Partial<Record<Action, SlotNumber>> = {};
|
|
116
151
|
|
|
117
152
|
private isPayloadEmptyCache: Map<string, boolean> = new Map<string, boolean>();
|
|
153
|
+
private payloadProposedCache: Set<string> = new Set<string>();
|
|
118
154
|
|
|
119
155
|
protected log: Logger;
|
|
120
156
|
protected ethereumSlotDuration: bigint;
|
|
157
|
+
protected aztecSlotDuration: bigint;
|
|
158
|
+
|
|
159
|
+
/** Date provider for wall-clock time. */
|
|
160
|
+
private readonly dateProvider: DateProvider;
|
|
121
161
|
|
|
122
162
|
private blobClient: BlobClientInterface;
|
|
123
163
|
|
|
124
164
|
/** Address to use for simulations in fisherman mode (actual proposer's address) */
|
|
125
165
|
private proposerAddressForSimulation?: EthAddress;
|
|
126
166
|
|
|
167
|
+
/** Optional callback to obtain a replacement publisher when the current one fails to send. */
|
|
168
|
+
private getNextPublisher?: (excludeAddresses: EthAddress[]) => Promise<L1TxUtils | undefined>;
|
|
169
|
+
|
|
127
170
|
/** L1 fee analyzer for fisherman mode */
|
|
128
171
|
private l1FeeAnalyzer?: L1FeeAnalyzer;
|
|
129
172
|
|
|
130
173
|
/** Fee asset price oracle for computing price modifiers from Uniswap V4 */
|
|
131
174
|
private feeAssetPriceOracle: FeeAssetPriceOracle;
|
|
132
175
|
|
|
176
|
+
/** Interruptible sleep used by sendRequestsAt to wait until a target timestamp. */
|
|
177
|
+
private readonly interruptibleSleep = new InterruptibleSleep();
|
|
178
|
+
|
|
133
179
|
// A CALL to a cold address is 2700 gas
|
|
134
180
|
public static MULTICALL_OVERHEAD_GAS_GUESS = 5000n;
|
|
135
181
|
|
|
136
182
|
// Gas report for VotingWithSigTest shows a max gas of 100k, but we've seen it cost 700k+ in testnet
|
|
137
183
|
public static VOTE_GAS_GUESS: bigint = 800_000n;
|
|
138
184
|
|
|
139
|
-
public l1TxUtils:
|
|
185
|
+
public l1TxUtils: L1TxUtils;
|
|
140
186
|
public rollupContract: RollupContract;
|
|
141
187
|
public govProposerContract: GovernanceProposerContract;
|
|
142
|
-
public slashingProposerContract:
|
|
143
|
-
public slashFactoryContract: SlashFactoryContract;
|
|
188
|
+
public slashingProposerContract: SlashingProposerContract | undefined;
|
|
144
189
|
|
|
145
190
|
public readonly tracer: Tracer;
|
|
146
191
|
|
|
147
192
|
protected requests: RequestWithExpiry[] = [];
|
|
148
193
|
|
|
149
194
|
constructor(
|
|
150
|
-
private config:
|
|
195
|
+
private config: Pick<SequencerPublisherConfig, 'fishermanMode' | 'l1TxFailedStore'> &
|
|
196
|
+
Pick<L1ContractsConfig, 'ethereumSlotDuration' | 'aztecSlotDuration'> & { l1ChainId: number },
|
|
151
197
|
deps: {
|
|
152
198
|
telemetry?: TelemetryClient;
|
|
153
199
|
blobClient: BlobClientInterface;
|
|
154
|
-
l1TxUtils:
|
|
200
|
+
l1TxUtils: L1TxUtils;
|
|
155
201
|
rollupContract: RollupContract;
|
|
156
|
-
slashingProposerContract:
|
|
202
|
+
slashingProposerContract: SlashingProposerContract | undefined;
|
|
157
203
|
governanceProposerContract: GovernanceProposerContract;
|
|
158
|
-
slashFactoryContract: SlashFactoryContract;
|
|
159
204
|
epochCache: EpochCache;
|
|
160
205
|
dateProvider: DateProvider;
|
|
161
206
|
metrics: SequencerPublisherMetrics;
|
|
162
207
|
lastActions: Partial<Record<Action, SlotNumber>>;
|
|
163
208
|
log?: Logger;
|
|
209
|
+
getNextPublisher?: (excludeAddresses: EthAddress[]) => Promise<L1TxUtils | undefined>;
|
|
164
210
|
},
|
|
165
211
|
) {
|
|
166
212
|
this.log = deps.log ?? createLogger('sequencer:publisher');
|
|
167
213
|
this.ethereumSlotDuration = BigInt(config.ethereumSlotDuration);
|
|
214
|
+
this.aztecSlotDuration = BigInt(config.aztecSlotDuration);
|
|
215
|
+
this.dateProvider = deps.dateProvider;
|
|
168
216
|
this.epochCache = deps.epochCache;
|
|
169
217
|
this.lastActions = deps.lastActions;
|
|
170
218
|
|
|
171
219
|
this.blobClient = deps.blobClient;
|
|
220
|
+
this.dateProvider = deps.dateProvider;
|
|
172
221
|
|
|
173
222
|
const telemetry = deps.telemetry ?? getTelemetryClient();
|
|
174
223
|
this.metrics = deps.metrics ?? new SequencerPublisherMetrics(telemetry, 'SequencerPublisher');
|
|
175
224
|
this.tracer = telemetry.getTracer('SequencerPublisher');
|
|
176
225
|
this.l1TxUtils = deps.l1TxUtils;
|
|
226
|
+
this.getNextPublisher = deps.getNextPublisher;
|
|
177
227
|
|
|
178
228
|
this.rollupContract = deps.rollupContract;
|
|
179
229
|
|
|
@@ -185,8 +235,6 @@ export class SequencerPublisher {
|
|
|
185
235
|
const newSlashingProposer = await this.rollupContract.getSlashingProposer();
|
|
186
236
|
this.slashingProposerContract = newSlashingProposer;
|
|
187
237
|
});
|
|
188
|
-
this.slashFactoryContract = deps.slashFactoryContract;
|
|
189
|
-
|
|
190
238
|
// Initialize L1 fee analyzer for fisherman mode
|
|
191
239
|
if (config.fishermanMode) {
|
|
192
240
|
this.l1FeeAnalyzer = new L1FeeAnalyzer(
|
|
@@ -202,6 +250,31 @@ export class SequencerPublisher {
|
|
|
202
250
|
this.rollupContract,
|
|
203
251
|
createLogger('sequencer:publisher:price-oracle'),
|
|
204
252
|
);
|
|
253
|
+
|
|
254
|
+
// Initialize failed L1 tx store (optional, for test networks)
|
|
255
|
+
this.failedTxStore = createL1TxFailedStore(config.l1TxFailedStore, this.log);
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
/**
|
|
259
|
+
* Backs up a failed L1 transaction to the configured store for debugging.
|
|
260
|
+
* Does nothing if no store is configured.
|
|
261
|
+
*/
|
|
262
|
+
private backupFailedTx(failedTx: Omit<FailedL1Tx, 'timestamp'>): void {
|
|
263
|
+
if (!this.failedTxStore) {
|
|
264
|
+
return;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
const tx: FailedL1Tx = {
|
|
268
|
+
...failedTx,
|
|
269
|
+
timestamp: Date.now(),
|
|
270
|
+
};
|
|
271
|
+
|
|
272
|
+
// Fire and forget - don't block on backup
|
|
273
|
+
void this.failedTxStore
|
|
274
|
+
.then(store => store?.saveFailedTx(tx))
|
|
275
|
+
.catch(err => {
|
|
276
|
+
this.log.warn(`Failed to backup failed L1 tx to store`, err);
|
|
277
|
+
});
|
|
205
278
|
}
|
|
206
279
|
|
|
207
280
|
public getRollupContract(): RollupContract {
|
|
@@ -240,7 +313,7 @@ export class SequencerPublisher {
|
|
|
240
313
|
}
|
|
241
314
|
|
|
242
315
|
public getCurrentL2Slot(): SlotNumber {
|
|
243
|
-
return this.epochCache.
|
|
316
|
+
return this.epochCache.getSlotNow();
|
|
244
317
|
}
|
|
245
318
|
|
|
246
319
|
/**
|
|
@@ -318,9 +391,10 @@ export class SequencerPublisher {
|
|
|
318
391
|
* - undefined if no valid requests are found OR the tx failed to send.
|
|
319
392
|
*/
|
|
320
393
|
@trackSpan('SequencerPublisher.sendRequests')
|
|
321
|
-
public async sendRequests() {
|
|
394
|
+
public async sendRequests(): Promise<SendRequestsResult | undefined> {
|
|
322
395
|
const requestsToProcess = [...this.requests];
|
|
323
396
|
this.requests = [];
|
|
397
|
+
|
|
324
398
|
if (this.interrupted || requestsToProcess.length === 0) {
|
|
325
399
|
return undefined;
|
|
326
400
|
}
|
|
@@ -353,8 +427,8 @@ export class SequencerPublisher {
|
|
|
353
427
|
// @note - we can only have one blob config per bundle
|
|
354
428
|
// find requests with gas and blob configs
|
|
355
429
|
// See https://github.com/AztecProtocol/aztec-packages/issues/11513
|
|
356
|
-
const gasConfigs =
|
|
357
|
-
const blobConfigs =
|
|
430
|
+
const gasConfigs = validRequests.filter(request => request.gasConfig).map(request => request.gasConfig);
|
|
431
|
+
const blobConfigs = validRequests.filter(request => request.blobConfig).map(request => request.blobConfig);
|
|
358
432
|
|
|
359
433
|
if (blobConfigs.length > 1) {
|
|
360
434
|
throw new Error('Multiple blob configs found');
|
|
@@ -383,19 +457,36 @@ export class SequencerPublisher {
|
|
|
383
457
|
validRequests.sort((a, b) => compareActions(a.action, b.action));
|
|
384
458
|
|
|
385
459
|
try {
|
|
460
|
+
// Capture context for failed tx backup before sending
|
|
461
|
+
const l1BlockNumber = await this.l1TxUtils.getBlockNumber();
|
|
462
|
+
const multicallData = encodeFunctionData({
|
|
463
|
+
abi: multicall3Abi,
|
|
464
|
+
functionName: 'aggregate3',
|
|
465
|
+
args: [
|
|
466
|
+
validRequests.map(r => ({
|
|
467
|
+
target: r.request.to!,
|
|
468
|
+
callData: r.request.data!,
|
|
469
|
+
allowFailure: true,
|
|
470
|
+
})),
|
|
471
|
+
],
|
|
472
|
+
});
|
|
473
|
+
const blobDataHex = blobConfig?.blobs?.map(b => toHex(b)) as Hex[] | undefined;
|
|
474
|
+
|
|
475
|
+
const txContext = { multicallData, blobData: blobDataHex, l1BlockNumber };
|
|
476
|
+
|
|
386
477
|
this.log.debug('Forwarding transactions', {
|
|
387
478
|
validRequests: validRequests.map(request => request.action),
|
|
388
479
|
txConfig,
|
|
389
480
|
});
|
|
390
|
-
const result = await
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
481
|
+
const result = await this.forwardWithPublisherRotation(validRequests, txConfig, blobConfig);
|
|
482
|
+
if (result === undefined) {
|
|
483
|
+
return undefined;
|
|
484
|
+
}
|
|
485
|
+
const { successfulActions = [], failedActions = [] } = this.callbackBundledTransactions(
|
|
486
|
+
validRequests,
|
|
487
|
+
result,
|
|
488
|
+
txContext,
|
|
397
489
|
);
|
|
398
|
-
const { successfulActions = [], failedActions = [] } = this.callbackBundledTransactions(validRequests, result);
|
|
399
490
|
return { result, expiredActions, sentActions: validActions, successfulActions, failedActions };
|
|
400
491
|
} catch (err) {
|
|
401
492
|
const viemError = formatViemError(err);
|
|
@@ -413,16 +504,127 @@ export class SequencerPublisher {
|
|
|
413
504
|
}
|
|
414
505
|
}
|
|
415
506
|
|
|
507
|
+
/**
|
|
508
|
+
* Forwards transactions via Multicall3, rotating to the next available publisher if a send
|
|
509
|
+
* failure occurs (i.e. the tx never reached the chain).
|
|
510
|
+
* On-chain reverts and simulation errors are returned as-is without rotation.
|
|
511
|
+
*/
|
|
512
|
+
private async forwardWithPublisherRotation(
|
|
513
|
+
validRequests: RequestWithExpiry[],
|
|
514
|
+
txConfig: RequestWithExpiry['gasConfig'],
|
|
515
|
+
blobConfig: L1BlobInputs | undefined,
|
|
516
|
+
) {
|
|
517
|
+
const triedAddresses: EthAddress[] = [];
|
|
518
|
+
let currentPublisher = this.l1TxUtils;
|
|
519
|
+
|
|
520
|
+
while (true) {
|
|
521
|
+
triedAddresses.push(currentPublisher.getSenderAddress());
|
|
522
|
+
try {
|
|
523
|
+
const result = await Multicall3.forward(
|
|
524
|
+
validRequests.map(r => r.request),
|
|
525
|
+
currentPublisher,
|
|
526
|
+
txConfig,
|
|
527
|
+
blobConfig,
|
|
528
|
+
this.rollupContract.address,
|
|
529
|
+
this.log,
|
|
530
|
+
);
|
|
531
|
+
this.l1TxUtils = currentPublisher;
|
|
532
|
+
return result;
|
|
533
|
+
} catch (err) {
|
|
534
|
+
if (err instanceof TimeoutError) {
|
|
535
|
+
throw err;
|
|
536
|
+
}
|
|
537
|
+
const viemError = formatViemError(err);
|
|
538
|
+
if (!this.getNextPublisher) {
|
|
539
|
+
this.log.error('Failed to publish bundled transactions', viemError);
|
|
540
|
+
return undefined;
|
|
541
|
+
}
|
|
542
|
+
this.log.warn(
|
|
543
|
+
`Publisher ${currentPublisher.getSenderAddress()} failed to send, rotating to next publisher`,
|
|
544
|
+
viemError,
|
|
545
|
+
);
|
|
546
|
+
const nextPublisher = await this.getNextPublisher([...triedAddresses]);
|
|
547
|
+
if (!nextPublisher) {
|
|
548
|
+
this.log.error('All available publishers exhausted, failed to publish bundled transactions');
|
|
549
|
+
return undefined;
|
|
550
|
+
}
|
|
551
|
+
currentPublisher = nextPublisher;
|
|
552
|
+
}
|
|
553
|
+
}
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
/*
|
|
557
|
+
* Schedules sending all enqueued requests at (or after) the given timestamp.
|
|
558
|
+
* Uses InterruptibleSleep so it can be cancelled via interrupt().
|
|
559
|
+
* Returns the promise for the L1 response (caller should NOT await this in the work loop).
|
|
560
|
+
*/
|
|
561
|
+
public async sendRequestsAt(submitAfter: Date): Promise<SendRequestsResult | undefined> {
|
|
562
|
+
const ms = submitAfter.getTime() - this.dateProvider.now();
|
|
563
|
+
if (ms > 0) {
|
|
564
|
+
this.log.debug(`Sleeping ${ms}ms before sending requests`, { submitAfter });
|
|
565
|
+
await this.interruptibleSleep.sleep(ms);
|
|
566
|
+
}
|
|
567
|
+
if (this.interrupted) {
|
|
568
|
+
return undefined;
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
// Re-validate enqueued requests after the sleep (state may have changed, e.g. prune or L1 reorg)
|
|
572
|
+
const validRequests: RequestWithExpiry[] = [];
|
|
573
|
+
for (const request of this.requests) {
|
|
574
|
+
if (!request.preCheck) {
|
|
575
|
+
validRequests.push(request);
|
|
576
|
+
continue;
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
try {
|
|
580
|
+
await request.preCheck();
|
|
581
|
+
validRequests.push(request);
|
|
582
|
+
} catch (err) {
|
|
583
|
+
this.log.warn(`Pre-send validation failed for ${request.action}, discarding request`, err);
|
|
584
|
+
}
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
this.requests = validRequests;
|
|
588
|
+
if (this.requests.length === 0) {
|
|
589
|
+
return undefined;
|
|
590
|
+
}
|
|
591
|
+
|
|
592
|
+
return this.sendRequests();
|
|
593
|
+
}
|
|
594
|
+
|
|
416
595
|
private callbackBundledTransactions(
|
|
417
596
|
requests: RequestWithExpiry[],
|
|
418
|
-
result
|
|
597
|
+
result: { receipt: TransactionReceipt; errorMsg?: string } | FormattedViemError | undefined,
|
|
598
|
+
txContext: { multicallData: Hex; blobData?: Hex[]; l1BlockNumber: bigint },
|
|
419
599
|
) {
|
|
420
600
|
const actionsListStr = requests.map(r => r.action).join(', ');
|
|
421
601
|
if (result instanceof FormattedViemError) {
|
|
422
602
|
this.log.error(`Failed to publish bundled transactions (${actionsListStr})`, result);
|
|
603
|
+
this.backupFailedTx({
|
|
604
|
+
id: keccak256(txContext.multicallData),
|
|
605
|
+
failureType: 'send-error',
|
|
606
|
+
request: { to: MULTI_CALL_3_ADDRESS, data: txContext.multicallData },
|
|
607
|
+
blobData: txContext.blobData,
|
|
608
|
+
l1BlockNumber: txContext.l1BlockNumber.toString(),
|
|
609
|
+
error: { message: result.message, name: result.name },
|
|
610
|
+
context: {
|
|
611
|
+
actions: requests.map(r => r.action),
|
|
612
|
+
requests: requests.map(r => ({ action: r.action, to: r.request.to! as Hex, data: r.request.data! })),
|
|
613
|
+
sender: this.getSenderAddress().toString(),
|
|
614
|
+
},
|
|
615
|
+
});
|
|
423
616
|
return { failedActions: requests.map(r => r.action) };
|
|
424
617
|
} else {
|
|
425
|
-
this.log.verbose(`Published bundled transactions (${actionsListStr})`, {
|
|
618
|
+
this.log.verbose(`Published bundled transactions (${actionsListStr})`, {
|
|
619
|
+
result,
|
|
620
|
+
requests: requests.map(r => ({
|
|
621
|
+
...r,
|
|
622
|
+
// Avoid logging large blob data
|
|
623
|
+
blobConfig: r.blobConfig
|
|
624
|
+
? { ...r.blobConfig, blobs: r.blobConfig.blobs.map(b => ({ size: trimmedBytesLength(b) })) }
|
|
625
|
+
: undefined,
|
|
626
|
+
})),
|
|
627
|
+
});
|
|
426
628
|
const successfulActions: Action[] = [];
|
|
427
629
|
const failedActions: Action[] = [];
|
|
428
630
|
for (const request of requests) {
|
|
@@ -432,27 +634,54 @@ export class SequencerPublisher {
|
|
|
432
634
|
failedActions.push(request.action);
|
|
433
635
|
}
|
|
434
636
|
}
|
|
637
|
+
// Single backup for the whole reverted tx
|
|
638
|
+
if (failedActions.length > 0 && result?.receipt?.status === 'reverted') {
|
|
639
|
+
this.backupFailedTx({
|
|
640
|
+
id: result.receipt.transactionHash,
|
|
641
|
+
failureType: 'revert',
|
|
642
|
+
request: { to: MULTI_CALL_3_ADDRESS, data: txContext.multicallData },
|
|
643
|
+
blobData: txContext.blobData,
|
|
644
|
+
l1BlockNumber: result.receipt.blockNumber.toString(),
|
|
645
|
+
receipt: {
|
|
646
|
+
transactionHash: result.receipt.transactionHash,
|
|
647
|
+
blockNumber: result.receipt.blockNumber.toString(),
|
|
648
|
+
gasUsed: (result.receipt.gasUsed ?? 0n).toString(),
|
|
649
|
+
status: 'reverted',
|
|
650
|
+
},
|
|
651
|
+
error: { message: result.errorMsg ?? 'Transaction reverted' },
|
|
652
|
+
context: {
|
|
653
|
+
actions: failedActions,
|
|
654
|
+
requests: requests
|
|
655
|
+
.filter(r => failedActions.includes(r.action))
|
|
656
|
+
.map(r => ({ action: r.action, to: r.request.to! as Hex, data: r.request.data! })),
|
|
657
|
+
sender: this.getSenderAddress().toString(),
|
|
658
|
+
},
|
|
659
|
+
});
|
|
660
|
+
}
|
|
435
661
|
return { successfulActions, failedActions };
|
|
436
662
|
}
|
|
437
663
|
}
|
|
438
664
|
|
|
439
665
|
/**
|
|
440
|
-
* @notice Will call `
|
|
666
|
+
* @notice Will call `canProposeAt` to make sure that it is possible to propose
|
|
441
667
|
* @param tipArchive - The archive to check
|
|
442
668
|
* @returns The slot and block number if it is possible to propose, undefined otherwise
|
|
443
669
|
*/
|
|
444
|
-
public
|
|
445
|
-
tipArchive: Fr,
|
|
446
|
-
msgSender: EthAddress,
|
|
447
|
-
opts: { forcePendingCheckpointNumber?: CheckpointNumber } = {},
|
|
448
|
-
) {
|
|
670
|
+
public async canProposeAt(tipArchive: Fr, msgSender: EthAddress, simulationOverridesPlan?: SimulationOverridesPlan) {
|
|
449
671
|
// TODO: #14291 - should loop through multiple keys to check if any of them can propose
|
|
450
672
|
const ignoredErrors = ['SlotAlreadyInChain', 'InvalidProposer', 'InvalidArchive'];
|
|
451
673
|
|
|
674
|
+
const pipelined = this.epochCache.isProposerPipeliningEnabled();
|
|
675
|
+
const slotOffset = pipelined ? this.aztecSlotDuration : 0n;
|
|
676
|
+
const nextL1SlotTs = this.getNextL1SlotTimestamp() + slotOffset;
|
|
677
|
+
|
|
452
678
|
return this.rollupContract
|
|
453
|
-
.
|
|
454
|
-
|
|
455
|
-
|
|
679
|
+
.canProposeAt(
|
|
680
|
+
tipArchive.toBuffer(),
|
|
681
|
+
msgSender.toString(),
|
|
682
|
+
nextL1SlotTs,
|
|
683
|
+
await buildSimulationOverridesStateOverride(this.rollupContract, simulationOverridesPlan),
|
|
684
|
+
)
|
|
456
685
|
.catch(err => {
|
|
457
686
|
if (err instanceof FormattedViemError && ignoredErrors.find(e => err.message.includes(e))) {
|
|
458
687
|
this.log.warn(`Failed canProposeAtTime check with ${ignoredErrors.find(e => err.message.includes(e))}`, {
|
|
@@ -464,6 +693,7 @@ export class SequencerPublisher {
|
|
|
464
693
|
return undefined;
|
|
465
694
|
});
|
|
466
695
|
}
|
|
696
|
+
|
|
467
697
|
/**
|
|
468
698
|
* @notice Will simulate `validateHeader` to make sure that the block header is valid
|
|
469
699
|
* @dev This is a convenience function that can be used by the sequencer to validate a "partial" header.
|
|
@@ -473,13 +703,13 @@ export class SequencerPublisher {
|
|
|
473
703
|
@trackSpan('SequencerPublisher.validateBlockHeader')
|
|
474
704
|
public async validateBlockHeader(
|
|
475
705
|
header: CheckpointHeader,
|
|
476
|
-
|
|
706
|
+
simulationOverridesPlan?: SimulationOverridesPlan,
|
|
477
707
|
): Promise<void> {
|
|
478
708
|
const flags = { ignoreDA: true, ignoreSignatures: true };
|
|
479
709
|
|
|
480
710
|
const args = [
|
|
481
711
|
header.toViem(),
|
|
482
|
-
CommitteeAttestationsAndSigners.
|
|
712
|
+
CommitteeAttestationsAndSigners.packAttestations([]),
|
|
483
713
|
[], // no signers
|
|
484
714
|
Signature.empty().toViemSignature(),
|
|
485
715
|
`0x${'0'.repeat(64)}`, // 32 empty bytes
|
|
@@ -487,10 +717,8 @@ export class SequencerPublisher {
|
|
|
487
717
|
flags,
|
|
488
718
|
] as const;
|
|
489
719
|
|
|
490
|
-
const ts =
|
|
491
|
-
const stateOverrides = await this.rollupContract
|
|
492
|
-
opts?.forcePendingCheckpointNumber,
|
|
493
|
-
);
|
|
720
|
+
const ts = this.getSimulationTimestamp(header.slotNumber);
|
|
721
|
+
const stateOverrides = await buildSimulationOverridesStateOverride(this.rollupContract, simulationOverridesPlan);
|
|
494
722
|
let balance = 0n;
|
|
495
723
|
if (this.config.fishermanMode) {
|
|
496
724
|
// In fisherman mode, we can't know where the proposer is publishing from
|
|
@@ -510,7 +738,7 @@ export class SequencerPublisher {
|
|
|
510
738
|
data: encodeFunctionData({ abi: RollupAbi, functionName: 'validateHeaderWithAttestations', args }),
|
|
511
739
|
from: MULTI_CALL_3_ADDRESS,
|
|
512
740
|
},
|
|
513
|
-
{ time: ts
|
|
741
|
+
{ time: ts },
|
|
514
742
|
stateOverrides,
|
|
515
743
|
);
|
|
516
744
|
this.log.debug(`Simulated validateHeader`);
|
|
@@ -543,6 +771,8 @@ export class SequencerPublisher {
|
|
|
543
771
|
const request = this.buildInvalidateCheckpointRequest(validationResult);
|
|
544
772
|
this.log.debug(`Simulating invalidate checkpoint ${checkpointNumber}`, { ...logData, request });
|
|
545
773
|
|
|
774
|
+
const l1BlockNumber = await this.l1TxUtils.getBlockNumber();
|
|
775
|
+
|
|
546
776
|
try {
|
|
547
777
|
const { gasUsed } = await this.l1TxUtils.simulate(
|
|
548
778
|
request,
|
|
@@ -561,6 +791,7 @@ export class SequencerPublisher {
|
|
|
561
791
|
gasUsed,
|
|
562
792
|
checkpointNumber,
|
|
563
793
|
forcePendingCheckpointNumber: CheckpointNumber(checkpointNumber - 1),
|
|
794
|
+
lastArchive: validationResult.checkpoint.lastArchive,
|
|
564
795
|
reason,
|
|
565
796
|
};
|
|
566
797
|
} catch (err) {
|
|
@@ -573,8 +804,8 @@ export class SequencerPublisher {
|
|
|
573
804
|
`Simulation for invalidate checkpoint ${checkpointNumber} failed due to checkpoint not being in pending chain`,
|
|
574
805
|
{ ...logData, request, error: viemError.message },
|
|
575
806
|
);
|
|
576
|
-
const
|
|
577
|
-
if (
|
|
807
|
+
const latestProposedCheckpointNumber = await this.rollupContract.getCheckpointNumber();
|
|
808
|
+
if (latestProposedCheckpointNumber < checkpointNumber) {
|
|
578
809
|
this.log.verbose(`Checkpoint ${checkpointNumber} has already been invalidated`, { ...logData });
|
|
579
810
|
return undefined;
|
|
580
811
|
} else {
|
|
@@ -594,6 +825,18 @@ export class SequencerPublisher {
|
|
|
594
825
|
|
|
595
826
|
// Otherwise, throw. We cannot build the next checkpoint if we cannot invalidate the previous one.
|
|
596
827
|
this.log.error(`Simulation for invalidate checkpoint ${checkpointNumber} failed`, viemError, logData);
|
|
828
|
+
this.backupFailedTx({
|
|
829
|
+
id: keccak256(request.data!),
|
|
830
|
+
failureType: 'simulation',
|
|
831
|
+
request: { to: request.to!, data: request.data!, value: request.value?.toString() },
|
|
832
|
+
l1BlockNumber: l1BlockNumber.toString(),
|
|
833
|
+
error: { message: viemError.message, name: viemError.name },
|
|
834
|
+
context: {
|
|
835
|
+
actions: [`invalidate-${reason}`],
|
|
836
|
+
checkpointNumber,
|
|
837
|
+
sender: this.getSenderAddress().toString(),
|
|
838
|
+
},
|
|
839
|
+
});
|
|
597
840
|
throw new Error(`Failed to simulate invalidate checkpoint ${checkpointNumber}`, { cause: viemError });
|
|
598
841
|
}
|
|
599
842
|
}
|
|
@@ -607,9 +850,7 @@ export class SequencerPublisher {
|
|
|
607
850
|
const logData = { ...checkpoint, reason };
|
|
608
851
|
this.log.debug(`Building invalidate checkpoint ${checkpoint.checkpointNumber} request`, logData);
|
|
609
852
|
|
|
610
|
-
const attestationsAndSigners =
|
|
611
|
-
validationResult.attestations,
|
|
612
|
-
).getPackedAttestations();
|
|
853
|
+
const attestationsAndSigners = CommitteeAttestationsAndSigners.packAttestations(validationResult.attestations);
|
|
613
854
|
|
|
614
855
|
if (reason === 'invalid-attestation') {
|
|
615
856
|
return this.rollupContract.buildInvalidateBadAttestationRequest(
|
|
@@ -636,27 +877,10 @@ export class SequencerPublisher {
|
|
|
636
877
|
checkpoint: Checkpoint,
|
|
637
878
|
attestationsAndSigners: CommitteeAttestationsAndSigners,
|
|
638
879
|
attestationsAndSignersSignature: Signature,
|
|
639
|
-
|
|
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
|
-
|
|
880
|
+
simulationOverridesPlan?: SimulationOverridesPlan,
|
|
881
|
+
): Promise<void> {
|
|
658
882
|
const blobFields = checkpoint.toBlobFields();
|
|
659
|
-
const blobs = getBlobsPerL1Block(blobFields);
|
|
883
|
+
const blobs = await getBlobsPerL1Block(blobFields);
|
|
660
884
|
const blobInput = getPrefixedEthBlobCommitments(blobs);
|
|
661
885
|
|
|
662
886
|
const args = [
|
|
@@ -673,13 +897,11 @@ export class SequencerPublisher {
|
|
|
673
897
|
blobInput,
|
|
674
898
|
] as const;
|
|
675
899
|
|
|
676
|
-
await this.simulateProposeTx(args,
|
|
677
|
-
return ts;
|
|
900
|
+
await this.simulateProposeTx(args, simulationOverridesPlan);
|
|
678
901
|
}
|
|
679
902
|
|
|
680
903
|
private async enqueueCastSignalHelper(
|
|
681
904
|
slotNumber: SlotNumber,
|
|
682
|
-
timestamp: bigint,
|
|
683
905
|
signalType: GovernanceSignalAction,
|
|
684
906
|
payload: EthAddress,
|
|
685
907
|
base: IEmpireBase,
|
|
@@ -713,6 +935,32 @@ export class SequencerPublisher {
|
|
|
713
935
|
return false;
|
|
714
936
|
}
|
|
715
937
|
|
|
938
|
+
// Check if payload was already submitted to governance
|
|
939
|
+
const cacheKey = payload.toString();
|
|
940
|
+
if (!this.payloadProposedCache.has(cacheKey)) {
|
|
941
|
+
try {
|
|
942
|
+
const l1StartBlock = await this.rollupContract.getL1StartBlock();
|
|
943
|
+
const proposed = await retry(
|
|
944
|
+
() => base.hasPayloadBeenProposed(payload.toString(), l1StartBlock),
|
|
945
|
+
'Check if payload was proposed',
|
|
946
|
+
makeBackoff([0, 1, 2]),
|
|
947
|
+
this.log,
|
|
948
|
+
true,
|
|
949
|
+
);
|
|
950
|
+
if (proposed) {
|
|
951
|
+
this.payloadProposedCache.add(cacheKey);
|
|
952
|
+
}
|
|
953
|
+
} catch (err) {
|
|
954
|
+
this.log.warn(`Failed to check if payload ${payload} was proposed after retries, skipping signal`, err);
|
|
955
|
+
return false;
|
|
956
|
+
}
|
|
957
|
+
}
|
|
958
|
+
|
|
959
|
+
if (this.payloadProposedCache.has(cacheKey)) {
|
|
960
|
+
this.log.info(`Payload ${payload} was already proposed to governance, stopping signals`);
|
|
961
|
+
return false;
|
|
962
|
+
}
|
|
963
|
+
|
|
716
964
|
const cachedLastVote = this.lastActions[signalType];
|
|
717
965
|
this.lastActions[signalType] = slotNumber;
|
|
718
966
|
const action = signalType;
|
|
@@ -731,11 +979,30 @@ export class SequencerPublisher {
|
|
|
731
979
|
lastValidL2Slot: slotNumber,
|
|
732
980
|
});
|
|
733
981
|
|
|
982
|
+
const l1BlockNumber = await this.l1TxUtils.getBlockNumber();
|
|
983
|
+
const timestamp = this.getSimulationTimestamp(slotNumber);
|
|
984
|
+
|
|
734
985
|
try {
|
|
735
986
|
await this.l1TxUtils.simulate(request, { time: timestamp }, [], mergeAbis([request.abi ?? [], ErrorsAbi]));
|
|
736
987
|
this.log.debug(`Simulation for ${action} at slot ${slotNumber} succeeded`, { request });
|
|
737
988
|
} catch (err) {
|
|
738
|
-
|
|
989
|
+
const viemError = formatViemError(err);
|
|
990
|
+
this.log.error(`Failed simulation for ${action} at slot ${slotNumber} (enqueuing the action anyway)`, viemError, {
|
|
991
|
+
simulationTimestamp: timestamp,
|
|
992
|
+
l1BlockNumber,
|
|
993
|
+
});
|
|
994
|
+
this.backupFailedTx({
|
|
995
|
+
id: keccak256(request.data!),
|
|
996
|
+
failureType: 'simulation',
|
|
997
|
+
request: { to: request.to!, data: request.data!, value: request.value?.toString() },
|
|
998
|
+
l1BlockNumber: l1BlockNumber.toString(),
|
|
999
|
+
error: { message: viemError.message, name: viemError.name },
|
|
1000
|
+
context: {
|
|
1001
|
+
actions: [action],
|
|
1002
|
+
slot: slotNumber,
|
|
1003
|
+
sender: this.getSenderAddress().toString(),
|
|
1004
|
+
},
|
|
1005
|
+
});
|
|
739
1006
|
// Yes, we enqueue the request anyway, in case there was a bug with the simulation itself
|
|
740
1007
|
}
|
|
741
1008
|
|
|
@@ -786,19 +1053,16 @@ export class SequencerPublisher {
|
|
|
786
1053
|
/**
|
|
787
1054
|
* Enqueues a governance castSignal transaction to cast a signal for a given slot number.
|
|
788
1055
|
* @param slotNumber - The slot number to cast a signal for.
|
|
789
|
-
* @param timestamp - The timestamp of the slot to cast a signal for.
|
|
790
1056
|
* @returns True if the signal was successfully enqueued, false otherwise.
|
|
791
1057
|
*/
|
|
792
1058
|
public enqueueGovernanceCastSignal(
|
|
793
1059
|
governancePayload: EthAddress,
|
|
794
1060
|
slotNumber: SlotNumber,
|
|
795
|
-
timestamp: bigint,
|
|
796
1061
|
signerAddress: EthAddress,
|
|
797
1062
|
signer: (msg: TypedDataDefinition) => Promise<`0x${string}`>,
|
|
798
1063
|
): Promise<boolean> {
|
|
799
1064
|
return this.enqueueCastSignalHelper(
|
|
800
1065
|
slotNumber,
|
|
801
|
-
timestamp,
|
|
802
1066
|
'governance-signal',
|
|
803
1067
|
governancePayload,
|
|
804
1068
|
this.govProposerContract,
|
|
@@ -811,7 +1075,6 @@ export class SequencerPublisher {
|
|
|
811
1075
|
public async enqueueSlashingActions(
|
|
812
1076
|
actions: ProposerSlashAction[],
|
|
813
1077
|
slotNumber: SlotNumber,
|
|
814
|
-
timestamp: bigint,
|
|
815
1078
|
signerAddress: EthAddress,
|
|
816
1079
|
signer: (msg: TypedDataDefinition) => Promise<`0x${string}`>,
|
|
817
1080
|
): Promise<boolean> {
|
|
@@ -822,58 +1085,6 @@ export class SequencerPublisher {
|
|
|
822
1085
|
|
|
823
1086
|
for (const action of actions) {
|
|
824
1087
|
switch (action.type) {
|
|
825
|
-
case 'vote-empire-payload': {
|
|
826
|
-
if (this.slashingProposerContract?.type !== 'empire') {
|
|
827
|
-
this.log.error('Cannot vote for empire payload on non-empire slashing contract');
|
|
828
|
-
break;
|
|
829
|
-
}
|
|
830
|
-
this.log.debug(`Enqueuing slashing vote for payload ${action.payload} at slot ${slotNumber}`, {
|
|
831
|
-
signerAddress,
|
|
832
|
-
});
|
|
833
|
-
await this.enqueueCastSignalHelper(
|
|
834
|
-
slotNumber,
|
|
835
|
-
timestamp,
|
|
836
|
-
'empire-slashing-signal',
|
|
837
|
-
action.payload,
|
|
838
|
-
this.slashingProposerContract,
|
|
839
|
-
signerAddress,
|
|
840
|
-
signer,
|
|
841
|
-
);
|
|
842
|
-
break;
|
|
843
|
-
}
|
|
844
|
-
|
|
845
|
-
case 'create-empire-payload': {
|
|
846
|
-
this.log.debug(`Enqueuing slashing create payload at slot ${slotNumber}`, { slotNumber, signerAddress });
|
|
847
|
-
const request = this.slashFactoryContract.buildCreatePayloadRequest(action.data);
|
|
848
|
-
await this.simulateAndEnqueueRequest(
|
|
849
|
-
'create-empire-payload',
|
|
850
|
-
request,
|
|
851
|
-
(receipt: TransactionReceipt) =>
|
|
852
|
-
!!this.slashFactoryContract.tryExtractSlashPayloadCreatedEvent(receipt.logs),
|
|
853
|
-
slotNumber,
|
|
854
|
-
timestamp,
|
|
855
|
-
);
|
|
856
|
-
break;
|
|
857
|
-
}
|
|
858
|
-
|
|
859
|
-
case 'execute-empire-payload': {
|
|
860
|
-
this.log.debug(`Enqueuing slashing execute payload at slot ${slotNumber}`, { slotNumber, signerAddress });
|
|
861
|
-
if (this.slashingProposerContract?.type !== 'empire') {
|
|
862
|
-
this.log.error('Cannot execute slashing payload on non-empire slashing contract');
|
|
863
|
-
return false;
|
|
864
|
-
}
|
|
865
|
-
const empireSlashingProposer = this.slashingProposerContract as EmpireSlashingProposerContract;
|
|
866
|
-
const request = empireSlashingProposer.buildExecuteRoundRequest(action.round);
|
|
867
|
-
await this.simulateAndEnqueueRequest(
|
|
868
|
-
'execute-empire-payload',
|
|
869
|
-
request,
|
|
870
|
-
(receipt: TransactionReceipt) => !!empireSlashingProposer.tryExtractPayloadSubmittedEvent(receipt.logs),
|
|
871
|
-
slotNumber,
|
|
872
|
-
timestamp,
|
|
873
|
-
);
|
|
874
|
-
break;
|
|
875
|
-
}
|
|
876
|
-
|
|
877
1088
|
case 'vote-offenses': {
|
|
878
1089
|
this.log.debug(`Enqueuing slashing vote for ${action.votes.length} votes at slot ${slotNumber}`, {
|
|
879
1090
|
slotNumber,
|
|
@@ -881,19 +1092,17 @@ export class SequencerPublisher {
|
|
|
881
1092
|
votesCount: action.votes.length,
|
|
882
1093
|
signerAddress,
|
|
883
1094
|
});
|
|
884
|
-
if (this.slashingProposerContract
|
|
885
|
-
this.log.error('
|
|
1095
|
+
if (!this.slashingProposerContract) {
|
|
1096
|
+
this.log.error('No slashing proposer contract available');
|
|
886
1097
|
return false;
|
|
887
1098
|
}
|
|
888
|
-
const tallySlashingProposer = this.slashingProposerContract as TallySlashingProposerContract;
|
|
889
1099
|
const votes = bufferToHex(encodeSlashConsensusVotes(action.votes));
|
|
890
|
-
const request = await
|
|
1100
|
+
const request = await this.slashingProposerContract.buildVoteRequestFromSigner(votes, slotNumber, signer);
|
|
891
1101
|
await this.simulateAndEnqueueRequest(
|
|
892
1102
|
'vote-offenses',
|
|
893
1103
|
request,
|
|
894
|
-
(receipt: TransactionReceipt) => !!
|
|
1104
|
+
(receipt: TransactionReceipt) => !!this.slashingProposerContract!.tryExtractVoteCastEvent(receipt.logs),
|
|
895
1105
|
slotNumber,
|
|
896
|
-
timestamp,
|
|
897
1106
|
);
|
|
898
1107
|
break;
|
|
899
1108
|
}
|
|
@@ -904,18 +1113,20 @@ export class SequencerPublisher {
|
|
|
904
1113
|
round: action.round,
|
|
905
1114
|
signerAddress,
|
|
906
1115
|
});
|
|
907
|
-
if (this.slashingProposerContract
|
|
908
|
-
this.log.error('
|
|
1116
|
+
if (!this.slashingProposerContract) {
|
|
1117
|
+
this.log.error('No slashing proposer contract available');
|
|
909
1118
|
return false;
|
|
910
1119
|
}
|
|
911
|
-
const
|
|
912
|
-
|
|
1120
|
+
const executeRequest = this.slashingProposerContract.buildExecuteRoundRequest(
|
|
1121
|
+
action.round,
|
|
1122
|
+
action.committees,
|
|
1123
|
+
);
|
|
913
1124
|
await this.simulateAndEnqueueRequest(
|
|
914
1125
|
'execute-slash',
|
|
915
|
-
|
|
916
|
-
(receipt: TransactionReceipt) =>
|
|
1126
|
+
executeRequest,
|
|
1127
|
+
(receipt: TransactionReceipt) =>
|
|
1128
|
+
!!this.slashingProposerContract!.tryExtractRoundExecutedEvent(receipt.logs),
|
|
917
1129
|
slotNumber,
|
|
918
|
-
timestamp,
|
|
919
1130
|
);
|
|
920
1131
|
break;
|
|
921
1132
|
}
|
|
@@ -935,12 +1146,12 @@ export class SequencerPublisher {
|
|
|
935
1146
|
checkpoint: Checkpoint,
|
|
936
1147
|
attestationsAndSigners: CommitteeAttestationsAndSigners,
|
|
937
1148
|
attestationsAndSignersSignature: Signature,
|
|
938
|
-
opts:
|
|
1149
|
+
opts: EnqueueProposeCheckpointOpts = {},
|
|
939
1150
|
): Promise<void> {
|
|
940
1151
|
const checkpointHeader = checkpoint.header;
|
|
941
1152
|
|
|
942
1153
|
const blobFields = checkpoint.toBlobFields();
|
|
943
|
-
const blobs = getBlobsPerL1Block(blobFields);
|
|
1154
|
+
const blobs = await getBlobsPerL1Block(blobFields);
|
|
944
1155
|
|
|
945
1156
|
const proposeTxArgs: L1ProcessArgs = {
|
|
946
1157
|
header: checkpointHeader,
|
|
@@ -951,7 +1162,9 @@ export class SequencerPublisher {
|
|
|
951
1162
|
feeAssetPriceModifier: checkpoint.feeAssetPriceModifier,
|
|
952
1163
|
};
|
|
953
1164
|
|
|
954
|
-
|
|
1165
|
+
const simulationOverridesPlan = SimulationOverridesBuilder.from(opts.simulationOverridesPlan)
|
|
1166
|
+
.withoutBlobCheck()
|
|
1167
|
+
.build();
|
|
955
1168
|
|
|
956
1169
|
try {
|
|
957
1170
|
// @note This will make sure that we are passing the checks for our header ASSUMING that the data is also made available
|
|
@@ -959,23 +1172,47 @@ export class SequencerPublisher {
|
|
|
959
1172
|
// By simulation issue, I mean the fact that the block.timestamp is equal to the last block, not the next, which
|
|
960
1173
|
// make time consistency checks break.
|
|
961
1174
|
// TODO(palla): Check whether we're validating twice, once here and once within addProposeTx, since we call simulateProposeTx in both places.
|
|
962
|
-
|
|
1175
|
+
await this.validateCheckpointForSubmission(
|
|
963
1176
|
checkpoint,
|
|
964
1177
|
attestationsAndSigners,
|
|
965
1178
|
attestationsAndSignersSignature,
|
|
966
|
-
|
|
1179
|
+
simulationOverridesPlan,
|
|
967
1180
|
);
|
|
968
1181
|
} catch (err: any) {
|
|
969
1182
|
this.log.error(`Checkpoint validation failed. ${err instanceof Error ? err.message : 'No error message'}`, err, {
|
|
970
1183
|
...checkpoint.getStats(),
|
|
971
1184
|
slotNumber: checkpoint.header.slotNumber,
|
|
972
|
-
|
|
1185
|
+
simulationOverridesPlan,
|
|
973
1186
|
});
|
|
974
1187
|
throw err;
|
|
975
1188
|
}
|
|
976
1189
|
|
|
977
|
-
|
|
978
|
-
|
|
1190
|
+
// Build a pre-check callback that re-validates the checkpoint before L1 submission.
|
|
1191
|
+
// During pipelining this catches stale proposals due to prunes or L1 reorgs that occur during the pipeline sleep.
|
|
1192
|
+
let preCheck = undefined;
|
|
1193
|
+
if (this.epochCache.isProposerPipeliningEnabled()) {
|
|
1194
|
+
preCheck = async () => {
|
|
1195
|
+
this.log.debug(`Re-validating checkpoint ${checkpoint.number} before L1 submission`);
|
|
1196
|
+
await this.validateCheckpointForSubmission(
|
|
1197
|
+
checkpoint,
|
|
1198
|
+
attestationsAndSigners,
|
|
1199
|
+
attestationsAndSignersSignature,
|
|
1200
|
+
simulationOverridesPlan,
|
|
1201
|
+
);
|
|
1202
|
+
};
|
|
1203
|
+
}
|
|
1204
|
+
|
|
1205
|
+
this.log.verbose(`Enqueuing checkpoint propose transaction`, {
|
|
1206
|
+
...checkpoint.toCheckpointInfo(),
|
|
1207
|
+
txTimeoutAt: opts.txTimeoutAt,
|
|
1208
|
+
simulationOverridesPlan,
|
|
1209
|
+
});
|
|
1210
|
+
await this.addProposeTx(
|
|
1211
|
+
checkpoint,
|
|
1212
|
+
proposeTxArgs,
|
|
1213
|
+
{ txTimeoutAt: opts.txTimeoutAt, simulationOverridesPlan },
|
|
1214
|
+
preCheck,
|
|
1215
|
+
);
|
|
979
1216
|
}
|
|
980
1217
|
|
|
981
1218
|
public enqueueInvalidateCheckpoint(
|
|
@@ -1018,8 +1255,8 @@ export class SequencerPublisher {
|
|
|
1018
1255
|
request: L1TxRequest,
|
|
1019
1256
|
checkSuccess: (receipt: TransactionReceipt) => boolean | undefined,
|
|
1020
1257
|
slotNumber: SlotNumber,
|
|
1021
|
-
timestamp: bigint,
|
|
1022
1258
|
) {
|
|
1259
|
+
const timestamp = this.getSimulationTimestamp(slotNumber);
|
|
1023
1260
|
const logData = { slotNumber, timestamp, gasLimit: undefined as bigint | undefined };
|
|
1024
1261
|
if (this.lastActions[action] && this.lastActions[action] === slotNumber) {
|
|
1025
1262
|
this.log.debug(`Skipping duplicate action ${action} for slot ${slotNumber}`);
|
|
@@ -1031,15 +1268,31 @@ export class SequencerPublisher {
|
|
|
1031
1268
|
|
|
1032
1269
|
this.log.debug(`Simulating ${action} for slot ${slotNumber}`, logData);
|
|
1033
1270
|
|
|
1271
|
+
const l1BlockNumber = await this.l1TxUtils.getBlockNumber();
|
|
1272
|
+
|
|
1034
1273
|
let gasUsed: bigint;
|
|
1035
1274
|
const simulateAbi = mergeAbis([request.abi ?? [], ErrorsAbi]);
|
|
1275
|
+
|
|
1036
1276
|
try {
|
|
1037
|
-
({ gasUsed } = await this.l1TxUtils.simulate(request, { time: timestamp }, [], simulateAbi));
|
|
1277
|
+
({ gasUsed } = await this.l1TxUtils.simulate(request, { time: timestamp }, [], simulateAbi));
|
|
1038
1278
|
this.log.verbose(`Simulation for ${action} succeeded`, { ...logData, request, gasUsed });
|
|
1039
1279
|
} catch (err) {
|
|
1040
1280
|
const viemError = formatViemError(err, simulateAbi);
|
|
1041
1281
|
this.log.error(`Simulation for ${action} at ${slotNumber} failed`, viemError, logData);
|
|
1042
1282
|
|
|
1283
|
+
this.backupFailedTx({
|
|
1284
|
+
id: keccak256(request.data!),
|
|
1285
|
+
failureType: 'simulation',
|
|
1286
|
+
request: { to: request.to!, data: request.data!, value: request.value?.toString() },
|
|
1287
|
+
l1BlockNumber: l1BlockNumber.toString(),
|
|
1288
|
+
error: { message: viemError.message, name: viemError.name },
|
|
1289
|
+
context: {
|
|
1290
|
+
actions: [action],
|
|
1291
|
+
slot: slotNumber,
|
|
1292
|
+
sender: this.getSenderAddress().toString(),
|
|
1293
|
+
},
|
|
1294
|
+
});
|
|
1295
|
+
|
|
1043
1296
|
return false;
|
|
1044
1297
|
}
|
|
1045
1298
|
|
|
@@ -1079,6 +1332,7 @@ export class SequencerPublisher {
|
|
|
1079
1332
|
*/
|
|
1080
1333
|
public interrupt() {
|
|
1081
1334
|
this.interrupted = true;
|
|
1335
|
+
this.interruptibleSleep.interrupt();
|
|
1082
1336
|
this.l1TxUtils.interrupt();
|
|
1083
1337
|
}
|
|
1084
1338
|
|
|
@@ -1088,11 +1342,7 @@ export class SequencerPublisher {
|
|
|
1088
1342
|
this.l1TxUtils.restart();
|
|
1089
1343
|
}
|
|
1090
1344
|
|
|
1091
|
-
private async prepareProposeTx(
|
|
1092
|
-
encodedData: L1ProcessArgs,
|
|
1093
|
-
timestamp: bigint,
|
|
1094
|
-
options: { forcePendingCheckpointNumber?: CheckpointNumber },
|
|
1095
|
-
) {
|
|
1345
|
+
private async prepareProposeTx(encodedData: L1ProcessArgs, simulationOverridesPlan?: SimulationOverridesPlan) {
|
|
1096
1346
|
const kzg = Blob.getViemKzgInstance();
|
|
1097
1347
|
const blobInput = getPrefixedEthBlobCommitments(encodedData.blobs);
|
|
1098
1348
|
this.log.debug('Validating blob input', { blobInput });
|
|
@@ -1123,9 +1373,27 @@ export class SequencerPublisher {
|
|
|
1123
1373
|
kzg,
|
|
1124
1374
|
},
|
|
1125
1375
|
)
|
|
1126
|
-
.catch(err => {
|
|
1127
|
-
const
|
|
1128
|
-
this.log.error(`Failed to validate blobs`, message, { metaMessages });
|
|
1376
|
+
.catch(async err => {
|
|
1377
|
+
const viemError = formatViemError(err);
|
|
1378
|
+
this.log.error(`Failed to validate blobs`, viemError.message, { metaMessages: viemError.metaMessages });
|
|
1379
|
+
const validateBlobsData = encodeFunctionData({
|
|
1380
|
+
abi: RollupAbi,
|
|
1381
|
+
functionName: 'validateBlobs',
|
|
1382
|
+
args: [blobInput],
|
|
1383
|
+
});
|
|
1384
|
+
const l1BlockNumber = await this.l1TxUtils.getBlockNumber();
|
|
1385
|
+
this.backupFailedTx({
|
|
1386
|
+
id: keccak256(validateBlobsData),
|
|
1387
|
+
failureType: 'simulation',
|
|
1388
|
+
request: { to: this.rollupContract.address as Hex, data: validateBlobsData },
|
|
1389
|
+
blobData: encodedData.blobs.map(b => toHex(b.data)) as Hex[],
|
|
1390
|
+
l1BlockNumber: l1BlockNumber.toString(),
|
|
1391
|
+
error: { message: viemError.message, name: viemError.name },
|
|
1392
|
+
context: {
|
|
1393
|
+
actions: ['validate-blobs'],
|
|
1394
|
+
sender: this.getSenderAddress().toString(),
|
|
1395
|
+
},
|
|
1396
|
+
});
|
|
1129
1397
|
throw new Error('Failed to validate blobs');
|
|
1130
1398
|
});
|
|
1131
1399
|
}
|
|
@@ -1145,7 +1413,7 @@ export class SequencerPublisher {
|
|
|
1145
1413
|
blobInput,
|
|
1146
1414
|
] as const;
|
|
1147
1415
|
|
|
1148
|
-
const { rollupData, simulationResult } = await this.simulateProposeTx(args,
|
|
1416
|
+
const { rollupData, simulationResult } = await this.simulateProposeTx(args, simulationOverridesPlan);
|
|
1149
1417
|
|
|
1150
1418
|
return { args, blobEvaluationGas, rollupData, simulationResult };
|
|
1151
1419
|
}
|
|
@@ -1153,7 +1421,6 @@ export class SequencerPublisher {
|
|
|
1153
1421
|
/**
|
|
1154
1422
|
* Simulates the propose tx with eth_simulateV1
|
|
1155
1423
|
* @param args - The propose tx args
|
|
1156
|
-
* @param timestamp - The timestamp to simulate proposal at
|
|
1157
1424
|
* @returns The simulation result
|
|
1158
1425
|
*/
|
|
1159
1426
|
private async simulateProposeTx(
|
|
@@ -1170,8 +1437,7 @@ export class SequencerPublisher {
|
|
|
1170
1437
|
ViemSignature,
|
|
1171
1438
|
`0x${string}`,
|
|
1172
1439
|
],
|
|
1173
|
-
|
|
1174
|
-
options: { forcePendingCheckpointNumber?: CheckpointNumber },
|
|
1440
|
+
simulationOverridesPlan?: SimulationOverridesPlan,
|
|
1175
1441
|
) {
|
|
1176
1442
|
const rollupData = encodeFunctionData({
|
|
1177
1443
|
abi: RollupAbi,
|
|
@@ -1179,23 +1445,7 @@ export class SequencerPublisher {
|
|
|
1179
1445
|
args,
|
|
1180
1446
|
});
|
|
1181
1447
|
|
|
1182
|
-
|
|
1183
|
-
const forcePendingCheckpointNumberStateDiff = (
|
|
1184
|
-
options.forcePendingCheckpointNumber !== undefined
|
|
1185
|
-
? await this.rollupContract.makePendingCheckpointNumberOverride(options.forcePendingCheckpointNumber)
|
|
1186
|
-
: []
|
|
1187
|
-
).flatMap(override => override.stateDiff ?? []);
|
|
1188
|
-
|
|
1189
|
-
const stateOverrides: StateOverride = [
|
|
1190
|
-
{
|
|
1191
|
-
address: this.rollupContract.address,
|
|
1192
|
-
// @note we override checkBlob to false since blobs are not part simulate()
|
|
1193
|
-
stateDiff: [
|
|
1194
|
-
{ slot: toPaddedHex(RollupContract.checkBlobStorageSlot, true), value: toPaddedHex(0n, true) },
|
|
1195
|
-
...forcePendingCheckpointNumberStateDiff,
|
|
1196
|
-
],
|
|
1197
|
-
},
|
|
1198
|
-
];
|
|
1448
|
+
const stateOverrides = await buildSimulationOverridesStateOverride(this.rollupContract, simulationOverridesPlan);
|
|
1199
1449
|
// In fisherman mode, simulate as the proposer but with sufficient balance
|
|
1200
1450
|
if (this.proposerAddressForSimulation) {
|
|
1201
1451
|
stateOverrides.push({
|
|
@@ -1204,6 +1454,9 @@ export class SequencerPublisher {
|
|
|
1204
1454
|
});
|
|
1205
1455
|
}
|
|
1206
1456
|
|
|
1457
|
+
const l1BlockNumber = await this.l1TxUtils.getBlockNumber();
|
|
1458
|
+
const simTs = this.getSimulationTimestamp(SlotNumber.fromBigInt(args[0].header.slotNumber));
|
|
1459
|
+
|
|
1207
1460
|
const simulationResult = await this.l1TxUtils
|
|
1208
1461
|
.simulate(
|
|
1209
1462
|
{
|
|
@@ -1213,8 +1466,7 @@ export class SequencerPublisher {
|
|
|
1213
1466
|
...(this.proposerAddressForSimulation && { from: this.proposerAddressForSimulation.toString() }),
|
|
1214
1467
|
},
|
|
1215
1468
|
{
|
|
1216
|
-
|
|
1217
|
-
time: timestamp + 1n,
|
|
1469
|
+
time: simTs,
|
|
1218
1470
|
// @note reth should have a 30m gas limit per block but throws errors that this tx is beyond limit so we increase here
|
|
1219
1471
|
gasLimit: MAX_L1_TX_LIMIT * 2n,
|
|
1220
1472
|
},
|
|
@@ -1236,7 +1488,19 @@ export class SequencerPublisher {
|
|
|
1236
1488
|
logs: [],
|
|
1237
1489
|
};
|
|
1238
1490
|
}
|
|
1239
|
-
this.log.error(`Failed to simulate propose tx`, viemError);
|
|
1491
|
+
this.log.error(`Failed to simulate propose tx`, viemError, { simulationTimestamp: simTs });
|
|
1492
|
+
this.backupFailedTx({
|
|
1493
|
+
id: keccak256(rollupData),
|
|
1494
|
+
failureType: 'simulation',
|
|
1495
|
+
request: { to: this.rollupContract.address, data: rollupData },
|
|
1496
|
+
l1BlockNumber: l1BlockNumber.toString(),
|
|
1497
|
+
error: { message: viemError.message, name: viemError.name },
|
|
1498
|
+
context: {
|
|
1499
|
+
actions: ['propose'],
|
|
1500
|
+
slot: Number(args[0].header.slotNumber),
|
|
1501
|
+
sender: this.getSenderAddress().toString(),
|
|
1502
|
+
},
|
|
1503
|
+
});
|
|
1240
1504
|
throw err;
|
|
1241
1505
|
});
|
|
1242
1506
|
|
|
@@ -1246,16 +1510,15 @@ export class SequencerPublisher {
|
|
|
1246
1510
|
private async addProposeTx(
|
|
1247
1511
|
checkpoint: Checkpoint,
|
|
1248
1512
|
encodedData: L1ProcessArgs,
|
|
1249
|
-
opts:
|
|
1250
|
-
|
|
1513
|
+
opts: EnqueueProposeCheckpointOpts = {},
|
|
1514
|
+
preCheck?: () => Promise<void>,
|
|
1251
1515
|
): Promise<void> {
|
|
1252
1516
|
const slot = checkpoint.header.slotNumber;
|
|
1253
1517
|
const timer = new Timer();
|
|
1254
1518
|
const kzg = Blob.getViemKzgInstance();
|
|
1255
1519
|
const { rollupData, simulationResult, blobEvaluationGas } = await this.prepareProposeTx(
|
|
1256
1520
|
encodedData,
|
|
1257
|
-
|
|
1258
|
-
opts,
|
|
1521
|
+
opts.simulationOverridesPlan,
|
|
1259
1522
|
);
|
|
1260
1523
|
const startBlock = await this.l1TxUtils.getBlockNumber();
|
|
1261
1524
|
const gasLimit = this.l1TxUtils.bumpGasLimit(
|
|
@@ -1279,7 +1542,8 @@ export class SequencerPublisher {
|
|
|
1279
1542
|
data: rollupData,
|
|
1280
1543
|
},
|
|
1281
1544
|
lastValidL2Slot: checkpoint.header.slotNumber,
|
|
1282
|
-
gasConfig: {
|
|
1545
|
+
gasConfig: { txTimeoutAt: opts.txTimeoutAt, gasLimit },
|
|
1546
|
+
preCheck,
|
|
1283
1547
|
blobConfig: {
|
|
1284
1548
|
blobs: encodedData.blobs.map(b => b.data),
|
|
1285
1549
|
kzg,
|
|
@@ -1332,4 +1596,17 @@ export class SequencerPublisher {
|
|
|
1332
1596
|
},
|
|
1333
1597
|
});
|
|
1334
1598
|
}
|
|
1599
|
+
|
|
1600
|
+
/** Returns the timestamp of the last L1 slot within a given L2 slot. Used as the simulation timestamp
|
|
1601
|
+
* for eth_simulateV1 calls, since it's guaranteed to be greater than any L1 block produced during the slot. */
|
|
1602
|
+
private getSimulationTimestamp(slot: SlotNumber): bigint {
|
|
1603
|
+
const l1Constants = this.epochCache.getL1Constants();
|
|
1604
|
+
return getLastL1SlotTimestampForL2Slot(slot, l1Constants);
|
|
1605
|
+
}
|
|
1606
|
+
|
|
1607
|
+
/** Returns the timestamp of the next L1 slot boundary after now. */
|
|
1608
|
+
private getNextL1SlotTimestamp(): bigint {
|
|
1609
|
+
const l1Constants = this.epochCache.getL1Constants();
|
|
1610
|
+
return getNextL1SlotTimestamp(this.dateProvider.nowInSeconds(), l1Constants);
|
|
1611
|
+
}
|
|
1335
1612
|
}
|