@aztec/sequencer-client 0.0.1-commit.e2b2873ed → 0.0.1-commit.e304674f1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dest/client/sequencer-client.d.ts +15 -7
- package/dest/client/sequencer-client.d.ts.map +1 -1
- package/dest/client/sequencer-client.js +60 -30
- package/dest/config.d.ts +26 -6
- package/dest/config.d.ts.map +1 -1
- package/dest/config.js +44 -21
- package/dest/global_variable_builder/global_builder.d.ts +15 -11
- package/dest/global_variable_builder/global_builder.d.ts.map +1 -1
- package/dest/global_variable_builder/global_builder.js +29 -25
- 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 +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 +59 -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 +82 -37
- package/dest/publisher/sequencer-publisher.d.ts.map +1 -1
- package/dest/publisher/sequencer-publisher.js +430 -118
- package/dest/sequencer/checkpoint_proposal_job.d.ts +36 -9
- package/dest/sequencer/checkpoint_proposal_job.d.ts.map +1 -1
- package/dest/sequencer/checkpoint_proposal_job.js +361 -192
- 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 +2 -1
- package/dest/sequencer/events.d.ts.map +1 -1
- package/dest/sequencer/metrics.d.ts +21 -5
- package/dest/sequencer/metrics.d.ts.map +1 -1
- package/dest/sequencer/metrics.js +97 -15
- package/dest/sequencer/sequencer.d.ts +40 -17
- package/dest/sequencer/sequencer.d.ts.map +1 -1
- package/dest/sequencer/sequencer.js +152 -95
- package/dest/sequencer/timetable.d.ts +7 -3
- package/dest/sequencer/timetable.d.ts.map +1 -1
- package/dest/sequencer/timetable.js +21 -12
- 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 +45 -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 +27 -28
- package/src/client/sequencer-client.ts +76 -30
- package/src/config.ts +56 -27
- package/src/global_variable_builder/global_builder.ts +38 -27
- package/src/global_variable_builder/index.ts +1 -1
- package/src/publisher/config.ts +153 -43
- 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 +55 -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 +503 -168
- package/src/sequencer/README.md +81 -12
- package/src/sequencer/checkpoint_proposal_job.ts +471 -201
- package/src/sequencer/checkpoint_voter.ts +1 -12
- package/src/sequencer/events.ts +1 -1
- package/src/sequencer/metrics.ts +106 -18
- package/src/sequencer/sequencer.ts +216 -109
- package/src/sequencer/timetable.ts +26 -15
- package/src/sequencer/types.ts +1 -1
- package/src/test/index.ts +2 -4
- package/src/test/mock_checkpoint_builder.ts +63 -49
- package/src/test/utils.ts +5 -2
|
@@ -3,13 +3,14 @@ import { Blob, getBlobsPerL1Block, getPrefixedEthBlobCommitments } from '@aztec/
|
|
|
3
3
|
import type { EpochCache } from '@aztec/epoch-cache';
|
|
4
4
|
import type { L1ContractsConfig } from '@aztec/ethereum/config';
|
|
5
5
|
import {
|
|
6
|
-
|
|
6
|
+
FeeAssetPriceOracle,
|
|
7
|
+
type FeeHeader,
|
|
7
8
|
type GovernanceProposerContract,
|
|
8
9
|
type IEmpireBase,
|
|
9
10
|
MULTI_CALL_3_ADDRESS,
|
|
10
11
|
Multicall3,
|
|
11
12
|
RollupContract,
|
|
12
|
-
type
|
|
13
|
+
type SlashingProposerContract,
|
|
13
14
|
type ViemCommitteeAttestations,
|
|
14
15
|
type ViemHeader,
|
|
15
16
|
} from '@aztec/ethereum/contracts';
|
|
@@ -18,36 +19,64 @@ 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';
|
|
34
|
+
import { TimeoutError } from '@aztec/foundation/error';
|
|
32
35
|
import { EthAddress } from '@aztec/foundation/eth-address';
|
|
33
36
|
import { Signature, type ViemSignature } from '@aztec/foundation/eth-signature';
|
|
34
37
|
import { type Logger, createLogger } from '@aztec/foundation/log';
|
|
38
|
+
import { makeBackoff, retry } from '@aztec/foundation/retry';
|
|
39
|
+
import { InterruptibleSleep } from '@aztec/foundation/sleep';
|
|
35
40
|
import { bufferToHex } from '@aztec/foundation/string';
|
|
36
|
-
import { DateProvider, Timer } from '@aztec/foundation/timer';
|
|
41
|
+
import { type DateProvider, Timer } from '@aztec/foundation/timer';
|
|
37
42
|
import { EmpireBaseAbi, ErrorsAbi, RollupAbi } from '@aztec/l1-artifacts';
|
|
38
43
|
import { type ProposerSlashAction, encodeSlashConsensusVotes } from '@aztec/slasher';
|
|
39
44
|
import { CommitteeAttestationsAndSigners, type ValidateCheckpointResult } from '@aztec/stdlib/block';
|
|
40
45
|
import type { Checkpoint } from '@aztec/stdlib/checkpoint';
|
|
41
|
-
import {
|
|
46
|
+
import { getLastL1SlotTimestampForL2Slot, getNextL1SlotTimestamp } from '@aztec/stdlib/epoch-helpers';
|
|
42
47
|
import type { CheckpointHeader } from '@aztec/stdlib/rollup';
|
|
43
48
|
import type { L1PublishCheckpointStats } from '@aztec/stdlib/stats';
|
|
44
49
|
import { type TelemetryClient, type Tracer, getTelemetryClient, trackSpan } from '@aztec/telemetry-client';
|
|
45
50
|
|
|
46
|
-
import {
|
|
47
|
-
|
|
48
|
-
|
|
51
|
+
import {
|
|
52
|
+
type Hex,
|
|
53
|
+
type StateOverride,
|
|
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';
|
|
49
64
|
import { SequencerPublisherMetrics } from './sequencer-publisher-metrics.js';
|
|
50
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
|
+
|
|
51
80
|
/** Arguments to the process method of the rollup contract */
|
|
52
81
|
type L1ProcessArgs = {
|
|
53
82
|
/** The L2 block header. */
|
|
@@ -60,6 +89,8 @@ type L1ProcessArgs = {
|
|
|
60
89
|
attestationsAndSigners: CommitteeAttestationsAndSigners;
|
|
61
90
|
/** Attestations and signers signature */
|
|
62
91
|
attestationsAndSignersSignature: Signature;
|
|
92
|
+
/** The fee asset price modifier in basis points (from oracle) */
|
|
93
|
+
feeAssetPriceModifier: bigint;
|
|
63
94
|
};
|
|
64
95
|
|
|
65
96
|
export const Actions = [
|
|
@@ -67,16 +98,13 @@ export const Actions = [
|
|
|
67
98
|
'invalidate-by-insufficient-attestations',
|
|
68
99
|
'propose',
|
|
69
100
|
'governance-signal',
|
|
70
|
-
'empire-slashing-signal',
|
|
71
|
-
'create-empire-payload',
|
|
72
|
-
'execute-empire-payload',
|
|
73
101
|
'vote-offenses',
|
|
74
102
|
'execute-slash',
|
|
75
103
|
] as const;
|
|
76
104
|
|
|
77
105
|
export type Action = (typeof Actions)[number];
|
|
78
106
|
|
|
79
|
-
type GovernanceSignalAction = Extract<Action, 'governance-signal'
|
|
107
|
+
type GovernanceSignalAction = Extract<Action, 'governance-signal'>;
|
|
80
108
|
|
|
81
109
|
// Sorting for actions such that invalidations go before proposals, and proposals go before votes
|
|
82
110
|
export const compareActions = (a: Action, b: Action) => Actions.indexOf(a) - Actions.indexOf(b);
|
|
@@ -87,6 +115,8 @@ export type InvalidateCheckpointRequest = {
|
|
|
87
115
|
gasUsed: bigint;
|
|
88
116
|
checkpointNumber: CheckpointNumber;
|
|
89
117
|
forcePendingCheckpointNumber: CheckpointNumber;
|
|
118
|
+
/** Archive at the rollback target checkpoint (checkpoint N-1). */
|
|
119
|
+
lastArchive: Fr;
|
|
90
120
|
};
|
|
91
121
|
|
|
92
122
|
interface RequestWithExpiry {
|
|
@@ -95,6 +125,8 @@ interface RequestWithExpiry {
|
|
|
95
125
|
lastValidL2Slot: SlotNumber;
|
|
96
126
|
gasConfig?: Pick<L1TxConfig, 'txTimeoutAt' | 'gasLimit'>;
|
|
97
127
|
blobConfig?: L1BlobInputs;
|
|
128
|
+
/** Optional pre-send validation. If it rejects, the request is discarded. */
|
|
129
|
+
preCheck?: () => Promise<void>;
|
|
98
130
|
checkSuccess: (
|
|
99
131
|
request: L1TxRequest,
|
|
100
132
|
result?: { receipt: TransactionReceipt; stats?: TransactionStats; errorMsg?: string },
|
|
@@ -105,6 +137,7 @@ export class SequencerPublisher {
|
|
|
105
137
|
private interrupted = false;
|
|
106
138
|
private metrics: SequencerPublisherMetrics;
|
|
107
139
|
public epochCache: EpochCache;
|
|
140
|
+
private failedTxStore?: Promise<L1TxFailedStore | undefined>;
|
|
108
141
|
|
|
109
142
|
protected governanceLog = createLogger('sequencer:publisher:governance');
|
|
110
143
|
protected slashingLog = createLogger('sequencer:publisher:slashing');
|
|
@@ -112,61 +145,80 @@ export class SequencerPublisher {
|
|
|
112
145
|
protected lastActions: Partial<Record<Action, SlotNumber>> = {};
|
|
113
146
|
|
|
114
147
|
private isPayloadEmptyCache: Map<string, boolean> = new Map<string, boolean>();
|
|
148
|
+
private payloadProposedCache: Set<string> = new Set<string>();
|
|
115
149
|
|
|
116
150
|
protected log: Logger;
|
|
117
151
|
protected ethereumSlotDuration: bigint;
|
|
152
|
+
protected aztecSlotDuration: bigint;
|
|
153
|
+
|
|
154
|
+
/** Date provider for wall-clock time. */
|
|
155
|
+
private readonly dateProvider: DateProvider;
|
|
118
156
|
|
|
119
157
|
private blobClient: BlobClientInterface;
|
|
120
158
|
|
|
121
159
|
/** Address to use for simulations in fisherman mode (actual proposer's address) */
|
|
122
160
|
private proposerAddressForSimulation?: EthAddress;
|
|
123
161
|
|
|
162
|
+
/** Optional callback to obtain a replacement publisher when the current one fails to send. */
|
|
163
|
+
private getNextPublisher?: (excludeAddresses: EthAddress[]) => Promise<L1TxUtils | undefined>;
|
|
164
|
+
|
|
124
165
|
/** L1 fee analyzer for fisherman mode */
|
|
125
166
|
private l1FeeAnalyzer?: L1FeeAnalyzer;
|
|
167
|
+
|
|
168
|
+
/** Fee asset price oracle for computing price modifiers from Uniswap V4 */
|
|
169
|
+
private feeAssetPriceOracle: FeeAssetPriceOracle;
|
|
170
|
+
|
|
171
|
+
/** Interruptible sleep used by sendRequestsAt to wait until a target timestamp. */
|
|
172
|
+
private readonly interruptibleSleep = new InterruptibleSleep();
|
|
173
|
+
|
|
126
174
|
// A CALL to a cold address is 2700 gas
|
|
127
175
|
public static MULTICALL_OVERHEAD_GAS_GUESS = 5000n;
|
|
128
176
|
|
|
129
177
|
// Gas report for VotingWithSigTest shows a max gas of 100k, but we've seen it cost 700k+ in testnet
|
|
130
178
|
public static VOTE_GAS_GUESS: bigint = 800_000n;
|
|
131
179
|
|
|
132
|
-
public l1TxUtils:
|
|
180
|
+
public l1TxUtils: L1TxUtils;
|
|
133
181
|
public rollupContract: RollupContract;
|
|
134
182
|
public govProposerContract: GovernanceProposerContract;
|
|
135
|
-
public slashingProposerContract:
|
|
136
|
-
public slashFactoryContract: SlashFactoryContract;
|
|
183
|
+
public slashingProposerContract: SlashingProposerContract | undefined;
|
|
137
184
|
|
|
138
185
|
public readonly tracer: Tracer;
|
|
139
186
|
|
|
140
187
|
protected requests: RequestWithExpiry[] = [];
|
|
141
188
|
|
|
142
189
|
constructor(
|
|
143
|
-
private config:
|
|
190
|
+
private config: Pick<SequencerPublisherConfig, 'fishermanMode' | 'l1TxFailedStore'> &
|
|
191
|
+
Pick<L1ContractsConfig, 'ethereumSlotDuration' | 'aztecSlotDuration'> & { l1ChainId: number },
|
|
144
192
|
deps: {
|
|
145
193
|
telemetry?: TelemetryClient;
|
|
146
194
|
blobClient: BlobClientInterface;
|
|
147
|
-
l1TxUtils:
|
|
195
|
+
l1TxUtils: L1TxUtils;
|
|
148
196
|
rollupContract: RollupContract;
|
|
149
|
-
slashingProposerContract:
|
|
197
|
+
slashingProposerContract: SlashingProposerContract | undefined;
|
|
150
198
|
governanceProposerContract: GovernanceProposerContract;
|
|
151
|
-
slashFactoryContract: SlashFactoryContract;
|
|
152
199
|
epochCache: EpochCache;
|
|
153
200
|
dateProvider: DateProvider;
|
|
154
201
|
metrics: SequencerPublisherMetrics;
|
|
155
202
|
lastActions: Partial<Record<Action, SlotNumber>>;
|
|
156
203
|
log?: Logger;
|
|
204
|
+
getNextPublisher?: (excludeAddresses: EthAddress[]) => Promise<L1TxUtils | undefined>;
|
|
157
205
|
},
|
|
158
206
|
) {
|
|
159
207
|
this.log = deps.log ?? createLogger('sequencer:publisher');
|
|
160
208
|
this.ethereumSlotDuration = BigInt(config.ethereumSlotDuration);
|
|
209
|
+
this.aztecSlotDuration = BigInt(config.aztecSlotDuration);
|
|
210
|
+
this.dateProvider = deps.dateProvider;
|
|
161
211
|
this.epochCache = deps.epochCache;
|
|
162
212
|
this.lastActions = deps.lastActions;
|
|
163
213
|
|
|
164
214
|
this.blobClient = deps.blobClient;
|
|
215
|
+
this.dateProvider = deps.dateProvider;
|
|
165
216
|
|
|
166
217
|
const telemetry = deps.telemetry ?? getTelemetryClient();
|
|
167
218
|
this.metrics = deps.metrics ?? new SequencerPublisherMetrics(telemetry, 'SequencerPublisher');
|
|
168
219
|
this.tracer = telemetry.getTracer('SequencerPublisher');
|
|
169
220
|
this.l1TxUtils = deps.l1TxUtils;
|
|
221
|
+
this.getNextPublisher = deps.getNextPublisher;
|
|
170
222
|
|
|
171
223
|
this.rollupContract = deps.rollupContract;
|
|
172
224
|
|
|
@@ -178,8 +230,6 @@ export class SequencerPublisher {
|
|
|
178
230
|
const newSlashingProposer = await this.rollupContract.getSlashingProposer();
|
|
179
231
|
this.slashingProposerContract = newSlashingProposer;
|
|
180
232
|
});
|
|
181
|
-
this.slashFactoryContract = deps.slashFactoryContract;
|
|
182
|
-
|
|
183
233
|
// Initialize L1 fee analyzer for fisherman mode
|
|
184
234
|
if (config.fishermanMode) {
|
|
185
235
|
this.l1FeeAnalyzer = new L1FeeAnalyzer(
|
|
@@ -188,12 +238,52 @@ export class SequencerPublisher {
|
|
|
188
238
|
createLogger('sequencer:publisher:fee-analyzer'),
|
|
189
239
|
);
|
|
190
240
|
}
|
|
241
|
+
|
|
242
|
+
// Initialize fee asset price oracle
|
|
243
|
+
this.feeAssetPriceOracle = new FeeAssetPriceOracle(
|
|
244
|
+
this.l1TxUtils.client,
|
|
245
|
+
this.rollupContract,
|
|
246
|
+
createLogger('sequencer:publisher:price-oracle'),
|
|
247
|
+
);
|
|
248
|
+
|
|
249
|
+
// Initialize failed L1 tx store (optional, for test networks)
|
|
250
|
+
this.failedTxStore = createL1TxFailedStore(config.l1TxFailedStore, this.log);
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
/**
|
|
254
|
+
* Backs up a failed L1 transaction to the configured store for debugging.
|
|
255
|
+
* Does nothing if no store is configured.
|
|
256
|
+
*/
|
|
257
|
+
private backupFailedTx(failedTx: Omit<FailedL1Tx, 'timestamp'>): void {
|
|
258
|
+
if (!this.failedTxStore) {
|
|
259
|
+
return;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
const tx: FailedL1Tx = {
|
|
263
|
+
...failedTx,
|
|
264
|
+
timestamp: Date.now(),
|
|
265
|
+
};
|
|
266
|
+
|
|
267
|
+
// Fire and forget - don't block on backup
|
|
268
|
+
void this.failedTxStore
|
|
269
|
+
.then(store => store?.saveFailedTx(tx))
|
|
270
|
+
.catch(err => {
|
|
271
|
+
this.log.warn(`Failed to backup failed L1 tx to store`, err);
|
|
272
|
+
});
|
|
191
273
|
}
|
|
192
274
|
|
|
193
275
|
public getRollupContract(): RollupContract {
|
|
194
276
|
return this.rollupContract;
|
|
195
277
|
}
|
|
196
278
|
|
|
279
|
+
/**
|
|
280
|
+
* Gets the fee asset price modifier from the oracle.
|
|
281
|
+
* Returns 0n if the oracle query fails.
|
|
282
|
+
*/
|
|
283
|
+
public getFeeAssetPriceModifier(): Promise<bigint> {
|
|
284
|
+
return this.feeAssetPriceOracle.computePriceModifier();
|
|
285
|
+
}
|
|
286
|
+
|
|
197
287
|
public getSenderAddress() {
|
|
198
288
|
return this.l1TxUtils.getSenderAddress();
|
|
199
289
|
}
|
|
@@ -218,7 +308,7 @@ export class SequencerPublisher {
|
|
|
218
308
|
}
|
|
219
309
|
|
|
220
310
|
public getCurrentL2Slot(): SlotNumber {
|
|
221
|
-
return this.epochCache.
|
|
311
|
+
return this.epochCache.getSlotNow();
|
|
222
312
|
}
|
|
223
313
|
|
|
224
314
|
/**
|
|
@@ -296,9 +386,10 @@ export class SequencerPublisher {
|
|
|
296
386
|
* - undefined if no valid requests are found OR the tx failed to send.
|
|
297
387
|
*/
|
|
298
388
|
@trackSpan('SequencerPublisher.sendRequests')
|
|
299
|
-
public async sendRequests() {
|
|
389
|
+
public async sendRequests(): Promise<SendRequestsResult | undefined> {
|
|
300
390
|
const requestsToProcess = [...this.requests];
|
|
301
391
|
this.requests = [];
|
|
392
|
+
|
|
302
393
|
if (this.interrupted || requestsToProcess.length === 0) {
|
|
303
394
|
return undefined;
|
|
304
395
|
}
|
|
@@ -331,8 +422,8 @@ export class SequencerPublisher {
|
|
|
331
422
|
// @note - we can only have one blob config per bundle
|
|
332
423
|
// find requests with gas and blob configs
|
|
333
424
|
// See https://github.com/AztecProtocol/aztec-packages/issues/11513
|
|
334
|
-
const gasConfigs =
|
|
335
|
-
const blobConfigs =
|
|
425
|
+
const gasConfigs = validRequests.filter(request => request.gasConfig).map(request => request.gasConfig);
|
|
426
|
+
const blobConfigs = validRequests.filter(request => request.blobConfig).map(request => request.blobConfig);
|
|
336
427
|
|
|
337
428
|
if (blobConfigs.length > 1) {
|
|
338
429
|
throw new Error('Multiple blob configs found');
|
|
@@ -361,19 +452,36 @@ export class SequencerPublisher {
|
|
|
361
452
|
validRequests.sort((a, b) => compareActions(a.action, b.action));
|
|
362
453
|
|
|
363
454
|
try {
|
|
455
|
+
// Capture context for failed tx backup before sending
|
|
456
|
+
const l1BlockNumber = await this.l1TxUtils.getBlockNumber();
|
|
457
|
+
const multicallData = encodeFunctionData({
|
|
458
|
+
abi: multicall3Abi,
|
|
459
|
+
functionName: 'aggregate3',
|
|
460
|
+
args: [
|
|
461
|
+
validRequests.map(r => ({
|
|
462
|
+
target: r.request.to!,
|
|
463
|
+
callData: r.request.data!,
|
|
464
|
+
allowFailure: true,
|
|
465
|
+
})),
|
|
466
|
+
],
|
|
467
|
+
});
|
|
468
|
+
const blobDataHex = blobConfig?.blobs?.map(b => toHex(b)) as Hex[] | undefined;
|
|
469
|
+
|
|
470
|
+
const txContext = { multicallData, blobData: blobDataHex, l1BlockNumber };
|
|
471
|
+
|
|
364
472
|
this.log.debug('Forwarding transactions', {
|
|
365
473
|
validRequests: validRequests.map(request => request.action),
|
|
366
474
|
txConfig,
|
|
367
475
|
});
|
|
368
|
-
const result = await
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
476
|
+
const result = await this.forwardWithPublisherRotation(validRequests, txConfig, blobConfig);
|
|
477
|
+
if (result === undefined) {
|
|
478
|
+
return undefined;
|
|
479
|
+
}
|
|
480
|
+
const { successfulActions = [], failedActions = [] } = this.callbackBundledTransactions(
|
|
481
|
+
validRequests,
|
|
482
|
+
result,
|
|
483
|
+
txContext,
|
|
375
484
|
);
|
|
376
|
-
const { successfulActions = [], failedActions = [] } = this.callbackBundledTransactions(validRequests, result);
|
|
377
485
|
return { result, expiredActions, sentActions: validActions, successfulActions, failedActions };
|
|
378
486
|
} catch (err) {
|
|
379
487
|
const viemError = formatViemError(err);
|
|
@@ -391,16 +499,127 @@ export class SequencerPublisher {
|
|
|
391
499
|
}
|
|
392
500
|
}
|
|
393
501
|
|
|
502
|
+
/**
|
|
503
|
+
* Forwards transactions via Multicall3, rotating to the next available publisher if a send
|
|
504
|
+
* failure occurs (i.e. the tx never reached the chain).
|
|
505
|
+
* On-chain reverts and simulation errors are returned as-is without rotation.
|
|
506
|
+
*/
|
|
507
|
+
private async forwardWithPublisherRotation(
|
|
508
|
+
validRequests: RequestWithExpiry[],
|
|
509
|
+
txConfig: RequestWithExpiry['gasConfig'],
|
|
510
|
+
blobConfig: L1BlobInputs | undefined,
|
|
511
|
+
) {
|
|
512
|
+
const triedAddresses: EthAddress[] = [];
|
|
513
|
+
let currentPublisher = this.l1TxUtils;
|
|
514
|
+
|
|
515
|
+
while (true) {
|
|
516
|
+
triedAddresses.push(currentPublisher.getSenderAddress());
|
|
517
|
+
try {
|
|
518
|
+
const result = await Multicall3.forward(
|
|
519
|
+
validRequests.map(r => r.request),
|
|
520
|
+
currentPublisher,
|
|
521
|
+
txConfig,
|
|
522
|
+
blobConfig,
|
|
523
|
+
this.rollupContract.address,
|
|
524
|
+
this.log,
|
|
525
|
+
);
|
|
526
|
+
this.l1TxUtils = currentPublisher;
|
|
527
|
+
return result;
|
|
528
|
+
} catch (err) {
|
|
529
|
+
if (err instanceof TimeoutError) {
|
|
530
|
+
throw err;
|
|
531
|
+
}
|
|
532
|
+
const viemError = formatViemError(err);
|
|
533
|
+
if (!this.getNextPublisher) {
|
|
534
|
+
this.log.error('Failed to publish bundled transactions', viemError);
|
|
535
|
+
return undefined;
|
|
536
|
+
}
|
|
537
|
+
this.log.warn(
|
|
538
|
+
`Publisher ${currentPublisher.getSenderAddress()} failed to send, rotating to next publisher`,
|
|
539
|
+
viemError,
|
|
540
|
+
);
|
|
541
|
+
const nextPublisher = await this.getNextPublisher([...triedAddresses]);
|
|
542
|
+
if (!nextPublisher) {
|
|
543
|
+
this.log.error('All available publishers exhausted, failed to publish bundled transactions');
|
|
544
|
+
return undefined;
|
|
545
|
+
}
|
|
546
|
+
currentPublisher = nextPublisher;
|
|
547
|
+
}
|
|
548
|
+
}
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
/*
|
|
552
|
+
* Schedules sending all enqueued requests at (or after) the given timestamp.
|
|
553
|
+
* Uses InterruptibleSleep so it can be cancelled via interrupt().
|
|
554
|
+
* Returns the promise for the L1 response (caller should NOT await this in the work loop).
|
|
555
|
+
*/
|
|
556
|
+
public async sendRequestsAt(submitAfter: Date): Promise<SendRequestsResult | undefined> {
|
|
557
|
+
const ms = submitAfter.getTime() - this.dateProvider.now();
|
|
558
|
+
if (ms > 0) {
|
|
559
|
+
this.log.debug(`Sleeping ${ms}ms before sending requests`, { submitAfter });
|
|
560
|
+
await this.interruptibleSleep.sleep(ms);
|
|
561
|
+
}
|
|
562
|
+
if (this.interrupted) {
|
|
563
|
+
return undefined;
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
// Re-validate enqueued requests after the sleep (state may have changed, e.g. prune or L1 reorg)
|
|
567
|
+
const validRequests: RequestWithExpiry[] = [];
|
|
568
|
+
for (const request of this.requests) {
|
|
569
|
+
if (!request.preCheck) {
|
|
570
|
+
validRequests.push(request);
|
|
571
|
+
continue;
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
try {
|
|
575
|
+
await request.preCheck();
|
|
576
|
+
validRequests.push(request);
|
|
577
|
+
} catch (err) {
|
|
578
|
+
this.log.warn(`Pre-send validation failed for ${request.action}, discarding request`, err);
|
|
579
|
+
}
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
this.requests = validRequests;
|
|
583
|
+
if (this.requests.length === 0) {
|
|
584
|
+
return undefined;
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
return this.sendRequests();
|
|
588
|
+
}
|
|
589
|
+
|
|
394
590
|
private callbackBundledTransactions(
|
|
395
591
|
requests: RequestWithExpiry[],
|
|
396
|
-
result
|
|
592
|
+
result: { receipt: TransactionReceipt; errorMsg?: string } | FormattedViemError | undefined,
|
|
593
|
+
txContext: { multicallData: Hex; blobData?: Hex[]; l1BlockNumber: bigint },
|
|
397
594
|
) {
|
|
398
595
|
const actionsListStr = requests.map(r => r.action).join(', ');
|
|
399
596
|
if (result instanceof FormattedViemError) {
|
|
400
597
|
this.log.error(`Failed to publish bundled transactions (${actionsListStr})`, result);
|
|
598
|
+
this.backupFailedTx({
|
|
599
|
+
id: keccak256(txContext.multicallData),
|
|
600
|
+
failureType: 'send-error',
|
|
601
|
+
request: { to: MULTI_CALL_3_ADDRESS, data: txContext.multicallData },
|
|
602
|
+
blobData: txContext.blobData,
|
|
603
|
+
l1BlockNumber: txContext.l1BlockNumber.toString(),
|
|
604
|
+
error: { message: result.message, name: result.name },
|
|
605
|
+
context: {
|
|
606
|
+
actions: requests.map(r => r.action),
|
|
607
|
+
requests: requests.map(r => ({ action: r.action, to: r.request.to! as Hex, data: r.request.data! })),
|
|
608
|
+
sender: this.getSenderAddress().toString(),
|
|
609
|
+
},
|
|
610
|
+
});
|
|
401
611
|
return { failedActions: requests.map(r => r.action) };
|
|
402
612
|
} else {
|
|
403
|
-
this.log.verbose(`Published bundled transactions (${actionsListStr})`, {
|
|
613
|
+
this.log.verbose(`Published bundled transactions (${actionsListStr})`, {
|
|
614
|
+
result,
|
|
615
|
+
requests: requests.map(r => ({
|
|
616
|
+
...r,
|
|
617
|
+
// Avoid logging large blob data
|
|
618
|
+
blobConfig: r.blobConfig
|
|
619
|
+
? { ...r.blobConfig, blobs: r.blobConfig.blobs.map(b => ({ size: trimmedBytesLength(b) })) }
|
|
620
|
+
: undefined,
|
|
621
|
+
})),
|
|
622
|
+
});
|
|
404
623
|
const successfulActions: Action[] = [];
|
|
405
624
|
const failedActions: Action[] = [];
|
|
406
625
|
for (const request of requests) {
|
|
@@ -410,26 +629,59 @@ export class SequencerPublisher {
|
|
|
410
629
|
failedActions.push(request.action);
|
|
411
630
|
}
|
|
412
631
|
}
|
|
632
|
+
// Single backup for the whole reverted tx
|
|
633
|
+
if (failedActions.length > 0 && result?.receipt?.status === 'reverted') {
|
|
634
|
+
this.backupFailedTx({
|
|
635
|
+
id: result.receipt.transactionHash,
|
|
636
|
+
failureType: 'revert',
|
|
637
|
+
request: { to: MULTI_CALL_3_ADDRESS, data: txContext.multicallData },
|
|
638
|
+
blobData: txContext.blobData,
|
|
639
|
+
l1BlockNumber: result.receipt.blockNumber.toString(),
|
|
640
|
+
receipt: {
|
|
641
|
+
transactionHash: result.receipt.transactionHash,
|
|
642
|
+
blockNumber: result.receipt.blockNumber.toString(),
|
|
643
|
+
gasUsed: (result.receipt.gasUsed ?? 0n).toString(),
|
|
644
|
+
status: 'reverted',
|
|
645
|
+
},
|
|
646
|
+
error: { message: result.errorMsg ?? 'Transaction reverted' },
|
|
647
|
+
context: {
|
|
648
|
+
actions: failedActions,
|
|
649
|
+
requests: requests
|
|
650
|
+
.filter(r => failedActions.includes(r.action))
|
|
651
|
+
.map(r => ({ action: r.action, to: r.request.to! as Hex, data: r.request.data! })),
|
|
652
|
+
sender: this.getSenderAddress().toString(),
|
|
653
|
+
},
|
|
654
|
+
});
|
|
655
|
+
}
|
|
413
656
|
return { successfulActions, failedActions };
|
|
414
657
|
}
|
|
415
658
|
}
|
|
416
659
|
|
|
417
660
|
/**
|
|
418
|
-
* @notice Will call `
|
|
661
|
+
* @notice Will call `canProposeAt` to make sure that it is possible to propose
|
|
419
662
|
* @param tipArchive - The archive to check
|
|
420
663
|
* @returns The slot and block number if it is possible to propose, undefined otherwise
|
|
421
664
|
*/
|
|
422
|
-
public
|
|
665
|
+
public canProposeAt(
|
|
423
666
|
tipArchive: Fr,
|
|
424
667
|
msgSender: EthAddress,
|
|
425
|
-
opts: {
|
|
668
|
+
opts: {
|
|
669
|
+
forcePendingCheckpointNumber?: CheckpointNumber;
|
|
670
|
+
forceArchive?: { checkpointNumber: CheckpointNumber; archive: Fr };
|
|
671
|
+
pipelined?: boolean;
|
|
672
|
+
} = {},
|
|
426
673
|
) {
|
|
427
674
|
// TODO: #14291 - should loop through multiple keys to check if any of them can propose
|
|
428
675
|
const ignoredErrors = ['SlotAlreadyInChain', 'InvalidProposer', 'InvalidArchive'];
|
|
429
676
|
|
|
677
|
+
const pipelined = opts.pipelined ?? this.epochCache.isProposerPipeliningEnabled();
|
|
678
|
+
const slotOffset = pipelined ? this.aztecSlotDuration : 0n;
|
|
679
|
+
const nextL1SlotTs = this.getNextL1SlotTimestamp() + slotOffset;
|
|
680
|
+
|
|
430
681
|
return this.rollupContract
|
|
431
|
-
.
|
|
682
|
+
.canProposeAt(tipArchive.toBuffer(), msgSender.toString(), nextL1SlotTs, {
|
|
432
683
|
forcePendingCheckpointNumber: opts.forcePendingCheckpointNumber,
|
|
684
|
+
forceArchive: opts.forceArchive,
|
|
433
685
|
})
|
|
434
686
|
.catch(err => {
|
|
435
687
|
if (err instanceof FormattedViemError && ignoredErrors.find(e => err.message.includes(e))) {
|
|
@@ -442,6 +694,7 @@ export class SequencerPublisher {
|
|
|
442
694
|
return undefined;
|
|
443
695
|
});
|
|
444
696
|
}
|
|
697
|
+
|
|
445
698
|
/**
|
|
446
699
|
* @notice Will simulate `validateHeader` to make sure that the block header is valid
|
|
447
700
|
* @dev This is a convenience function that can be used by the sequencer to validate a "partial" header.
|
|
@@ -465,7 +718,7 @@ export class SequencerPublisher {
|
|
|
465
718
|
flags,
|
|
466
719
|
] as const;
|
|
467
720
|
|
|
468
|
-
const ts =
|
|
721
|
+
const ts = this.getSimulationTimestamp(header.slotNumber);
|
|
469
722
|
const stateOverrides = await this.rollupContract.makePendingCheckpointNumberOverride(
|
|
470
723
|
opts?.forcePendingCheckpointNumber,
|
|
471
724
|
);
|
|
@@ -488,7 +741,7 @@ export class SequencerPublisher {
|
|
|
488
741
|
data: encodeFunctionData({ abi: RollupAbi, functionName: 'validateHeaderWithAttestations', args }),
|
|
489
742
|
from: MULTI_CALL_3_ADDRESS,
|
|
490
743
|
},
|
|
491
|
-
{ time: ts
|
|
744
|
+
{ time: ts },
|
|
492
745
|
stateOverrides,
|
|
493
746
|
);
|
|
494
747
|
this.log.debug(`Simulated validateHeader`);
|
|
@@ -521,6 +774,8 @@ export class SequencerPublisher {
|
|
|
521
774
|
const request = this.buildInvalidateCheckpointRequest(validationResult);
|
|
522
775
|
this.log.debug(`Simulating invalidate checkpoint ${checkpointNumber}`, { ...logData, request });
|
|
523
776
|
|
|
777
|
+
const l1BlockNumber = await this.l1TxUtils.getBlockNumber();
|
|
778
|
+
|
|
524
779
|
try {
|
|
525
780
|
const { gasUsed } = await this.l1TxUtils.simulate(
|
|
526
781
|
request,
|
|
@@ -539,6 +794,7 @@ export class SequencerPublisher {
|
|
|
539
794
|
gasUsed,
|
|
540
795
|
checkpointNumber,
|
|
541
796
|
forcePendingCheckpointNumber: CheckpointNumber(checkpointNumber - 1),
|
|
797
|
+
lastArchive: validationResult.checkpoint.lastArchive,
|
|
542
798
|
reason,
|
|
543
799
|
};
|
|
544
800
|
} catch (err) {
|
|
@@ -551,8 +807,8 @@ export class SequencerPublisher {
|
|
|
551
807
|
`Simulation for invalidate checkpoint ${checkpointNumber} failed due to checkpoint not being in pending chain`,
|
|
552
808
|
{ ...logData, request, error: viemError.message },
|
|
553
809
|
);
|
|
554
|
-
const
|
|
555
|
-
if (
|
|
810
|
+
const latestProposedCheckpointNumber = await this.rollupContract.getCheckpointNumber();
|
|
811
|
+
if (latestProposedCheckpointNumber < checkpointNumber) {
|
|
556
812
|
this.log.verbose(`Checkpoint ${checkpointNumber} has already been invalidated`, { ...logData });
|
|
557
813
|
return undefined;
|
|
558
814
|
} else {
|
|
@@ -572,6 +828,18 @@ export class SequencerPublisher {
|
|
|
572
828
|
|
|
573
829
|
// Otherwise, throw. We cannot build the next checkpoint if we cannot invalidate the previous one.
|
|
574
830
|
this.log.error(`Simulation for invalidate checkpoint ${checkpointNumber} failed`, viemError, logData);
|
|
831
|
+
this.backupFailedTx({
|
|
832
|
+
id: keccak256(request.data!),
|
|
833
|
+
failureType: 'simulation',
|
|
834
|
+
request: { to: request.to!, data: request.data!, value: request.value?.toString() },
|
|
835
|
+
l1BlockNumber: l1BlockNumber.toString(),
|
|
836
|
+
error: { message: viemError.message, name: viemError.name },
|
|
837
|
+
context: {
|
|
838
|
+
actions: [`invalidate-${reason}`],
|
|
839
|
+
checkpointNumber,
|
|
840
|
+
sender: this.getSenderAddress().toString(),
|
|
841
|
+
},
|
|
842
|
+
});
|
|
575
843
|
throw new Error(`Failed to simulate invalidate checkpoint ${checkpointNumber}`, { cause: viemError });
|
|
576
844
|
}
|
|
577
845
|
}
|
|
@@ -614,27 +882,13 @@ export class SequencerPublisher {
|
|
|
614
882
|
checkpoint: Checkpoint,
|
|
615
883
|
attestationsAndSigners: CommitteeAttestationsAndSigners,
|
|
616
884
|
attestationsAndSignersSignature: Signature,
|
|
617
|
-
options: {
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
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
|
-
|
|
885
|
+
options: {
|
|
886
|
+
forcePendingCheckpointNumber?: CheckpointNumber;
|
|
887
|
+
forceProposedFeeHeader?: { checkpointNumber: CheckpointNumber; feeHeader: FeeHeader };
|
|
888
|
+
},
|
|
889
|
+
): Promise<void> {
|
|
636
890
|
const blobFields = checkpoint.toBlobFields();
|
|
637
|
-
const blobs = getBlobsPerL1Block(blobFields);
|
|
891
|
+
const blobs = await getBlobsPerL1Block(blobFields);
|
|
638
892
|
const blobInput = getPrefixedEthBlobCommitments(blobs);
|
|
639
893
|
|
|
640
894
|
const args = [
|
|
@@ -642,7 +896,7 @@ export class SequencerPublisher {
|
|
|
642
896
|
header: checkpoint.header.toViem(),
|
|
643
897
|
archive: toHex(checkpoint.archive.root.toBuffer()),
|
|
644
898
|
oracleInput: {
|
|
645
|
-
feeAssetPriceModifier:
|
|
899
|
+
feeAssetPriceModifier: checkpoint.feeAssetPriceModifier,
|
|
646
900
|
},
|
|
647
901
|
},
|
|
648
902
|
attestationsAndSigners.getPackedAttestations(),
|
|
@@ -651,13 +905,11 @@ export class SequencerPublisher {
|
|
|
651
905
|
blobInput,
|
|
652
906
|
] as const;
|
|
653
907
|
|
|
654
|
-
await this.simulateProposeTx(args,
|
|
655
|
-
return ts;
|
|
908
|
+
await this.simulateProposeTx(args, options);
|
|
656
909
|
}
|
|
657
910
|
|
|
658
911
|
private async enqueueCastSignalHelper(
|
|
659
912
|
slotNumber: SlotNumber,
|
|
660
|
-
timestamp: bigint,
|
|
661
913
|
signalType: GovernanceSignalAction,
|
|
662
914
|
payload: EthAddress,
|
|
663
915
|
base: IEmpireBase,
|
|
@@ -691,6 +943,32 @@ export class SequencerPublisher {
|
|
|
691
943
|
return false;
|
|
692
944
|
}
|
|
693
945
|
|
|
946
|
+
// Check if payload was already submitted to governance
|
|
947
|
+
const cacheKey = payload.toString();
|
|
948
|
+
if (!this.payloadProposedCache.has(cacheKey)) {
|
|
949
|
+
try {
|
|
950
|
+
const l1StartBlock = await this.rollupContract.getL1StartBlock();
|
|
951
|
+
const proposed = await retry(
|
|
952
|
+
() => base.hasPayloadBeenProposed(payload.toString(), l1StartBlock),
|
|
953
|
+
'Check if payload was proposed',
|
|
954
|
+
makeBackoff([0, 1, 2]),
|
|
955
|
+
this.log,
|
|
956
|
+
true,
|
|
957
|
+
);
|
|
958
|
+
if (proposed) {
|
|
959
|
+
this.payloadProposedCache.add(cacheKey);
|
|
960
|
+
}
|
|
961
|
+
} catch (err) {
|
|
962
|
+
this.log.warn(`Failed to check if payload ${payload} was proposed after retries, skipping signal`, err);
|
|
963
|
+
return false;
|
|
964
|
+
}
|
|
965
|
+
}
|
|
966
|
+
|
|
967
|
+
if (this.payloadProposedCache.has(cacheKey)) {
|
|
968
|
+
this.log.info(`Payload ${payload} was already proposed to governance, stopping signals`);
|
|
969
|
+
return false;
|
|
970
|
+
}
|
|
971
|
+
|
|
694
972
|
const cachedLastVote = this.lastActions[signalType];
|
|
695
973
|
this.lastActions[signalType] = slotNumber;
|
|
696
974
|
const action = signalType;
|
|
@@ -709,11 +987,30 @@ export class SequencerPublisher {
|
|
|
709
987
|
lastValidL2Slot: slotNumber,
|
|
710
988
|
});
|
|
711
989
|
|
|
990
|
+
const l1BlockNumber = await this.l1TxUtils.getBlockNumber();
|
|
991
|
+
const timestamp = this.getSimulationTimestamp(slotNumber);
|
|
992
|
+
|
|
712
993
|
try {
|
|
713
994
|
await this.l1TxUtils.simulate(request, { time: timestamp }, [], mergeAbis([request.abi ?? [], ErrorsAbi]));
|
|
714
995
|
this.log.debug(`Simulation for ${action} at slot ${slotNumber} succeeded`, { request });
|
|
715
996
|
} catch (err) {
|
|
716
|
-
|
|
997
|
+
const viemError = formatViemError(err);
|
|
998
|
+
this.log.error(`Failed simulation for ${action} at slot ${slotNumber} (enqueuing the action anyway)`, viemError, {
|
|
999
|
+
simulationTimestamp: timestamp,
|
|
1000
|
+
l1BlockNumber,
|
|
1001
|
+
});
|
|
1002
|
+
this.backupFailedTx({
|
|
1003
|
+
id: keccak256(request.data!),
|
|
1004
|
+
failureType: 'simulation',
|
|
1005
|
+
request: { to: request.to!, data: request.data!, value: request.value?.toString() },
|
|
1006
|
+
l1BlockNumber: l1BlockNumber.toString(),
|
|
1007
|
+
error: { message: viemError.message, name: viemError.name },
|
|
1008
|
+
context: {
|
|
1009
|
+
actions: [action],
|
|
1010
|
+
slot: slotNumber,
|
|
1011
|
+
sender: this.getSenderAddress().toString(),
|
|
1012
|
+
},
|
|
1013
|
+
});
|
|
717
1014
|
// Yes, we enqueue the request anyway, in case there was a bug with the simulation itself
|
|
718
1015
|
}
|
|
719
1016
|
|
|
@@ -764,19 +1061,16 @@ export class SequencerPublisher {
|
|
|
764
1061
|
/**
|
|
765
1062
|
* Enqueues a governance castSignal transaction to cast a signal for a given slot number.
|
|
766
1063
|
* @param slotNumber - The slot number to cast a signal for.
|
|
767
|
-
* @param timestamp - The timestamp of the slot to cast a signal for.
|
|
768
1064
|
* @returns True if the signal was successfully enqueued, false otherwise.
|
|
769
1065
|
*/
|
|
770
1066
|
public enqueueGovernanceCastSignal(
|
|
771
1067
|
governancePayload: EthAddress,
|
|
772
1068
|
slotNumber: SlotNumber,
|
|
773
|
-
timestamp: bigint,
|
|
774
1069
|
signerAddress: EthAddress,
|
|
775
1070
|
signer: (msg: TypedDataDefinition) => Promise<`0x${string}`>,
|
|
776
1071
|
): Promise<boolean> {
|
|
777
1072
|
return this.enqueueCastSignalHelper(
|
|
778
1073
|
slotNumber,
|
|
779
|
-
timestamp,
|
|
780
1074
|
'governance-signal',
|
|
781
1075
|
governancePayload,
|
|
782
1076
|
this.govProposerContract,
|
|
@@ -789,7 +1083,6 @@ export class SequencerPublisher {
|
|
|
789
1083
|
public async enqueueSlashingActions(
|
|
790
1084
|
actions: ProposerSlashAction[],
|
|
791
1085
|
slotNumber: SlotNumber,
|
|
792
|
-
timestamp: bigint,
|
|
793
1086
|
signerAddress: EthAddress,
|
|
794
1087
|
signer: (msg: TypedDataDefinition) => Promise<`0x${string}`>,
|
|
795
1088
|
): Promise<boolean> {
|
|
@@ -800,58 +1093,6 @@ export class SequencerPublisher {
|
|
|
800
1093
|
|
|
801
1094
|
for (const action of actions) {
|
|
802
1095
|
switch (action.type) {
|
|
803
|
-
case 'vote-empire-payload': {
|
|
804
|
-
if (this.slashingProposerContract?.type !== 'empire') {
|
|
805
|
-
this.log.error('Cannot vote for empire payload on non-empire slashing contract');
|
|
806
|
-
break;
|
|
807
|
-
}
|
|
808
|
-
this.log.debug(`Enqueuing slashing vote for payload ${action.payload} at slot ${slotNumber}`, {
|
|
809
|
-
signerAddress,
|
|
810
|
-
});
|
|
811
|
-
await this.enqueueCastSignalHelper(
|
|
812
|
-
slotNumber,
|
|
813
|
-
timestamp,
|
|
814
|
-
'empire-slashing-signal',
|
|
815
|
-
action.payload,
|
|
816
|
-
this.slashingProposerContract,
|
|
817
|
-
signerAddress,
|
|
818
|
-
signer,
|
|
819
|
-
);
|
|
820
|
-
break;
|
|
821
|
-
}
|
|
822
|
-
|
|
823
|
-
case 'create-empire-payload': {
|
|
824
|
-
this.log.debug(`Enqueuing slashing create payload at slot ${slotNumber}`, { slotNumber, signerAddress });
|
|
825
|
-
const request = this.slashFactoryContract.buildCreatePayloadRequest(action.data);
|
|
826
|
-
await this.simulateAndEnqueueRequest(
|
|
827
|
-
'create-empire-payload',
|
|
828
|
-
request,
|
|
829
|
-
(receipt: TransactionReceipt) =>
|
|
830
|
-
!!this.slashFactoryContract.tryExtractSlashPayloadCreatedEvent(receipt.logs),
|
|
831
|
-
slotNumber,
|
|
832
|
-
timestamp,
|
|
833
|
-
);
|
|
834
|
-
break;
|
|
835
|
-
}
|
|
836
|
-
|
|
837
|
-
case 'execute-empire-payload': {
|
|
838
|
-
this.log.debug(`Enqueuing slashing execute payload at slot ${slotNumber}`, { slotNumber, signerAddress });
|
|
839
|
-
if (this.slashingProposerContract?.type !== 'empire') {
|
|
840
|
-
this.log.error('Cannot execute slashing payload on non-empire slashing contract');
|
|
841
|
-
return false;
|
|
842
|
-
}
|
|
843
|
-
const empireSlashingProposer = this.slashingProposerContract as EmpireSlashingProposerContract;
|
|
844
|
-
const request = empireSlashingProposer.buildExecuteRoundRequest(action.round);
|
|
845
|
-
await this.simulateAndEnqueueRequest(
|
|
846
|
-
'execute-empire-payload',
|
|
847
|
-
request,
|
|
848
|
-
(receipt: TransactionReceipt) => !!empireSlashingProposer.tryExtractPayloadSubmittedEvent(receipt.logs),
|
|
849
|
-
slotNumber,
|
|
850
|
-
timestamp,
|
|
851
|
-
);
|
|
852
|
-
break;
|
|
853
|
-
}
|
|
854
|
-
|
|
855
1096
|
case 'vote-offenses': {
|
|
856
1097
|
this.log.debug(`Enqueuing slashing vote for ${action.votes.length} votes at slot ${slotNumber}`, {
|
|
857
1098
|
slotNumber,
|
|
@@ -859,19 +1100,17 @@ export class SequencerPublisher {
|
|
|
859
1100
|
votesCount: action.votes.length,
|
|
860
1101
|
signerAddress,
|
|
861
1102
|
});
|
|
862
|
-
if (this.slashingProposerContract
|
|
863
|
-
this.log.error('
|
|
1103
|
+
if (!this.slashingProposerContract) {
|
|
1104
|
+
this.log.error('No slashing proposer contract available');
|
|
864
1105
|
return false;
|
|
865
1106
|
}
|
|
866
|
-
const tallySlashingProposer = this.slashingProposerContract as TallySlashingProposerContract;
|
|
867
1107
|
const votes = bufferToHex(encodeSlashConsensusVotes(action.votes));
|
|
868
|
-
const request = await
|
|
1108
|
+
const request = await this.slashingProposerContract.buildVoteRequestFromSigner(votes, slotNumber, signer);
|
|
869
1109
|
await this.simulateAndEnqueueRequest(
|
|
870
1110
|
'vote-offenses',
|
|
871
1111
|
request,
|
|
872
|
-
(receipt: TransactionReceipt) => !!
|
|
1112
|
+
(receipt: TransactionReceipt) => !!this.slashingProposerContract!.tryExtractVoteCastEvent(receipt.logs),
|
|
873
1113
|
slotNumber,
|
|
874
|
-
timestamp,
|
|
875
1114
|
);
|
|
876
1115
|
break;
|
|
877
1116
|
}
|
|
@@ -882,18 +1121,20 @@ export class SequencerPublisher {
|
|
|
882
1121
|
round: action.round,
|
|
883
1122
|
signerAddress,
|
|
884
1123
|
});
|
|
885
|
-
if (this.slashingProposerContract
|
|
886
|
-
this.log.error('
|
|
1124
|
+
if (!this.slashingProposerContract) {
|
|
1125
|
+
this.log.error('No slashing proposer contract available');
|
|
887
1126
|
return false;
|
|
888
1127
|
}
|
|
889
|
-
const
|
|
890
|
-
|
|
1128
|
+
const executeRequest = this.slashingProposerContract.buildExecuteRoundRequest(
|
|
1129
|
+
action.round,
|
|
1130
|
+
action.committees,
|
|
1131
|
+
);
|
|
891
1132
|
await this.simulateAndEnqueueRequest(
|
|
892
1133
|
'execute-slash',
|
|
893
|
-
|
|
894
|
-
(receipt: TransactionReceipt) =>
|
|
1134
|
+
executeRequest,
|
|
1135
|
+
(receipt: TransactionReceipt) =>
|
|
1136
|
+
!!this.slashingProposerContract!.tryExtractRoundExecutedEvent(receipt.logs),
|
|
895
1137
|
slotNumber,
|
|
896
|
-
timestamp,
|
|
897
1138
|
);
|
|
898
1139
|
break;
|
|
899
1140
|
}
|
|
@@ -913,30 +1154,33 @@ export class SequencerPublisher {
|
|
|
913
1154
|
checkpoint: Checkpoint,
|
|
914
1155
|
attestationsAndSigners: CommitteeAttestationsAndSigners,
|
|
915
1156
|
attestationsAndSignersSignature: Signature,
|
|
916
|
-
opts: {
|
|
1157
|
+
opts: {
|
|
1158
|
+
txTimeoutAt?: Date;
|
|
1159
|
+
forcePendingCheckpointNumber?: CheckpointNumber;
|
|
1160
|
+
forceProposedFeeHeader?: { checkpointNumber: CheckpointNumber; feeHeader: FeeHeader };
|
|
1161
|
+
} = {},
|
|
917
1162
|
): Promise<void> {
|
|
918
1163
|
const checkpointHeader = checkpoint.header;
|
|
919
1164
|
|
|
920
1165
|
const blobFields = checkpoint.toBlobFields();
|
|
921
|
-
const blobs = getBlobsPerL1Block(blobFields);
|
|
1166
|
+
const blobs = await getBlobsPerL1Block(blobFields);
|
|
922
1167
|
|
|
923
|
-
const proposeTxArgs = {
|
|
1168
|
+
const proposeTxArgs: L1ProcessArgs = {
|
|
924
1169
|
header: checkpointHeader,
|
|
925
1170
|
archive: checkpoint.archive.root.toBuffer(),
|
|
926
1171
|
blobs,
|
|
927
1172
|
attestationsAndSigners,
|
|
928
1173
|
attestationsAndSignersSignature,
|
|
1174
|
+
feeAssetPriceModifier: checkpoint.feeAssetPriceModifier,
|
|
929
1175
|
};
|
|
930
1176
|
|
|
931
|
-
let ts: bigint;
|
|
932
|
-
|
|
933
1177
|
try {
|
|
934
1178
|
// @note This will make sure that we are passing the checks for our header ASSUMING that the data is also made available
|
|
935
1179
|
// This means that we can avoid the simulation issues in later checks.
|
|
936
1180
|
// By simulation issue, I mean the fact that the block.timestamp is equal to the last block, not the next, which
|
|
937
1181
|
// make time consistency checks break.
|
|
938
1182
|
// TODO(palla): Check whether we're validating twice, once here and once within addProposeTx, since we call simulateProposeTx in both places.
|
|
939
|
-
|
|
1183
|
+
await this.validateCheckpointForSubmission(
|
|
940
1184
|
checkpoint,
|
|
941
1185
|
attestationsAndSigners,
|
|
942
1186
|
attestationsAndSignersSignature,
|
|
@@ -951,8 +1195,26 @@ export class SequencerPublisher {
|
|
|
951
1195
|
throw err;
|
|
952
1196
|
}
|
|
953
1197
|
|
|
1198
|
+
// Build a pre-check callback that re-validates the checkpoint before L1 submission.
|
|
1199
|
+
// During pipelining this catches stale proposals due to prunes or L1 reorgs that occur during the pipeline sleep.
|
|
1200
|
+
let preCheck = undefined;
|
|
1201
|
+
if (this.epochCache.isProposerPipeliningEnabled()) {
|
|
1202
|
+
preCheck = async () => {
|
|
1203
|
+
this.log.debug(`Re-validating checkpoint ${checkpoint.number} before L1 submission`);
|
|
1204
|
+
await this.validateCheckpointForSubmission(
|
|
1205
|
+
checkpoint,
|
|
1206
|
+
attestationsAndSigners,
|
|
1207
|
+
attestationsAndSignersSignature,
|
|
1208
|
+
{
|
|
1209
|
+
// Forcing pending checkpoint number is included its required if an invalidation request is included
|
|
1210
|
+
forcePendingCheckpointNumber: opts.forcePendingCheckpointNumber,
|
|
1211
|
+
},
|
|
1212
|
+
);
|
|
1213
|
+
};
|
|
1214
|
+
}
|
|
1215
|
+
|
|
954
1216
|
this.log.verbose(`Enqueuing checkpoint propose transaction`, { ...checkpoint.toCheckpointInfo(), ...opts });
|
|
955
|
-
await this.addProposeTx(checkpoint, proposeTxArgs, opts,
|
|
1217
|
+
await this.addProposeTx(checkpoint, proposeTxArgs, opts, preCheck);
|
|
956
1218
|
}
|
|
957
1219
|
|
|
958
1220
|
public enqueueInvalidateCheckpoint(
|
|
@@ -995,8 +1257,8 @@ export class SequencerPublisher {
|
|
|
995
1257
|
request: L1TxRequest,
|
|
996
1258
|
checkSuccess: (receipt: TransactionReceipt) => boolean | undefined,
|
|
997
1259
|
slotNumber: SlotNumber,
|
|
998
|
-
timestamp: bigint,
|
|
999
1260
|
) {
|
|
1261
|
+
const timestamp = this.getSimulationTimestamp(slotNumber);
|
|
1000
1262
|
const logData = { slotNumber, timestamp, gasLimit: undefined as bigint | undefined };
|
|
1001
1263
|
if (this.lastActions[action] && this.lastActions[action] === slotNumber) {
|
|
1002
1264
|
this.log.debug(`Skipping duplicate action ${action} for slot ${slotNumber}`);
|
|
@@ -1008,15 +1270,31 @@ export class SequencerPublisher {
|
|
|
1008
1270
|
|
|
1009
1271
|
this.log.debug(`Simulating ${action} for slot ${slotNumber}`, logData);
|
|
1010
1272
|
|
|
1273
|
+
const l1BlockNumber = await this.l1TxUtils.getBlockNumber();
|
|
1274
|
+
|
|
1011
1275
|
let gasUsed: bigint;
|
|
1012
1276
|
const simulateAbi = mergeAbis([request.abi ?? [], ErrorsAbi]);
|
|
1277
|
+
|
|
1013
1278
|
try {
|
|
1014
|
-
({ gasUsed } = await this.l1TxUtils.simulate(request, { time: timestamp }, [], simulateAbi));
|
|
1279
|
+
({ gasUsed } = await this.l1TxUtils.simulate(request, { time: timestamp }, [], simulateAbi));
|
|
1015
1280
|
this.log.verbose(`Simulation for ${action} succeeded`, { ...logData, request, gasUsed });
|
|
1016
1281
|
} catch (err) {
|
|
1017
1282
|
const viemError = formatViemError(err, simulateAbi);
|
|
1018
1283
|
this.log.error(`Simulation for ${action} at ${slotNumber} failed`, viemError, logData);
|
|
1019
1284
|
|
|
1285
|
+
this.backupFailedTx({
|
|
1286
|
+
id: keccak256(request.data!),
|
|
1287
|
+
failureType: 'simulation',
|
|
1288
|
+
request: { to: request.to!, data: request.data!, value: request.value?.toString() },
|
|
1289
|
+
l1BlockNumber: l1BlockNumber.toString(),
|
|
1290
|
+
error: { message: viemError.message, name: viemError.name },
|
|
1291
|
+
context: {
|
|
1292
|
+
actions: [action],
|
|
1293
|
+
slot: slotNumber,
|
|
1294
|
+
sender: this.getSenderAddress().toString(),
|
|
1295
|
+
},
|
|
1296
|
+
});
|
|
1297
|
+
|
|
1020
1298
|
return false;
|
|
1021
1299
|
}
|
|
1022
1300
|
|
|
@@ -1056,6 +1334,7 @@ export class SequencerPublisher {
|
|
|
1056
1334
|
*/
|
|
1057
1335
|
public interrupt() {
|
|
1058
1336
|
this.interrupted = true;
|
|
1337
|
+
this.interruptibleSleep.interrupt();
|
|
1059
1338
|
this.l1TxUtils.interrupt();
|
|
1060
1339
|
}
|
|
1061
1340
|
|
|
@@ -1067,7 +1346,6 @@ export class SequencerPublisher {
|
|
|
1067
1346
|
|
|
1068
1347
|
private async prepareProposeTx(
|
|
1069
1348
|
encodedData: L1ProcessArgs,
|
|
1070
|
-
timestamp: bigint,
|
|
1071
1349
|
options: { forcePendingCheckpointNumber?: CheckpointNumber },
|
|
1072
1350
|
) {
|
|
1073
1351
|
const kzg = Blob.getViemKzgInstance();
|
|
@@ -1100,9 +1378,27 @@ export class SequencerPublisher {
|
|
|
1100
1378
|
kzg,
|
|
1101
1379
|
},
|
|
1102
1380
|
)
|
|
1103
|
-
.catch(err => {
|
|
1104
|
-
const
|
|
1105
|
-
this.log.error(`Failed to validate blobs`, message, { metaMessages });
|
|
1381
|
+
.catch(async err => {
|
|
1382
|
+
const viemError = formatViemError(err);
|
|
1383
|
+
this.log.error(`Failed to validate blobs`, viemError.message, { metaMessages: viemError.metaMessages });
|
|
1384
|
+
const validateBlobsData = encodeFunctionData({
|
|
1385
|
+
abi: RollupAbi,
|
|
1386
|
+
functionName: 'validateBlobs',
|
|
1387
|
+
args: [blobInput],
|
|
1388
|
+
});
|
|
1389
|
+
const l1BlockNumber = await this.l1TxUtils.getBlockNumber();
|
|
1390
|
+
this.backupFailedTx({
|
|
1391
|
+
id: keccak256(validateBlobsData),
|
|
1392
|
+
failureType: 'simulation',
|
|
1393
|
+
request: { to: this.rollupContract.address as Hex, data: validateBlobsData },
|
|
1394
|
+
blobData: encodedData.blobs.map(b => toHex(b.data)) as Hex[],
|
|
1395
|
+
l1BlockNumber: l1BlockNumber.toString(),
|
|
1396
|
+
error: { message: viemError.message, name: viemError.name },
|
|
1397
|
+
context: {
|
|
1398
|
+
actions: ['validate-blobs'],
|
|
1399
|
+
sender: this.getSenderAddress().toString(),
|
|
1400
|
+
},
|
|
1401
|
+
});
|
|
1106
1402
|
throw new Error('Failed to validate blobs');
|
|
1107
1403
|
});
|
|
1108
1404
|
}
|
|
@@ -1113,8 +1409,7 @@ export class SequencerPublisher {
|
|
|
1113
1409
|
header: encodedData.header.toViem(),
|
|
1114
1410
|
archive: toHex(encodedData.archive),
|
|
1115
1411
|
oracleInput: {
|
|
1116
|
-
|
|
1117
|
-
feeAssetPriceModifier: 0n,
|
|
1412
|
+
feeAssetPriceModifier: encodedData.feeAssetPriceModifier,
|
|
1118
1413
|
},
|
|
1119
1414
|
},
|
|
1120
1415
|
encodedData.attestationsAndSigners.getPackedAttestations(),
|
|
@@ -1123,7 +1418,7 @@ export class SequencerPublisher {
|
|
|
1123
1418
|
blobInput,
|
|
1124
1419
|
] as const;
|
|
1125
1420
|
|
|
1126
|
-
const { rollupData, simulationResult } = await this.simulateProposeTx(args,
|
|
1421
|
+
const { rollupData, simulationResult } = await this.simulateProposeTx(args, options);
|
|
1127
1422
|
|
|
1128
1423
|
return { args, blobEvaluationGas, rollupData, simulationResult };
|
|
1129
1424
|
}
|
|
@@ -1131,7 +1426,6 @@ export class SequencerPublisher {
|
|
|
1131
1426
|
/**
|
|
1132
1427
|
* Simulates the propose tx with eth_simulateV1
|
|
1133
1428
|
* @param args - The propose tx args
|
|
1134
|
-
* @param timestamp - The timestamp to simulate proposal at
|
|
1135
1429
|
* @returns The simulation result
|
|
1136
1430
|
*/
|
|
1137
1431
|
private async simulateProposeTx(
|
|
@@ -1140,7 +1434,7 @@ export class SequencerPublisher {
|
|
|
1140
1434
|
readonly header: ViemHeader;
|
|
1141
1435
|
readonly archive: `0x${string}`;
|
|
1142
1436
|
readonly oracleInput: {
|
|
1143
|
-
readonly feeAssetPriceModifier:
|
|
1437
|
+
readonly feeAssetPriceModifier: bigint;
|
|
1144
1438
|
};
|
|
1145
1439
|
},
|
|
1146
1440
|
ViemCommitteeAttestations,
|
|
@@ -1148,8 +1442,10 @@ export class SequencerPublisher {
|
|
|
1148
1442
|
ViemSignature,
|
|
1149
1443
|
`0x${string}`,
|
|
1150
1444
|
],
|
|
1151
|
-
|
|
1152
|
-
|
|
1445
|
+
options: {
|
|
1446
|
+
forcePendingCheckpointNumber?: CheckpointNumber;
|
|
1447
|
+
forceProposedFeeHeader?: { checkpointNumber: CheckpointNumber; feeHeader: FeeHeader };
|
|
1448
|
+
},
|
|
1153
1449
|
) {
|
|
1154
1450
|
const rollupData = encodeFunctionData({
|
|
1155
1451
|
abi: RollupAbi,
|
|
@@ -1157,13 +1453,23 @@ export class SequencerPublisher {
|
|
|
1157
1453
|
args,
|
|
1158
1454
|
});
|
|
1159
1455
|
|
|
1160
|
-
// override the
|
|
1456
|
+
// override the proposed checkpoint number if requested
|
|
1161
1457
|
const forcePendingCheckpointNumberStateDiff = (
|
|
1162
1458
|
options.forcePendingCheckpointNumber !== undefined
|
|
1163
1459
|
? await this.rollupContract.makePendingCheckpointNumberOverride(options.forcePendingCheckpointNumber)
|
|
1164
1460
|
: []
|
|
1165
1461
|
).flatMap(override => override.stateDiff ?? []);
|
|
1166
1462
|
|
|
1463
|
+
// override the fee header for a specific checkpoint number if requested (used when pipelining)
|
|
1464
|
+
const forceProposedFeeHeaderStateDiff = (
|
|
1465
|
+
options.forceProposedFeeHeader !== undefined
|
|
1466
|
+
? await this.rollupContract.makeFeeHeaderOverride(
|
|
1467
|
+
options.forceProposedFeeHeader.checkpointNumber,
|
|
1468
|
+
options.forceProposedFeeHeader.feeHeader,
|
|
1469
|
+
)
|
|
1470
|
+
: []
|
|
1471
|
+
).flatMap(override => override.stateDiff ?? []);
|
|
1472
|
+
|
|
1167
1473
|
const stateOverrides: StateOverride = [
|
|
1168
1474
|
{
|
|
1169
1475
|
address: this.rollupContract.address,
|
|
@@ -1171,6 +1477,7 @@ export class SequencerPublisher {
|
|
|
1171
1477
|
stateDiff: [
|
|
1172
1478
|
{ slot: toPaddedHex(RollupContract.checkBlobStorageSlot, true), value: toPaddedHex(0n, true) },
|
|
1173
1479
|
...forcePendingCheckpointNumberStateDiff,
|
|
1480
|
+
...forceProposedFeeHeaderStateDiff,
|
|
1174
1481
|
],
|
|
1175
1482
|
},
|
|
1176
1483
|
];
|
|
@@ -1182,6 +1489,9 @@ export class SequencerPublisher {
|
|
|
1182
1489
|
});
|
|
1183
1490
|
}
|
|
1184
1491
|
|
|
1492
|
+
const l1BlockNumber = await this.l1TxUtils.getBlockNumber();
|
|
1493
|
+
const simTs = this.getSimulationTimestamp(SlotNumber.fromBigInt(args[0].header.slotNumber));
|
|
1494
|
+
|
|
1185
1495
|
const simulationResult = await this.l1TxUtils
|
|
1186
1496
|
.simulate(
|
|
1187
1497
|
{
|
|
@@ -1191,8 +1501,7 @@ export class SequencerPublisher {
|
|
|
1191
1501
|
...(this.proposerAddressForSimulation && { from: this.proposerAddressForSimulation.toString() }),
|
|
1192
1502
|
},
|
|
1193
1503
|
{
|
|
1194
|
-
|
|
1195
|
-
time: timestamp + 1n,
|
|
1504
|
+
time: simTs,
|
|
1196
1505
|
// @note reth should have a 30m gas limit per block but throws errors that this tx is beyond limit so we increase here
|
|
1197
1506
|
gasLimit: MAX_L1_TX_LIMIT * 2n,
|
|
1198
1507
|
},
|
|
@@ -1214,7 +1523,19 @@ export class SequencerPublisher {
|
|
|
1214
1523
|
logs: [],
|
|
1215
1524
|
};
|
|
1216
1525
|
}
|
|
1217
|
-
this.log.error(`Failed to simulate propose tx`, viemError);
|
|
1526
|
+
this.log.error(`Failed to simulate propose tx`, viemError, { simulationTimestamp: simTs });
|
|
1527
|
+
this.backupFailedTx({
|
|
1528
|
+
id: keccak256(rollupData),
|
|
1529
|
+
failureType: 'simulation',
|
|
1530
|
+
request: { to: this.rollupContract.address, data: rollupData },
|
|
1531
|
+
l1BlockNumber: l1BlockNumber.toString(),
|
|
1532
|
+
error: { message: viemError.message, name: viemError.name },
|
|
1533
|
+
context: {
|
|
1534
|
+
actions: ['propose'],
|
|
1535
|
+
slot: Number(args[0].header.slotNumber),
|
|
1536
|
+
sender: this.getSenderAddress().toString(),
|
|
1537
|
+
},
|
|
1538
|
+
});
|
|
1218
1539
|
throw err;
|
|
1219
1540
|
});
|
|
1220
1541
|
|
|
@@ -1224,17 +1545,17 @@ export class SequencerPublisher {
|
|
|
1224
1545
|
private async addProposeTx(
|
|
1225
1546
|
checkpoint: Checkpoint,
|
|
1226
1547
|
encodedData: L1ProcessArgs,
|
|
1227
|
-
opts: {
|
|
1228
|
-
|
|
1548
|
+
opts: {
|
|
1549
|
+
txTimeoutAt?: Date;
|
|
1550
|
+
forcePendingCheckpointNumber?: CheckpointNumber;
|
|
1551
|
+
forceProposedFeeHeader?: { checkpointNumber: CheckpointNumber; feeHeader: FeeHeader };
|
|
1552
|
+
} = {},
|
|
1553
|
+
preCheck?: () => Promise<void>,
|
|
1229
1554
|
): Promise<void> {
|
|
1230
1555
|
const slot = checkpoint.header.slotNumber;
|
|
1231
1556
|
const timer = new Timer();
|
|
1232
1557
|
const kzg = Blob.getViemKzgInstance();
|
|
1233
|
-
const { rollupData, simulationResult, blobEvaluationGas } = await this.prepareProposeTx(
|
|
1234
|
-
encodedData,
|
|
1235
|
-
timestamp,
|
|
1236
|
-
opts,
|
|
1237
|
-
);
|
|
1558
|
+
const { rollupData, simulationResult, blobEvaluationGas } = await this.prepareProposeTx(encodedData, opts);
|
|
1238
1559
|
const startBlock = await this.l1TxUtils.getBlockNumber();
|
|
1239
1560
|
const gasLimit = this.l1TxUtils.bumpGasLimit(
|
|
1240
1561
|
BigInt(Math.ceil((Number(simulationResult.gasUsed) * 64) / 63)) +
|
|
@@ -1258,6 +1579,7 @@ export class SequencerPublisher {
|
|
|
1258
1579
|
},
|
|
1259
1580
|
lastValidL2Slot: checkpoint.header.slotNumber,
|
|
1260
1581
|
gasConfig: { ...opts, gasLimit },
|
|
1582
|
+
preCheck,
|
|
1261
1583
|
blobConfig: {
|
|
1262
1584
|
blobs: encodedData.blobs.map(b => b.data),
|
|
1263
1585
|
kzg,
|
|
@@ -1310,4 +1632,17 @@ export class SequencerPublisher {
|
|
|
1310
1632
|
},
|
|
1311
1633
|
});
|
|
1312
1634
|
}
|
|
1635
|
+
|
|
1636
|
+
/** Returns the timestamp of the last L1 slot within a given L2 slot. Used as the simulation timestamp
|
|
1637
|
+
* for eth_simulateV1 calls, since it's guaranteed to be greater than any L1 block produced during the slot. */
|
|
1638
|
+
private getSimulationTimestamp(slot: SlotNumber): bigint {
|
|
1639
|
+
const l1Constants = this.epochCache.getL1Constants();
|
|
1640
|
+
return getLastL1SlotTimestampForL2Slot(slot, l1Constants);
|
|
1641
|
+
}
|
|
1642
|
+
|
|
1643
|
+
/** Returns the timestamp of the next L1 slot boundary after now. */
|
|
1644
|
+
private getNextL1SlotTimestamp(): bigint {
|
|
1645
|
+
const l1Constants = this.epochCache.getL1Constants();
|
|
1646
|
+
return getNextL1SlotTimestamp(this.dateProvider.nowInSeconds(), l1Constants);
|
|
1647
|
+
}
|
|
1313
1648
|
}
|