@aztec/sequencer-client 3.0.0-devnet.2 → 3.0.0-manual.20251030
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/sequencer/sequencer.d.ts +33 -4
- package/dest/sequencer/sequencer.d.ts.map +1 -1
- package/dest/sequencer/sequencer.js +235 -126
- package/dest/sequencer/timetable.d.ts +2 -0
- package/dest/sequencer/timetable.d.ts.map +1 -1
- package/package.json +27 -27
- package/src/sequencer/sequencer.ts +260 -143
- package/src/sequencer/timetable.ts +7 -0
|
@@ -89,6 +89,8 @@ export declare class Sequencer extends Sequencer_base {
|
|
|
89
89
|
private metrics;
|
|
90
90
|
private lastBlockPublished;
|
|
91
91
|
private governanceProposerPayload;
|
|
92
|
+
/** The last slot for which we attempted to vote when sync failed, to prevent duplicate attempts. */
|
|
93
|
+
private lastSlotForVoteWhenSyncFailed;
|
|
92
94
|
/** The maximum number of seconds that the sequencer can be into a slot to transition to a particular state. */
|
|
93
95
|
protected timetable: SequencerTimetable;
|
|
94
96
|
protected enforceTimeTable: boolean;
|
|
@@ -128,8 +130,10 @@ export declare class Sequencer extends Sequencer_base {
|
|
|
128
130
|
* - Submit block
|
|
129
131
|
* - If our block for some reason is not included, revert the state
|
|
130
132
|
*/
|
|
131
|
-
protected doRealWork(): Promise<void>;
|
|
132
133
|
protected work(): Promise<void>;
|
|
134
|
+
/** Tries building a block proposal, and if successful, enqueues it for publishing. */
|
|
135
|
+
private tryBuildBlockAndEnqueuePublish;
|
|
136
|
+
protected safeWork(): Promise<void>;
|
|
133
137
|
/**
|
|
134
138
|
* Sets the sequencer state and checks if we have enough time left in the slot to transition to the new state.
|
|
135
139
|
* @param proposedState - The new state to transition to.
|
|
@@ -165,22 +169,47 @@ export declare class Sequencer extends Sequencer_base {
|
|
|
165
169
|
/**
|
|
166
170
|
* Returns whether all dependencies have caught up.
|
|
167
171
|
* We don't check against the previous block submitted since it may have been reorg'd out.
|
|
168
|
-
* @returns Boolean indicating if our dependencies are synced to the latest block.
|
|
169
172
|
*/
|
|
170
|
-
protected
|
|
173
|
+
protected checkSync(args: {
|
|
174
|
+
ts: bigint;
|
|
175
|
+
slot: bigint;
|
|
176
|
+
}): Promise<{
|
|
171
177
|
block?: L2Block;
|
|
172
178
|
blockNumber: number;
|
|
173
179
|
archive: Fr;
|
|
174
180
|
l1Timestamp: bigint;
|
|
175
181
|
pendingChainValidationStatus: ValidateBlockResult;
|
|
176
182
|
} | undefined>;
|
|
183
|
+
/**
|
|
184
|
+
* Enqueues governance and slashing votes with the publisher. Does not block.
|
|
185
|
+
* @param publisher - The publisher to enqueue votes with
|
|
186
|
+
* @param attestorAddress - The attestor address to use for signing
|
|
187
|
+
* @param slot - The slot number
|
|
188
|
+
* @param timestamp - The timestamp for the votes
|
|
189
|
+
* @param context - Optional context for logging (e.g., block number)
|
|
190
|
+
* @returns A tuple of [governanceEnqueued, slashingEnqueued]
|
|
191
|
+
*/
|
|
192
|
+
protected enqueueGovernanceAndSlashingVotes(publisher: SequencerPublisher, attestorAddress: EthAddress, slot: bigint, timestamp: bigint): [Promise<boolean> | undefined, Promise<boolean> | undefined];
|
|
193
|
+
/**
|
|
194
|
+
* Checks if we are the proposer for the next slot.
|
|
195
|
+
* @returns True if we can propose, and the proposer address (undefined if anyone can propose)
|
|
196
|
+
*/
|
|
197
|
+
protected checkCanPropose(slot: bigint): Promise<[boolean, EthAddress | undefined]>;
|
|
198
|
+
/**
|
|
199
|
+
* Tries to vote on slashing actions and governance when the sync check fails but we're past the max time for initializing a proposal.
|
|
200
|
+
* This allows the sequencer to participate in governance/slashing votes even when it cannot build blocks.
|
|
201
|
+
*/
|
|
202
|
+
protected tryVoteWhenSyncFails(args: {
|
|
203
|
+
slot: bigint;
|
|
204
|
+
ts: bigint;
|
|
205
|
+
}): Promise<void>;
|
|
177
206
|
/**
|
|
178
207
|
* Considers invalidating a block if the pending chain is invalid. Depends on how long the invalid block
|
|
179
208
|
* has been there without being invalidated and whether the sequencer is in the committee or not. We always
|
|
180
209
|
* have the proposer try to invalidate, but if they fail, the sequencers in the committee are expected to try,
|
|
181
210
|
* and if they fail, any sequencer will try as well.
|
|
182
211
|
*/
|
|
183
|
-
protected considerInvalidatingBlock(syncedTo: NonNullable<Awaited<ReturnType<Sequencer['
|
|
212
|
+
protected considerInvalidatingBlock(syncedTo: NonNullable<Awaited<ReturnType<Sequencer['checkSync']>>>, currentSlot: bigint): Promise<void>;
|
|
184
213
|
private getSlotStartBuildTimestamp;
|
|
185
214
|
private getSecondsIntoSlot;
|
|
186
215
|
get aztecSlotDuration(): number;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sequencer.d.ts","sourceRoot":"","sources":["../../src/sequencer/sequencer.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"sequencer.d.ts","sourceRoot":"","sources":["../../src/sequencer/sequencer.ts"],"names":[],"mappings":"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"}
|
|
@@ -68,6 +68,7 @@ export { SequencerState };
|
|
|
68
68
|
metrics;
|
|
69
69
|
lastBlockPublished;
|
|
70
70
|
governanceProposerPayload;
|
|
71
|
+
/** The last slot for which we attempted to vote when sync failed, to prevent duplicate attempts. */ lastSlotForVoteWhenSyncFailed;
|
|
71
72
|
/** The maximum number of seconds that the sequencer can be into a slot to transition to a particular state. */ timetable;
|
|
72
73
|
enforceTimeTable;
|
|
73
74
|
// This shouldn't be here as this gets re-created each time we build/propose a block.
|
|
@@ -143,7 +144,7 @@ export { SequencerState };
|
|
|
143
144
|
/**
|
|
144
145
|
* Starts the sequencer and moves to IDLE state.
|
|
145
146
|
*/ start() {
|
|
146
|
-
this.runningPromise = new RunningPromise(this.
|
|
147
|
+
this.runningPromise = new RunningPromise(this.safeWork.bind(this), this.log, this.pollingIntervalMs);
|
|
147
148
|
this.setState(SequencerState.IDLE, undefined, {
|
|
148
149
|
force: true
|
|
149
150
|
});
|
|
@@ -179,21 +180,28 @@ export { SequencerState };
|
|
|
179
180
|
* - Collect attestations for the block
|
|
180
181
|
* - Submit block
|
|
181
182
|
* - If our block for some reason is not included, revert the state
|
|
182
|
-
*/ async
|
|
183
|
+
*/ async work() {
|
|
183
184
|
this.setState(SequencerState.SYNCHRONIZING, undefined);
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
185
|
+
const { slot, ts, now } = this.epochCache.getEpochAndSlotInNextL1Slot();
|
|
186
|
+
// Check we have not already published a block for this slot (cheapest check)
|
|
187
|
+
if (this.lastBlockPublished && this.lastBlockPublished.header.getSlot() >= slot) {
|
|
188
|
+
this.log.debug(`Cannot propose block at next L2 slot ${slot} since that slot was taken by our own block ${this.lastBlockPublished.number}`);
|
|
189
|
+
return;
|
|
190
|
+
}
|
|
191
|
+
// Check all components are synced to latest as seen by the archiver (queries all subsystems)
|
|
192
|
+
const syncedTo = await this.checkSync({
|
|
193
|
+
ts,
|
|
194
|
+
slot
|
|
195
|
+
});
|
|
187
196
|
if (!syncedTo) {
|
|
197
|
+
await this.tryVoteWhenSyncFails({
|
|
198
|
+
slot,
|
|
199
|
+
ts
|
|
200
|
+
});
|
|
188
201
|
return;
|
|
189
202
|
}
|
|
190
203
|
const chainTipArchive = syncedTo.archive;
|
|
191
204
|
const newBlockNumber = syncedTo.blockNumber + 1;
|
|
192
|
-
const { slot, ts, now } = this.epochCache.getEpochAndSlotInNextL1Slot();
|
|
193
|
-
this.setState(SequencerState.PROPOSER_CHECK, slot);
|
|
194
|
-
// Check that the archiver and dependencies have synced to the previous L1 slot at least
|
|
195
|
-
// TODO(#14766): Archiver reports L1 timestamp based on L1 blocks seen, which means that a missed L1 block will
|
|
196
|
-
// cause the archiver L1 timestamp to fall behind, and cause this sequencer to start processing one L1 slot later.
|
|
197
205
|
const syncLogData = {
|
|
198
206
|
now,
|
|
199
207
|
syncedToL1Ts: syncedTo.l1Timestamp,
|
|
@@ -204,67 +212,35 @@ export { SequencerState };
|
|
|
204
212
|
newBlockNumber,
|
|
205
213
|
isPendingChainValid: pick(syncedTo.pendingChainValidationStatus, 'valid', 'reason', 'invalidIndex')
|
|
206
214
|
};
|
|
207
|
-
|
|
208
|
-
|
|
215
|
+
// Check that we are a proposer for the next slot
|
|
216
|
+
this.setState(SequencerState.PROPOSER_CHECK, slot);
|
|
217
|
+
const [canPropose, proposer] = await this.checkCanPropose(slot);
|
|
218
|
+
// If we are not a proposer, check if we should invalidate a invalid block, and bail
|
|
219
|
+
if (!canPropose) {
|
|
220
|
+
await this.considerInvalidatingBlock(syncedTo, slot);
|
|
209
221
|
return;
|
|
210
222
|
}
|
|
211
|
-
// Check that the slot is not taken by a block already
|
|
223
|
+
// Check that the slot is not taken by a block already (should never happen, since only us can propose for this slot)
|
|
212
224
|
if (syncedTo.block && syncedTo.block.header.getSlot() >= slot) {
|
|
213
|
-
this.log.
|
|
225
|
+
this.log.warn(`Cannot propose block at next L2 slot ${slot} since that slot was taken by block ${syncedTo.blockNumber}`, {
|
|
214
226
|
...syncLogData,
|
|
215
227
|
block: syncedTo.block.header.toInspect()
|
|
216
228
|
});
|
|
217
229
|
return;
|
|
218
230
|
}
|
|
219
|
-
// Or that we haven't published it ourselves
|
|
220
|
-
if (this.lastBlockPublished && this.lastBlockPublished.header.getSlot() >= slot) {
|
|
221
|
-
this.log.debug(`Cannot propose block at next L2 slot ${slot} since that slot was taken by our own block ${this.lastBlockPublished.number}`, {
|
|
222
|
-
...syncLogData,
|
|
223
|
-
block: this.lastBlockPublished.header.toInspect()
|
|
224
|
-
});
|
|
225
|
-
return;
|
|
226
|
-
}
|
|
227
|
-
// Check that we are a proposer for the next slot
|
|
228
|
-
let proposerInNextSlot;
|
|
229
|
-
try {
|
|
230
|
-
proposerInNextSlot = await this.epochCache.getProposerAttesterAddressInNextSlot();
|
|
231
|
-
} catch (e) {
|
|
232
|
-
if (e instanceof NoCommitteeError) {
|
|
233
|
-
this.log.warn(`Cannot propose block ${newBlockNumber} at next L2 slot ${slot} since the committee does not exist on L1`);
|
|
234
|
-
return;
|
|
235
|
-
}
|
|
236
|
-
}
|
|
237
|
-
// If get proposer in next slot is undefined, then the committee is empty and anyone may propose.
|
|
238
|
-
// If the committee is defined and not empty, but none of our validators are the proposer, then stop.
|
|
239
|
-
const validatorAddresses = this.validatorClient.getValidatorAddresses();
|
|
240
|
-
if (proposerInNextSlot !== undefined && !validatorAddresses.some((addr)=>addr.equals(proposerInNextSlot))) {
|
|
241
|
-
this.log.debug(`Cannot propose block ${newBlockNumber} since we are not a proposer`, {
|
|
242
|
-
us: validatorAddresses,
|
|
243
|
-
proposer: proposerInNextSlot,
|
|
244
|
-
...syncLogData
|
|
245
|
-
});
|
|
246
|
-
// If the pending chain is invalid, we may need to invalidate the block if no one else is doing it.
|
|
247
|
-
if (!syncedTo.pendingChainValidationStatus.valid) {
|
|
248
|
-
// We pass i undefined here to get any available publisher.
|
|
249
|
-
const { publisher } = await this.publisherFactory.create(undefined);
|
|
250
|
-
await this.considerInvalidatingBlock(syncedTo, slot, validatorAddresses, publisher);
|
|
251
|
-
}
|
|
252
|
-
return;
|
|
253
|
-
}
|
|
254
|
-
// Check with the rollup if we can indeed propose at the next L2 slot. This check should not fail
|
|
255
|
-
// if all the previous checks are good, but we do it just in case.
|
|
256
|
-
const proposerAddressInNextSlot = proposerInNextSlot ?? EthAddress.ZERO;
|
|
257
231
|
// We now need to get ourselves a publisher.
|
|
258
232
|
// The returned attestor will be the one we provided if we provided one.
|
|
259
233
|
// Otherwise it will be a valid attestor for the returned publisher.
|
|
260
|
-
const { attestorAddress, publisher } = await this.publisherFactory.create(
|
|
234
|
+
const { attestorAddress, publisher } = await this.publisherFactory.create(proposer);
|
|
261
235
|
this.log.verbose(`Created publisher at address ${publisher.getSenderAddress()} for attestor ${attestorAddress}`);
|
|
262
236
|
this.publisher = publisher;
|
|
263
237
|
const coinbase = this.validatorClient.getCoinbaseForAttestor(attestorAddress);
|
|
264
238
|
const feeRecipient = this.validatorClient.getFeeRecipientForAttestor(attestorAddress);
|
|
265
239
|
// Prepare invalidation request if the pending chain is invalid (returns undefined if no need)
|
|
266
240
|
const invalidateBlock = await publisher.simulateInvalidateBlock(syncedTo.pendingChainValidationStatus);
|
|
267
|
-
|
|
241
|
+
// Check with the rollup if we can indeed propose at the next L2 slot. This check should not fail
|
|
242
|
+
// if all the previous checks are good, but we do it just in case.
|
|
243
|
+
const canProposeCheck = await publisher.canProposeAtNextEthBlock(chainTipArchive, proposer ?? EthAddress.ZERO, invalidateBlock);
|
|
268
244
|
if (canProposeCheck === undefined) {
|
|
269
245
|
this.log.warn(`Cannot propose block ${newBlockNumber} at slot ${slot} due to failed rollup contract check`, syncLogData);
|
|
270
246
|
this.emit('proposer-rollup-check-failed', {
|
|
@@ -294,43 +270,45 @@ export { SequencerState };
|
|
|
294
270
|
});
|
|
295
271
|
return;
|
|
296
272
|
}
|
|
297
|
-
this.log.debug(`Can propose block ${newBlockNumber} at slot ${slot}
|
|
298
|
-
...syncLogData
|
|
299
|
-
validatorAddresses
|
|
273
|
+
this.log.debug(`Can propose block ${newBlockNumber} at slot ${slot} as ${proposer}`, {
|
|
274
|
+
...syncLogData
|
|
300
275
|
});
|
|
301
276
|
const newGlobalVariables = await this.globalsBuilder.buildGlobalVariables(newBlockNumber, coinbase, feeRecipient, slot);
|
|
302
|
-
|
|
303
|
-
const
|
|
304
|
-
|
|
305
|
-
this.log.error(`Error enqueuing governance vote`, err, {
|
|
306
|
-
blockNumber: newBlockNumber,
|
|
307
|
-
slot
|
|
308
|
-
});
|
|
309
|
-
return false;
|
|
310
|
-
}) : Promise.resolve(false);
|
|
311
|
-
const enqueueSlashingActionsPromise = this.slasherClient ? this.slasherClient.getProposerActions(slot).then((actions)=>publisher.enqueueSlashingActions(actions, slot, timestamp, attestorAddress, signerFn)).catch((err)=>{
|
|
312
|
-
this.log.error(`Error enqueuing slashing actions`, err, {
|
|
313
|
-
blockNumber: newBlockNumber,
|
|
314
|
-
slot
|
|
315
|
-
});
|
|
316
|
-
return false;
|
|
317
|
-
}) : Promise.resolve(false);
|
|
277
|
+
// Enqueue governance and slashing votes (returns promises that will be awaited later)
|
|
278
|
+
const votesPromises = this.enqueueGovernanceAndSlashingVotes(publisher, attestorAddress, slot, newGlobalVariables.timestamp);
|
|
279
|
+
// Enqueues block invalidation
|
|
318
280
|
if (invalidateBlock && !this.config.skipInvalidateBlockAsProposer) {
|
|
319
281
|
publisher.enqueueInvalidateBlock(invalidateBlock);
|
|
320
282
|
}
|
|
283
|
+
// Actual block building
|
|
321
284
|
this.setState(SequencerState.INITIALIZING_PROPOSAL, slot);
|
|
322
|
-
this.
|
|
285
|
+
const block = await this.tryBuildBlockAndEnqueuePublish(slot, proposer, newBlockNumber, publisher, newGlobalVariables, chainTipArchive, invalidateBlock);
|
|
286
|
+
// Wait until the voting promises have resolved, so all requests are enqueued
|
|
287
|
+
await Promise.all(votesPromises);
|
|
288
|
+
// And send the tx to L1
|
|
289
|
+
const l1Response = await publisher.sendRequests();
|
|
290
|
+
const proposedBlock = l1Response?.successfulActions.find((a)=>a === 'propose');
|
|
291
|
+
if (proposedBlock) {
|
|
292
|
+
this.lastBlockPublished = block;
|
|
293
|
+
this.emit('block-published', {
|
|
294
|
+
blockNumber: newBlockNumber,
|
|
295
|
+
slot: Number(slot)
|
|
296
|
+
});
|
|
297
|
+
await this.metrics.incFilledSlot(publisher.getSenderAddress().toString(), coinbase);
|
|
298
|
+
} else if (block) {
|
|
299
|
+
this.emit('block-publish-failed', l1Response ?? {});
|
|
300
|
+
}
|
|
301
|
+
this.setState(SequencerState.IDLE, undefined);
|
|
302
|
+
}
|
|
303
|
+
/** Tries building a block proposal, and if successful, enqueues it for publishing. */ async tryBuildBlockAndEnqueuePublish(slot, proposer, newBlockNumber, publisher, newGlobalVariables, chainTipArchive, invalidateBlock) {
|
|
323
304
|
this.log.verbose(`Preparing proposal for block ${newBlockNumber} at slot ${slot}`, {
|
|
324
|
-
proposer
|
|
325
|
-
coinbase,
|
|
305
|
+
proposer,
|
|
326
306
|
publisher: publisher.getSenderAddress(),
|
|
327
|
-
feeRecipient,
|
|
328
307
|
globalVariables: newGlobalVariables.toInspect(),
|
|
329
308
|
chainTipArchive,
|
|
330
309
|
blockNumber: newBlockNumber,
|
|
331
310
|
slot
|
|
332
311
|
});
|
|
333
|
-
// If I created a "partial" header here that should make our job much easier.
|
|
334
312
|
const proposalHeader = CheckpointHeader.from({
|
|
335
313
|
...newGlobalVariables,
|
|
336
314
|
timestamp: newGlobalVariables.timestamp,
|
|
@@ -345,7 +323,7 @@ export { SequencerState };
|
|
|
345
323
|
// and also we may need to fetch more if we don't have enough valid txs.
|
|
346
324
|
const pendingTxs = this.p2pClient.iteratePendingTxs();
|
|
347
325
|
try {
|
|
348
|
-
block = await this.buildBlockAndEnqueuePublish(pendingTxs, proposalHeader, newGlobalVariables,
|
|
326
|
+
block = await this.buildBlockAndEnqueuePublish(pendingTxs, proposalHeader, newGlobalVariables, proposer, invalidateBlock, publisher);
|
|
349
327
|
} catch (err) {
|
|
350
328
|
this.emit('block-build-failed', {
|
|
351
329
|
reason: err.message
|
|
@@ -370,27 +348,11 @@ export { SequencerState };
|
|
|
370
348
|
availableTxs: pendingTxCount
|
|
371
349
|
});
|
|
372
350
|
}
|
|
373
|
-
|
|
374
|
-
enqueueGovernanceSignalPromise,
|
|
375
|
-
enqueueSlashingActionsPromise
|
|
376
|
-
]);
|
|
377
|
-
const l1Response = await publisher.sendRequests();
|
|
378
|
-
const proposedBlock = l1Response?.successfulActions.find((a)=>a === 'propose');
|
|
379
|
-
if (proposedBlock) {
|
|
380
|
-
this.lastBlockPublished = block;
|
|
381
|
-
this.emit('block-published', {
|
|
382
|
-
blockNumber: newBlockNumber,
|
|
383
|
-
slot: Number(slot)
|
|
384
|
-
});
|
|
385
|
-
await this.metrics.incFilledSlot(publisher.getSenderAddress().toString(), coinbase);
|
|
386
|
-
} else if (block) {
|
|
387
|
-
this.emit('block-publish-failed', l1Response ?? {});
|
|
388
|
-
}
|
|
389
|
-
this.setState(SequencerState.IDLE, undefined);
|
|
351
|
+
return block;
|
|
390
352
|
}
|
|
391
|
-
async
|
|
353
|
+
async safeWork() {
|
|
392
354
|
try {
|
|
393
|
-
await this.
|
|
355
|
+
await this.work();
|
|
394
356
|
} catch (err) {
|
|
395
357
|
if (err instanceof SequencerTooSlowError) {
|
|
396
358
|
// Log as warn only if we had to abort halfway through the block proposal
|
|
@@ -614,8 +576,20 @@ export { SequencerState };
|
|
|
614
576
|
/**
|
|
615
577
|
* Returns whether all dependencies have caught up.
|
|
616
578
|
* We don't check against the previous block submitted since it may have been reorg'd out.
|
|
617
|
-
|
|
618
|
-
|
|
579
|
+
*/ async checkSync(args) {
|
|
580
|
+
// Check that the archiver and dependencies have synced to the previous L1 slot at least
|
|
581
|
+
// TODO(#14766): Archiver reports L1 timestamp based on L1 blocks seen, which means that a missed L1 block will
|
|
582
|
+
// cause the archiver L1 timestamp to fall behind, and cause this sequencer to start processing one L1 slot later.
|
|
583
|
+
const l1Timestamp = await this.l2BlockSource.getL1Timestamp();
|
|
584
|
+
const { slot, ts } = args;
|
|
585
|
+
if (l1Timestamp === undefined || l1Timestamp + BigInt(this.l1Constants.ethereumSlotDuration) < ts) {
|
|
586
|
+
this.log.debug(`Cannot propose block at next L2 slot ${slot} due to pending sync from L1`, {
|
|
587
|
+
slot,
|
|
588
|
+
ts,
|
|
589
|
+
l1Timestamp
|
|
590
|
+
});
|
|
591
|
+
return undefined;
|
|
592
|
+
}
|
|
619
593
|
const syncedBlocks = await Promise.all([
|
|
620
594
|
this.worldState.status().then(({ syncSummary })=>({
|
|
621
595
|
number: syncSummary.latestBlockNumber,
|
|
@@ -624,39 +598,24 @@ export { SequencerState };
|
|
|
624
598
|
this.l2BlockSource.getL2Tips().then((t)=>t.latest),
|
|
625
599
|
this.p2pClient.getStatus().then((p2p)=>p2p.syncedToL2Block),
|
|
626
600
|
this.l1ToL2MessageSource.getL2Tips().then((t)=>t.latest),
|
|
627
|
-
this.l2BlockSource.getL1Timestamp(),
|
|
628
601
|
this.l2BlockSource.getPendingChainValidationStatus()
|
|
629
602
|
]);
|
|
630
|
-
const [worldState, l2BlockSource, p2p, l1ToL2MessageSource,
|
|
603
|
+
const [worldState, l2BlockSource, p2p, l1ToL2MessageSource, pendingChainValidationStatus] = syncedBlocks;
|
|
631
604
|
// The archiver reports 'undefined' hash for the genesis block
|
|
632
605
|
// because it doesn't have access to world state to compute it (facepalm)
|
|
633
606
|
const result = l2BlockSource.hash === undefined ? worldState.number === 0 && p2p.number === 0 && l1ToL2MessageSource.number === 0 : worldState.hash === l2BlockSource.hash && p2p.hash === l2BlockSource.hash && l1ToL2MessageSource.hash === l2BlockSource.hash;
|
|
634
|
-
const logData = {
|
|
635
|
-
worldState,
|
|
636
|
-
l2BlockSource,
|
|
637
|
-
p2p,
|
|
638
|
-
l1ToL2MessageSource
|
|
639
|
-
};
|
|
640
|
-
this.log.debug(`Sequencer sync check ${result ? 'succeeded' : 'failed'}`, logData);
|
|
641
607
|
if (!result) {
|
|
608
|
+
this.log.debug(`Sequencer sync check failed`, {
|
|
609
|
+
worldState,
|
|
610
|
+
l2BlockSource,
|
|
611
|
+
p2p,
|
|
612
|
+
l1ToL2MessageSource
|
|
613
|
+
});
|
|
642
614
|
return undefined;
|
|
643
615
|
}
|
|
616
|
+
// Special case for genesis state
|
|
644
617
|
const blockNumber = worldState.number;
|
|
645
|
-
if (blockNumber
|
|
646
|
-
const block = await this.l2BlockSource.getBlock(blockNumber);
|
|
647
|
-
if (!block) {
|
|
648
|
-
// this shouldn't really happen because a moment ago we checked that all components were in sync
|
|
649
|
-
this.log.warn(`Failed to get L2 block ${blockNumber} from the archiver with all components in sync`, logData);
|
|
650
|
-
return undefined;
|
|
651
|
-
}
|
|
652
|
-
return {
|
|
653
|
-
block,
|
|
654
|
-
blockNumber: block.number,
|
|
655
|
-
archive: block.archive.root,
|
|
656
|
-
l1Timestamp,
|
|
657
|
-
pendingChainValidationStatus
|
|
658
|
-
};
|
|
659
|
-
} else {
|
|
618
|
+
if (blockNumber < INITIAL_L2_BLOCK_NUM) {
|
|
660
619
|
const archive = new Fr((await this.worldState.getCommitted().getTreeInfo(MerkleTreeId.ARCHIVE)).root);
|
|
661
620
|
return {
|
|
662
621
|
blockNumber: INITIAL_L2_BLOCK_NUM - 1,
|
|
@@ -665,20 +624,170 @@ export { SequencerState };
|
|
|
665
624
|
pendingChainValidationStatus
|
|
666
625
|
};
|
|
667
626
|
}
|
|
627
|
+
const block = await this.l2BlockSource.getBlock(blockNumber);
|
|
628
|
+
if (!block) {
|
|
629
|
+
// this shouldn't really happen because a moment ago we checked that all components were in sync
|
|
630
|
+
this.log.error(`Failed to get L2 block ${blockNumber} from the archiver with all components in sync`);
|
|
631
|
+
return undefined;
|
|
632
|
+
}
|
|
633
|
+
return {
|
|
634
|
+
block,
|
|
635
|
+
blockNumber: block.number,
|
|
636
|
+
archive: block.archive.root,
|
|
637
|
+
l1Timestamp,
|
|
638
|
+
pendingChainValidationStatus
|
|
639
|
+
};
|
|
640
|
+
}
|
|
641
|
+
/**
|
|
642
|
+
* Enqueues governance and slashing votes with the publisher. Does not block.
|
|
643
|
+
* @param publisher - The publisher to enqueue votes with
|
|
644
|
+
* @param attestorAddress - The attestor address to use for signing
|
|
645
|
+
* @param slot - The slot number
|
|
646
|
+
* @param timestamp - The timestamp for the votes
|
|
647
|
+
* @param context - Optional context for logging (e.g., block number)
|
|
648
|
+
* @returns A tuple of [governanceEnqueued, slashingEnqueued]
|
|
649
|
+
*/ enqueueGovernanceAndSlashingVotes(publisher, attestorAddress, slot, timestamp) {
|
|
650
|
+
try {
|
|
651
|
+
const signerFn = (msg)=>this.validatorClient.signWithAddress(attestorAddress, msg).then((s)=>s.toString());
|
|
652
|
+
const enqueueGovernancePromise = this.governanceProposerPayload && !this.governanceProposerPayload.isZero() ? publisher.enqueueGovernanceCastSignal(this.governanceProposerPayload, slot, timestamp, attestorAddress, signerFn).catch((err)=>{
|
|
653
|
+
this.log.error(`Error enqueuing governance vote`, err, {
|
|
654
|
+
slot
|
|
655
|
+
});
|
|
656
|
+
return false;
|
|
657
|
+
}) : undefined;
|
|
658
|
+
const enqueueSlashingPromise = this.slasherClient ? this.slasherClient.getProposerActions(slot).then((actions)=>publisher.enqueueSlashingActions(actions, slot, timestamp, attestorAddress, signerFn)).catch((err)=>{
|
|
659
|
+
this.log.error(`Error enqueuing slashing actions`, err, {
|
|
660
|
+
slot
|
|
661
|
+
});
|
|
662
|
+
return false;
|
|
663
|
+
}) : undefined;
|
|
664
|
+
return [
|
|
665
|
+
enqueueGovernancePromise,
|
|
666
|
+
enqueueSlashingPromise
|
|
667
|
+
];
|
|
668
|
+
} catch (err) {
|
|
669
|
+
this.log.error(`Error enqueueing governance and slashing votes`, err);
|
|
670
|
+
return [
|
|
671
|
+
undefined,
|
|
672
|
+
undefined
|
|
673
|
+
];
|
|
674
|
+
}
|
|
675
|
+
}
|
|
676
|
+
/**
|
|
677
|
+
* Checks if we are the proposer for the next slot.
|
|
678
|
+
* @returns True if we can propose, and the proposer address (undefined if anyone can propose)
|
|
679
|
+
*/ async checkCanPropose(slot) {
|
|
680
|
+
let proposer;
|
|
681
|
+
try {
|
|
682
|
+
proposer = await this.epochCache.getProposerAttesterAddressInSlot(slot);
|
|
683
|
+
} catch (e) {
|
|
684
|
+
if (e instanceof NoCommitteeError) {
|
|
685
|
+
this.log.warn(`Cannot propose at next L2 slot ${slot} since the committee does not exist on L1`);
|
|
686
|
+
return [
|
|
687
|
+
false,
|
|
688
|
+
undefined
|
|
689
|
+
];
|
|
690
|
+
}
|
|
691
|
+
this.log.error(`Error getting proposer for slot ${slot}`, e);
|
|
692
|
+
return [
|
|
693
|
+
false,
|
|
694
|
+
undefined
|
|
695
|
+
];
|
|
696
|
+
}
|
|
697
|
+
// If proposer is undefined, then the committee is empty and anyone may propose
|
|
698
|
+
if (proposer === undefined) {
|
|
699
|
+
return [
|
|
700
|
+
true,
|
|
701
|
+
undefined
|
|
702
|
+
];
|
|
703
|
+
}
|
|
704
|
+
const validatorAddresses = this.validatorClient.getValidatorAddresses();
|
|
705
|
+
const weAreProposer = validatorAddresses.some((addr)=>addr.equals(proposer));
|
|
706
|
+
if (!weAreProposer) {
|
|
707
|
+
this.log.debug(`Cannot propose at slot ${slot} since we are not a proposer`, {
|
|
708
|
+
validatorAddresses,
|
|
709
|
+
proposer
|
|
710
|
+
});
|
|
711
|
+
return [
|
|
712
|
+
false,
|
|
713
|
+
proposer
|
|
714
|
+
];
|
|
715
|
+
}
|
|
716
|
+
return [
|
|
717
|
+
true,
|
|
718
|
+
proposer
|
|
719
|
+
];
|
|
720
|
+
}
|
|
721
|
+
/**
|
|
722
|
+
* Tries to vote on slashing actions and governance when the sync check fails but we're past the max time for initializing a proposal.
|
|
723
|
+
* This allows the sequencer to participate in governance/slashing votes even when it cannot build blocks.
|
|
724
|
+
*/ async tryVoteWhenSyncFails(args) {
|
|
725
|
+
const { slot, ts } = args;
|
|
726
|
+
// Prevent duplicate attempts in the same slot
|
|
727
|
+
if (this.lastSlotForVoteWhenSyncFailed === slot) {
|
|
728
|
+
this.log.debug(`Already attempted to vote in slot ${slot} (skipping)`);
|
|
729
|
+
return;
|
|
730
|
+
}
|
|
731
|
+
// Check if we're past the max time for initializing a proposal
|
|
732
|
+
const secondsIntoSlot = this.getSecondsIntoSlot(slot);
|
|
733
|
+
const maxAllowedTime = this.timetable.getMaxAllowedTime(SequencerState.INITIALIZING_PROPOSAL);
|
|
734
|
+
// If we haven't exceeded the time limit for initializing a proposal, don't proceed with voting
|
|
735
|
+
// We use INITIALIZING_PROPOSAL time limit because if we're past that, we can't build a block anyway
|
|
736
|
+
if (maxAllowedTime === undefined || secondsIntoSlot <= maxAllowedTime) {
|
|
737
|
+
this.log.trace(`Not attempting to vote since there is still for block building`, {
|
|
738
|
+
secondsIntoSlot,
|
|
739
|
+
maxAllowedTime
|
|
740
|
+
});
|
|
741
|
+
return;
|
|
742
|
+
}
|
|
743
|
+
this.log.debug(`Sync for slot ${slot} failed, checking for voting opportunities`, {
|
|
744
|
+
secondsIntoSlot,
|
|
745
|
+
maxAllowedTime
|
|
746
|
+
});
|
|
747
|
+
// Check if we're a proposer or proposal is open
|
|
748
|
+
const [canPropose, proposer] = await this.checkCanPropose(slot);
|
|
749
|
+
if (!canPropose) {
|
|
750
|
+
this.log.debug(`Cannot vote in slot ${slot} since we are not a proposer`, {
|
|
751
|
+
slot,
|
|
752
|
+
proposer
|
|
753
|
+
});
|
|
754
|
+
return;
|
|
755
|
+
}
|
|
756
|
+
// Mark this slot as attempted
|
|
757
|
+
this.lastSlotForVoteWhenSyncFailed = slot;
|
|
758
|
+
// Get a publisher for voting
|
|
759
|
+
const { attestorAddress, publisher } = await this.publisherFactory.create(proposer);
|
|
760
|
+
this.log.debug(`Attempting to vote despite sync failure at slot ${slot}`, {
|
|
761
|
+
attestorAddress,
|
|
762
|
+
slot
|
|
763
|
+
});
|
|
764
|
+
// Enqueue governance and slashing votes using the shared helper method
|
|
765
|
+
const votesPromises = this.enqueueGovernanceAndSlashingVotes(publisher, attestorAddress, slot, ts);
|
|
766
|
+
await Promise.all(votesPromises);
|
|
767
|
+
if (votesPromises.every((p)=>!p)) {
|
|
768
|
+
this.log.debug(`No votes to enqueue for slot ${slot}`);
|
|
769
|
+
return;
|
|
770
|
+
}
|
|
771
|
+
this.log.info(`Voting in slot ${slot} despite sync failure`, {
|
|
772
|
+
slot
|
|
773
|
+
});
|
|
774
|
+
await publisher.sendRequests();
|
|
668
775
|
}
|
|
669
776
|
/**
|
|
670
777
|
* Considers invalidating a block if the pending chain is invalid. Depends on how long the invalid block
|
|
671
778
|
* has been there without being invalidated and whether the sequencer is in the committee or not. We always
|
|
672
779
|
* have the proposer try to invalidate, but if they fail, the sequencers in the committee are expected to try,
|
|
673
780
|
* and if they fail, any sequencer will try as well.
|
|
674
|
-
*/ async considerInvalidatingBlock(syncedTo, currentSlot
|
|
781
|
+
*/ async considerInvalidatingBlock(syncedTo, currentSlot) {
|
|
675
782
|
const { pendingChainValidationStatus, l1Timestamp } = syncedTo;
|
|
676
783
|
if (pendingChainValidationStatus.valid) {
|
|
677
784
|
return;
|
|
678
785
|
}
|
|
786
|
+
const { publisher } = await this.publisherFactory.create(undefined);
|
|
679
787
|
const invalidBlockNumber = pendingChainValidationStatus.block.blockNumber;
|
|
680
788
|
const invalidBlockTimestamp = pendingChainValidationStatus.block.timestamp;
|
|
681
789
|
const timeSinceChainInvalid = this.dateProvider.nowInSeconds() - Number(invalidBlockTimestamp);
|
|
790
|
+
const ourValidatorAddresses = this.validatorClient.getValidatorAddresses();
|
|
682
791
|
const { secondsBeforeInvalidatingBlockAsCommitteeMember, secondsBeforeInvalidatingBlockAsNonCommitteeMember } = this.config;
|
|
683
792
|
const logData = {
|
|
684
793
|
invalidL1Timestamp: invalidBlockTimestamp,
|
|
@@ -724,7 +833,7 @@ export { SequencerState };
|
|
|
724
833
|
}
|
|
725
834
|
_ts_decorate([
|
|
726
835
|
trackSpan('Sequencer.work')
|
|
727
|
-
], Sequencer.prototype, "
|
|
836
|
+
], Sequencer.prototype, "safeWork", null);
|
|
728
837
|
_ts_decorate([
|
|
729
838
|
trackSpan('Sequencer.buildBlockAndEnqueuePublish', (_validTxs, _proposalHeader, newGlobalVariables)=>({
|
|
730
839
|
[Attributes.BLOCK_NUMBER]: newGlobalVariables.blockNumber
|
|
@@ -42,6 +42,8 @@ export declare class SequencerTimetable {
|
|
|
42
42
|
getBlockProposalExecTimeEnd(secondsIntoSlot: number): number;
|
|
43
43
|
private get afterBlockReexecTimeNeeded();
|
|
44
44
|
getValidatorReexecTimeEnd(secondsIntoSlot?: number): number;
|
|
45
|
+
getMaxAllowedTime(state: Extract<SequencerState, SequencerState.STOPPED | SequencerState.IDLE | SequencerState.SYNCHRONIZING>): undefined;
|
|
46
|
+
getMaxAllowedTime(state: Exclude<SequencerState, SequencerState.STOPPED | SequencerState.IDLE | SequencerState.SYNCHRONIZING>): number;
|
|
45
47
|
getMaxAllowedTime(state: SequencerState): number | undefined;
|
|
46
48
|
assertTimeLeft(newState: SequencerState, secondsIntoSlot: number): void;
|
|
47
49
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"timetable.d.ts","sourceRoot":"","sources":["../../src/sequencer/timetable.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AACrD,OAAO,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAM5C,qBAAa,kBAAkB;IA+C3B,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAC;IACzB,OAAO,CAAC,QAAQ,CAAC,GAAG;IA/CtB;;;;OAIG;IACH,SAAgB,kBAAkB,EAAE,MAAM,CAAC;IAE3C;;;;OAIG;IACH,SAAgB,gBAAgB,SAAC;IAEjC,sHAAsH;IACtH,SAAgB,gBAAgB,EAAE,MAAM,CAAsB;IAE9D,uDAAuD;IACvD,SAAgB,gBAAgB,EAAE,MAAM,CAAsB;IAE9D,mGAAmG;IACnG,SAAgB,0BAA0B,EAAE,MAAM,CAAC;IAEnD,mIAAmI;IACnI,SAAgB,mBAAmB,EAAE,MAAM,CAAyB;IAEpE,wCAAwC;IACxC,SAAgB,oBAAoB,EAAE,MAAM,CAAC;IAE7C,kFAAkF;IAClF,SAAgB,iBAAiB,EAAE,MAAM,CAAC;IAE1C,2IAA2I;IAC3I,SAAgB,4BAA4B,EAAE,MAAM,CAAC;IAErD,4DAA4D;IAC5D,SAAgB,OAAO,EAAE,OAAO,CAAC;gBAG/B,IAAI,EAAE;QACJ,oBAAoB,EAAE,MAAM,CAAC;QAC7B,iBAAiB,EAAE,MAAM,CAAC;QAC1B,4BAA4B,EAAE,MAAM,CAAC;QACrC,0BAA0B,CAAC,EAAE,MAAM,CAAC;QACpC,OAAO,EAAE,OAAO,CAAC;KAClB,EACgB,OAAO,CAAC,EAAE,gBAAgB,YAAA,EAC1B,GAAG,uCAAsC;IA+C5D,OAAO,KAAK,yCAAyC,GAEpD;IAEM,2BAA2B,CAAC,eAAe,EAAE,MAAM,GAAG,MAAM;IAenE,OAAO,KAAK,0BAA0B,GAErC;IAEM,yBAAyB,CAAC,eAAe,CAAC,EAAE,MAAM,GAAG,MAAM;IAU3D,iBAAiB,CAAC,KAAK,EAAE,cAAc,GAAG,MAAM,GAAG,SAAS;
|
|
1
|
+
{"version":3,"file":"timetable.d.ts","sourceRoot":"","sources":["../../src/sequencer/timetable.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AACrD,OAAO,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAM5C,qBAAa,kBAAkB;IA+C3B,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAC;IACzB,OAAO,CAAC,QAAQ,CAAC,GAAG;IA/CtB;;;;OAIG;IACH,SAAgB,kBAAkB,EAAE,MAAM,CAAC;IAE3C;;;;OAIG;IACH,SAAgB,gBAAgB,SAAC;IAEjC,sHAAsH;IACtH,SAAgB,gBAAgB,EAAE,MAAM,CAAsB;IAE9D,uDAAuD;IACvD,SAAgB,gBAAgB,EAAE,MAAM,CAAsB;IAE9D,mGAAmG;IACnG,SAAgB,0BAA0B,EAAE,MAAM,CAAC;IAEnD,mIAAmI;IACnI,SAAgB,mBAAmB,EAAE,MAAM,CAAyB;IAEpE,wCAAwC;IACxC,SAAgB,oBAAoB,EAAE,MAAM,CAAC;IAE7C,kFAAkF;IAClF,SAAgB,iBAAiB,EAAE,MAAM,CAAC;IAE1C,2IAA2I;IAC3I,SAAgB,4BAA4B,EAAE,MAAM,CAAC;IAErD,4DAA4D;IAC5D,SAAgB,OAAO,EAAE,OAAO,CAAC;gBAG/B,IAAI,EAAE;QACJ,oBAAoB,EAAE,MAAM,CAAC;QAC7B,iBAAiB,EAAE,MAAM,CAAC;QAC1B,4BAA4B,EAAE,MAAM,CAAC;QACrC,0BAA0B,CAAC,EAAE,MAAM,CAAC;QACpC,OAAO,EAAE,OAAO,CAAC;KAClB,EACgB,OAAO,CAAC,EAAE,gBAAgB,YAAA,EAC1B,GAAG,uCAAsC;IA+C5D,OAAO,KAAK,yCAAyC,GAEpD;IAEM,2BAA2B,CAAC,eAAe,EAAE,MAAM,GAAG,MAAM;IAenE,OAAO,KAAK,0BAA0B,GAErC;IAEM,yBAAyB,CAAC,eAAe,CAAC,EAAE,MAAM,GAAG,MAAM;IAU3D,iBAAiB,CACtB,KAAK,EAAE,OAAO,CAAC,cAAc,EAAE,cAAc,CAAC,OAAO,GAAG,cAAc,CAAC,IAAI,GAAG,cAAc,CAAC,aAAa,CAAC,GAC1G,SAAS;IACL,iBAAiB,CACtB,KAAK,EAAE,OAAO,CAAC,cAAc,EAAE,cAAc,CAAC,OAAO,GAAG,cAAc,CAAC,IAAI,GAAG,cAAc,CAAC,aAAa,CAAC,GAC1G,MAAM;IACF,iBAAiB,CAAC,KAAK,EAAE,cAAc,GAAG,MAAM,GAAG,SAAS;IAwB5D,cAAc,CAAC,QAAQ,EAAE,cAAc,EAAE,eAAe,EAAE,MAAM;CAkBxE"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aztec/sequencer-client",
|
|
3
|
-
"version": "3.0.0-
|
|
3
|
+
"version": "3.0.0-manual.20251030",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"exports": {
|
|
6
6
|
".": "./dest/index.js",
|
|
@@ -26,37 +26,37 @@
|
|
|
26
26
|
"test:integration:run": "NODE_NO_WARNINGS=1 node --experimental-vm-modules $(yarn bin jest) --no-cache --config jest.integration.config.json"
|
|
27
27
|
},
|
|
28
28
|
"dependencies": {
|
|
29
|
-
"@aztec/aztec.js": "3.0.0-
|
|
30
|
-
"@aztec/bb-prover": "3.0.0-
|
|
31
|
-
"@aztec/blob-lib": "3.0.0-
|
|
32
|
-
"@aztec/blob-sink": "3.0.0-
|
|
33
|
-
"@aztec/constants": "3.0.0-
|
|
34
|
-
"@aztec/epoch-cache": "3.0.0-
|
|
35
|
-
"@aztec/ethereum": "3.0.0-
|
|
36
|
-
"@aztec/foundation": "3.0.0-
|
|
37
|
-
"@aztec/l1-artifacts": "3.0.0-
|
|
38
|
-
"@aztec/merkle-tree": "3.0.0-
|
|
39
|
-
"@aztec/node-keystore": "3.0.0-
|
|
40
|
-
"@aztec/noir-acvm_js": "3.0.0-
|
|
41
|
-
"@aztec/noir-contracts.js": "3.0.0-
|
|
42
|
-
"@aztec/noir-protocol-circuits-types": "3.0.0-
|
|
43
|
-
"@aztec/noir-types": "3.0.0-
|
|
44
|
-
"@aztec/p2p": "3.0.0-
|
|
45
|
-
"@aztec/protocol-contracts": "3.0.0-
|
|
46
|
-
"@aztec/prover-client": "3.0.0-
|
|
47
|
-
"@aztec/simulator": "3.0.0-
|
|
48
|
-
"@aztec/slasher": "3.0.0-
|
|
49
|
-
"@aztec/stdlib": "3.0.0-
|
|
50
|
-
"@aztec/telemetry-client": "3.0.0-
|
|
51
|
-
"@aztec/validator-client": "3.0.0-
|
|
52
|
-
"@aztec/world-state": "3.0.0-
|
|
29
|
+
"@aztec/aztec.js": "3.0.0-manual.20251030",
|
|
30
|
+
"@aztec/bb-prover": "3.0.0-manual.20251030",
|
|
31
|
+
"@aztec/blob-lib": "3.0.0-manual.20251030",
|
|
32
|
+
"@aztec/blob-sink": "3.0.0-manual.20251030",
|
|
33
|
+
"@aztec/constants": "3.0.0-manual.20251030",
|
|
34
|
+
"@aztec/epoch-cache": "3.0.0-manual.20251030",
|
|
35
|
+
"@aztec/ethereum": "3.0.0-manual.20251030",
|
|
36
|
+
"@aztec/foundation": "3.0.0-manual.20251030",
|
|
37
|
+
"@aztec/l1-artifacts": "3.0.0-manual.20251030",
|
|
38
|
+
"@aztec/merkle-tree": "3.0.0-manual.20251030",
|
|
39
|
+
"@aztec/node-keystore": "3.0.0-manual.20251030",
|
|
40
|
+
"@aztec/noir-acvm_js": "3.0.0-manual.20251030",
|
|
41
|
+
"@aztec/noir-contracts.js": "3.0.0-manual.20251030",
|
|
42
|
+
"@aztec/noir-protocol-circuits-types": "3.0.0-manual.20251030",
|
|
43
|
+
"@aztec/noir-types": "3.0.0-manual.20251030",
|
|
44
|
+
"@aztec/p2p": "3.0.0-manual.20251030",
|
|
45
|
+
"@aztec/protocol-contracts": "3.0.0-manual.20251030",
|
|
46
|
+
"@aztec/prover-client": "3.0.0-manual.20251030",
|
|
47
|
+
"@aztec/simulator": "3.0.0-manual.20251030",
|
|
48
|
+
"@aztec/slasher": "3.0.0-manual.20251030",
|
|
49
|
+
"@aztec/stdlib": "3.0.0-manual.20251030",
|
|
50
|
+
"@aztec/telemetry-client": "3.0.0-manual.20251030",
|
|
51
|
+
"@aztec/validator-client": "3.0.0-manual.20251030",
|
|
52
|
+
"@aztec/world-state": "3.0.0-manual.20251030",
|
|
53
53
|
"lodash.chunk": "^4.2.0",
|
|
54
54
|
"tslib": "^2.4.0",
|
|
55
55
|
"viem": "npm:@spalladino/viem@2.38.2-eip7594.0"
|
|
56
56
|
},
|
|
57
57
|
"devDependencies": {
|
|
58
|
-
"@aztec/archiver": "3.0.0-
|
|
59
|
-
"@aztec/kv-store": "3.0.0-
|
|
58
|
+
"@aztec/archiver": "3.0.0-manual.20251030",
|
|
59
|
+
"@aztec/kv-store": "3.0.0-manual.20251030",
|
|
60
60
|
"@jest/globals": "^30.0.0",
|
|
61
61
|
"@types/jest": "^30.0.0",
|
|
62
62
|
"@types/lodash.chunk": "^4.2.7",
|
|
@@ -98,6 +98,9 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
|
|
|
98
98
|
|
|
99
99
|
private governanceProposerPayload: EthAddress | undefined;
|
|
100
100
|
|
|
101
|
+
/** The last slot for which we attempted to vote when sync failed, to prevent duplicate attempts. */
|
|
102
|
+
private lastSlotForVoteWhenSyncFailed: bigint | undefined;
|
|
103
|
+
|
|
101
104
|
/** The maximum number of seconds that the sequencer can be into a slot to transition to a particular state. */
|
|
102
105
|
protected timetable!: SequencerTimetable;
|
|
103
106
|
protected enforceTimeTable: boolean = false;
|
|
@@ -214,7 +217,7 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
|
|
|
214
217
|
* Starts the sequencer and moves to IDLE state.
|
|
215
218
|
*/
|
|
216
219
|
public start() {
|
|
217
|
-
this.runningPromise = new RunningPromise(this.
|
|
220
|
+
this.runningPromise = new RunningPromise(this.safeWork.bind(this), this.log, this.pollingIntervalMs);
|
|
218
221
|
this.setState(SequencerState.IDLE, undefined, { force: true });
|
|
219
222
|
this.runningPromise.start();
|
|
220
223
|
this.log.info('Started sequencer');
|
|
@@ -248,27 +251,28 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
|
|
|
248
251
|
* - Submit block
|
|
249
252
|
* - If our block for some reason is not included, revert the state
|
|
250
253
|
*/
|
|
251
|
-
protected async
|
|
254
|
+
protected async work() {
|
|
252
255
|
this.setState(SequencerState.SYNCHRONIZING, undefined);
|
|
256
|
+
const { slot, ts, now } = this.epochCache.getEpochAndSlotInNextL1Slot();
|
|
253
257
|
|
|
254
|
-
// Check
|
|
255
|
-
|
|
258
|
+
// Check we have not already published a block for this slot (cheapest check)
|
|
259
|
+
if (this.lastBlockPublished && this.lastBlockPublished.header.getSlot() >= slot) {
|
|
260
|
+
this.log.debug(
|
|
261
|
+
`Cannot propose block at next L2 slot ${slot} since that slot was taken by our own block ${this.lastBlockPublished.number}`,
|
|
262
|
+
);
|
|
263
|
+
return;
|
|
264
|
+
}
|
|
256
265
|
|
|
257
|
-
//
|
|
266
|
+
// Check all components are synced to latest as seen by the archiver (queries all subsystems)
|
|
267
|
+
const syncedTo = await this.checkSync({ ts, slot });
|
|
258
268
|
if (!syncedTo) {
|
|
269
|
+
await this.tryVoteWhenSyncFails({ slot, ts });
|
|
259
270
|
return;
|
|
260
271
|
}
|
|
261
272
|
|
|
262
273
|
const chainTipArchive = syncedTo.archive;
|
|
263
274
|
const newBlockNumber = syncedTo.blockNumber + 1;
|
|
264
275
|
|
|
265
|
-
const { slot, ts, now } = this.epochCache.getEpochAndSlotInNextL1Slot();
|
|
266
|
-
|
|
267
|
-
this.setState(SequencerState.PROPOSER_CHECK, slot);
|
|
268
|
-
|
|
269
|
-
// Check that the archiver and dependencies have synced to the previous L1 slot at least
|
|
270
|
-
// TODO(#14766): Archiver reports L1 timestamp based on L1 blocks seen, which means that a missed L1 block will
|
|
271
|
-
// cause the archiver L1 timestamp to fall behind, and cause this sequencer to start processing one L1 slot later.
|
|
272
276
|
const syncLogData = {
|
|
273
277
|
now,
|
|
274
278
|
syncedToL1Ts: syncedTo.l1Timestamp,
|
|
@@ -280,74 +284,30 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
|
|
|
280
284
|
isPendingChainValid: pick(syncedTo.pendingChainValidationStatus, 'valid', 'reason', 'invalidIndex'),
|
|
281
285
|
};
|
|
282
286
|
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
287
|
+
// Check that we are a proposer for the next slot
|
|
288
|
+
this.setState(SequencerState.PROPOSER_CHECK, slot);
|
|
289
|
+
const [canPropose, proposer] = await this.checkCanPropose(slot);
|
|
290
|
+
|
|
291
|
+
// If we are not a proposer, check if we should invalidate a invalid block, and bail
|
|
292
|
+
if (!canPropose) {
|
|
293
|
+
await this.considerInvalidatingBlock(syncedTo, slot);
|
|
288
294
|
return;
|
|
289
295
|
}
|
|
290
296
|
|
|
291
|
-
// Check that the slot is not taken by a block already
|
|
297
|
+
// Check that the slot is not taken by a block already (should never happen, since only us can propose for this slot)
|
|
292
298
|
if (syncedTo.block && syncedTo.block.header.getSlot() >= slot) {
|
|
293
|
-
this.log.
|
|
299
|
+
this.log.warn(
|
|
294
300
|
`Cannot propose block at next L2 slot ${slot} since that slot was taken by block ${syncedTo.blockNumber}`,
|
|
295
301
|
{ ...syncLogData, block: syncedTo.block.header.toInspect() },
|
|
296
302
|
);
|
|
297
303
|
return;
|
|
298
304
|
}
|
|
299
305
|
|
|
300
|
-
// Or that we haven't published it ourselves
|
|
301
|
-
if (this.lastBlockPublished && this.lastBlockPublished.header.getSlot() >= slot) {
|
|
302
|
-
this.log.debug(
|
|
303
|
-
`Cannot propose block at next L2 slot ${slot} since that slot was taken by our own block ${this.lastBlockPublished.number}`,
|
|
304
|
-
{ ...syncLogData, block: this.lastBlockPublished.header.toInspect() },
|
|
305
|
-
);
|
|
306
|
-
return;
|
|
307
|
-
}
|
|
308
|
-
|
|
309
|
-
// Check that we are a proposer for the next slot
|
|
310
|
-
let proposerInNextSlot: EthAddress | undefined;
|
|
311
|
-
try {
|
|
312
|
-
proposerInNextSlot = await this.epochCache.getProposerAttesterAddressInNextSlot();
|
|
313
|
-
} catch (e) {
|
|
314
|
-
if (e instanceof NoCommitteeError) {
|
|
315
|
-
this.log.warn(
|
|
316
|
-
`Cannot propose block ${newBlockNumber} at next L2 slot ${slot} since the committee does not exist on L1`,
|
|
317
|
-
);
|
|
318
|
-
return;
|
|
319
|
-
}
|
|
320
|
-
}
|
|
321
|
-
|
|
322
|
-
// If get proposer in next slot is undefined, then the committee is empty and anyone may propose.
|
|
323
|
-
// If the committee is defined and not empty, but none of our validators are the proposer, then stop.
|
|
324
|
-
const validatorAddresses = this.validatorClient!.getValidatorAddresses();
|
|
325
|
-
if (proposerInNextSlot !== undefined && !validatorAddresses.some(addr => addr.equals(proposerInNextSlot))) {
|
|
326
|
-
this.log.debug(`Cannot propose block ${newBlockNumber} since we are not a proposer`, {
|
|
327
|
-
us: validatorAddresses,
|
|
328
|
-
proposer: proposerInNextSlot,
|
|
329
|
-
...syncLogData,
|
|
330
|
-
});
|
|
331
|
-
// If the pending chain is invalid, we may need to invalidate the block if no one else is doing it.
|
|
332
|
-
if (!syncedTo.pendingChainValidationStatus.valid) {
|
|
333
|
-
// We pass i undefined here to get any available publisher.
|
|
334
|
-
const { publisher } = await this.publisherFactory.create(undefined);
|
|
335
|
-
await this.considerInvalidatingBlock(syncedTo, slot, validatorAddresses, publisher);
|
|
336
|
-
}
|
|
337
|
-
return;
|
|
338
|
-
}
|
|
339
|
-
|
|
340
|
-
// Check with the rollup if we can indeed propose at the next L2 slot. This check should not fail
|
|
341
|
-
// if all the previous checks are good, but we do it just in case.
|
|
342
|
-
const proposerAddressInNextSlot = proposerInNextSlot ?? EthAddress.ZERO;
|
|
343
|
-
|
|
344
306
|
// We now need to get ourselves a publisher.
|
|
345
307
|
// The returned attestor will be the one we provided if we provided one.
|
|
346
308
|
// Otherwise it will be a valid attestor for the returned publisher.
|
|
347
|
-
const { attestorAddress, publisher } = await this.publisherFactory.create(
|
|
348
|
-
|
|
309
|
+
const { attestorAddress, publisher } = await this.publisherFactory.create(proposer);
|
|
349
310
|
this.log.verbose(`Created publisher at address ${publisher.getSenderAddress()} for attestor ${attestorAddress}`);
|
|
350
|
-
|
|
351
311
|
this.publisher = publisher;
|
|
352
312
|
|
|
353
313
|
const coinbase = this.validatorClient!.getCoinbaseForAttestor(attestorAddress);
|
|
@@ -355,9 +315,12 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
|
|
|
355
315
|
|
|
356
316
|
// Prepare invalidation request if the pending chain is invalid (returns undefined if no need)
|
|
357
317
|
const invalidateBlock = await publisher.simulateInvalidateBlock(syncedTo.pendingChainValidationStatus);
|
|
318
|
+
|
|
319
|
+
// Check with the rollup if we can indeed propose at the next L2 slot. This check should not fail
|
|
320
|
+
// if all the previous checks are good, but we do it just in case.
|
|
358
321
|
const canProposeCheck = await publisher.canProposeAtNextEthBlock(
|
|
359
322
|
chainTipArchive,
|
|
360
|
-
|
|
323
|
+
proposer ?? EthAddress.ZERO,
|
|
361
324
|
invalidateBlock,
|
|
362
325
|
);
|
|
363
326
|
|
|
@@ -384,10 +347,7 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
|
|
|
384
347
|
return;
|
|
385
348
|
}
|
|
386
349
|
|
|
387
|
-
this.log.debug(
|
|
388
|
-
`Can propose block ${newBlockNumber} at slot ${slot}` + (proposerInNextSlot ? ` as ${proposerInNextSlot}` : ''),
|
|
389
|
-
{ ...syncLogData, validatorAddresses },
|
|
390
|
-
);
|
|
350
|
+
this.log.debug(`Can propose block ${newBlockNumber} at slot ${slot} as ${proposer}`, { ...syncLogData });
|
|
391
351
|
|
|
392
352
|
const newGlobalVariables = await this.globalsBuilder.buildGlobalVariables(
|
|
393
353
|
newBlockNumber,
|
|
@@ -396,49 +356,67 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
|
|
|
396
356
|
slot,
|
|
397
357
|
);
|
|
398
358
|
|
|
399
|
-
|
|
400
|
-
const
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
.enqueueGovernanceCastSignal(this.governanceProposerPayload, slot, timestamp, attestorAddress, signerFn)
|
|
407
|
-
.catch(err => {
|
|
408
|
-
this.log.error(`Error enqueuing governance vote`, err, { blockNumber: newBlockNumber, slot });
|
|
409
|
-
return false;
|
|
410
|
-
})
|
|
411
|
-
: Promise.resolve(false);
|
|
412
|
-
|
|
413
|
-
const enqueueSlashingActionsPromise = this.slasherClient
|
|
414
|
-
? this.slasherClient
|
|
415
|
-
.getProposerActions(slot)
|
|
416
|
-
.then(actions => publisher.enqueueSlashingActions(actions, slot, timestamp, attestorAddress, signerFn))
|
|
417
|
-
.catch(err => {
|
|
418
|
-
this.log.error(`Error enqueuing slashing actions`, err, { blockNumber: newBlockNumber, slot });
|
|
419
|
-
return false;
|
|
420
|
-
})
|
|
421
|
-
: Promise.resolve(false);
|
|
359
|
+
// Enqueue governance and slashing votes (returns promises that will be awaited later)
|
|
360
|
+
const votesPromises = this.enqueueGovernanceAndSlashingVotes(
|
|
361
|
+
publisher,
|
|
362
|
+
attestorAddress,
|
|
363
|
+
slot,
|
|
364
|
+
newGlobalVariables.timestamp,
|
|
365
|
+
);
|
|
422
366
|
|
|
367
|
+
// Enqueues block invalidation
|
|
423
368
|
if (invalidateBlock && !this.config.skipInvalidateBlockAsProposer) {
|
|
424
369
|
publisher.enqueueInvalidateBlock(invalidateBlock);
|
|
425
370
|
}
|
|
426
371
|
|
|
372
|
+
// Actual block building
|
|
427
373
|
this.setState(SequencerState.INITIALIZING_PROPOSAL, slot);
|
|
374
|
+
const block: L2Block | undefined = await this.tryBuildBlockAndEnqueuePublish(
|
|
375
|
+
slot,
|
|
376
|
+
proposer,
|
|
377
|
+
newBlockNumber,
|
|
378
|
+
publisher,
|
|
379
|
+
newGlobalVariables,
|
|
380
|
+
chainTipArchive,
|
|
381
|
+
invalidateBlock,
|
|
382
|
+
);
|
|
383
|
+
|
|
384
|
+
// Wait until the voting promises have resolved, so all requests are enqueued
|
|
385
|
+
await Promise.all(votesPromises);
|
|
428
386
|
|
|
429
|
-
|
|
387
|
+
// And send the tx to L1
|
|
388
|
+
const l1Response = await publisher.sendRequests();
|
|
389
|
+
const proposedBlock = l1Response?.successfulActions.find(a => a === 'propose');
|
|
390
|
+
if (proposedBlock) {
|
|
391
|
+
this.lastBlockPublished = block;
|
|
392
|
+
this.emit('block-published', { blockNumber: newBlockNumber, slot: Number(slot) });
|
|
393
|
+
await this.metrics.incFilledSlot(publisher.getSenderAddress().toString(), coinbase);
|
|
394
|
+
} else if (block) {
|
|
395
|
+
this.emit('block-publish-failed', l1Response ?? {});
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
this.setState(SequencerState.IDLE, undefined);
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
/** Tries building a block proposal, and if successful, enqueues it for publishing. */
|
|
402
|
+
private async tryBuildBlockAndEnqueuePublish(
|
|
403
|
+
slot: bigint,
|
|
404
|
+
proposer: EthAddress | undefined,
|
|
405
|
+
newBlockNumber: number,
|
|
406
|
+
publisher: SequencerPublisher,
|
|
407
|
+
newGlobalVariables: GlobalVariables,
|
|
408
|
+
chainTipArchive: Fr,
|
|
409
|
+
invalidateBlock: InvalidateBlockRequest | undefined,
|
|
410
|
+
) {
|
|
430
411
|
this.log.verbose(`Preparing proposal for block ${newBlockNumber} at slot ${slot}`, {
|
|
431
|
-
proposer
|
|
432
|
-
coinbase,
|
|
412
|
+
proposer,
|
|
433
413
|
publisher: publisher.getSenderAddress(),
|
|
434
|
-
feeRecipient,
|
|
435
414
|
globalVariables: newGlobalVariables.toInspect(),
|
|
436
415
|
chainTipArchive,
|
|
437
416
|
blockNumber: newBlockNumber,
|
|
438
417
|
slot,
|
|
439
418
|
});
|
|
440
419
|
|
|
441
|
-
// If I created a "partial" header here that should make our job much easier.
|
|
442
420
|
const proposalHeader = CheckpointHeader.from({
|
|
443
421
|
...newGlobalVariables,
|
|
444
422
|
timestamp: newGlobalVariables.timestamp,
|
|
@@ -459,7 +437,7 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
|
|
|
459
437
|
pendingTxs,
|
|
460
438
|
proposalHeader,
|
|
461
439
|
newGlobalVariables,
|
|
462
|
-
|
|
440
|
+
proposer,
|
|
463
441
|
invalidateBlock,
|
|
464
442
|
publisher,
|
|
465
443
|
);
|
|
@@ -478,26 +456,13 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
|
|
|
478
456
|
);
|
|
479
457
|
this.emit('tx-count-check-failed', { minTxs: this.minTxsPerBlock, availableTxs: pendingTxCount });
|
|
480
458
|
}
|
|
481
|
-
|
|
482
|
-
await Promise.all([enqueueGovernanceSignalPromise, enqueueSlashingActionsPromise]);
|
|
483
|
-
|
|
484
|
-
const l1Response = await publisher.sendRequests();
|
|
485
|
-
const proposedBlock = l1Response?.successfulActions.find(a => a === 'propose');
|
|
486
|
-
if (proposedBlock) {
|
|
487
|
-
this.lastBlockPublished = block;
|
|
488
|
-
this.emit('block-published', { blockNumber: newBlockNumber, slot: Number(slot) });
|
|
489
|
-
await this.metrics.incFilledSlot(publisher.getSenderAddress().toString(), coinbase);
|
|
490
|
-
} else if (block) {
|
|
491
|
-
this.emit('block-publish-failed', l1Response ?? {});
|
|
492
|
-
}
|
|
493
|
-
|
|
494
|
-
this.setState(SequencerState.IDLE, undefined);
|
|
459
|
+
return block;
|
|
495
460
|
}
|
|
496
461
|
|
|
497
462
|
@trackSpan('Sequencer.work')
|
|
498
|
-
protected async
|
|
463
|
+
protected async safeWork() {
|
|
499
464
|
try {
|
|
500
|
-
await this.
|
|
465
|
+
await this.work();
|
|
501
466
|
} catch (err) {
|
|
502
467
|
if (err instanceof SequencerTooSlowError) {
|
|
503
468
|
// Log as warn only if we had to abort halfway through the block proposal
|
|
@@ -833,9 +798,8 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
|
|
|
833
798
|
/**
|
|
834
799
|
* Returns whether all dependencies have caught up.
|
|
835
800
|
* We don't check against the previous block submitted since it may have been reorg'd out.
|
|
836
|
-
* @returns Boolean indicating if our dependencies are synced to the latest block.
|
|
837
801
|
*/
|
|
838
|
-
protected async
|
|
802
|
+
protected async checkSync(args: { ts: bigint; slot: bigint }): Promise<
|
|
839
803
|
| {
|
|
840
804
|
block?: L2Block;
|
|
841
805
|
blockNumber: number;
|
|
@@ -845,6 +809,20 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
|
|
|
845
809
|
}
|
|
846
810
|
| undefined
|
|
847
811
|
> {
|
|
812
|
+
// Check that the archiver and dependencies have synced to the previous L1 slot at least
|
|
813
|
+
// TODO(#14766): Archiver reports L1 timestamp based on L1 blocks seen, which means that a missed L1 block will
|
|
814
|
+
// cause the archiver L1 timestamp to fall behind, and cause this sequencer to start processing one L1 slot later.
|
|
815
|
+
const l1Timestamp = await this.l2BlockSource.getL1Timestamp();
|
|
816
|
+
const { slot, ts } = args;
|
|
817
|
+
if (l1Timestamp === undefined || l1Timestamp + BigInt(this.l1Constants.ethereumSlotDuration) < ts) {
|
|
818
|
+
this.log.debug(`Cannot propose block at next L2 slot ${slot} due to pending sync from L1`, {
|
|
819
|
+
slot,
|
|
820
|
+
ts,
|
|
821
|
+
l1Timestamp,
|
|
822
|
+
});
|
|
823
|
+
return undefined;
|
|
824
|
+
}
|
|
825
|
+
|
|
848
826
|
const syncedBlocks = await Promise.all([
|
|
849
827
|
this.worldState.status().then(({ syncSummary }) => ({
|
|
850
828
|
number: syncSummary.latestBlockNumber,
|
|
@@ -853,12 +831,10 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
|
|
|
853
831
|
this.l2BlockSource.getL2Tips().then(t => t.latest),
|
|
854
832
|
this.p2pClient.getStatus().then(p2p => p2p.syncedToL2Block),
|
|
855
833
|
this.l1ToL2MessageSource.getL2Tips().then(t => t.latest),
|
|
856
|
-
this.l2BlockSource.getL1Timestamp(),
|
|
857
834
|
this.l2BlockSource.getPendingChainValidationStatus(),
|
|
858
835
|
] as const);
|
|
859
836
|
|
|
860
|
-
const [worldState, l2BlockSource, p2p, l1ToL2MessageSource,
|
|
861
|
-
syncedBlocks;
|
|
837
|
+
const [worldState, l2BlockSource, p2p, l1ToL2MessageSource, pendingChainValidationStatus] = syncedBlocks;
|
|
862
838
|
|
|
863
839
|
// The archiver reports 'undefined' hash for the genesis block
|
|
864
840
|
// because it doesn't have access to world state to compute it (facepalm)
|
|
@@ -869,33 +845,174 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
|
|
|
869
845
|
p2p.hash === l2BlockSource.hash &&
|
|
870
846
|
l1ToL2MessageSource.hash === l2BlockSource.hash;
|
|
871
847
|
|
|
872
|
-
const logData = { worldState, l2BlockSource, p2p, l1ToL2MessageSource };
|
|
873
|
-
this.log.debug(`Sequencer sync check ${result ? 'succeeded' : 'failed'}`, logData);
|
|
874
|
-
|
|
875
848
|
if (!result) {
|
|
849
|
+
this.log.debug(`Sequencer sync check failed`, { worldState, l2BlockSource, p2p, l1ToL2MessageSource });
|
|
876
850
|
return undefined;
|
|
877
851
|
}
|
|
878
852
|
|
|
853
|
+
// Special case for genesis state
|
|
879
854
|
const blockNumber = worldState.number;
|
|
880
|
-
if (blockNumber
|
|
881
|
-
const block = await this.l2BlockSource.getBlock(blockNumber);
|
|
882
|
-
if (!block) {
|
|
883
|
-
// this shouldn't really happen because a moment ago we checked that all components were in sync
|
|
884
|
-
this.log.warn(`Failed to get L2 block ${blockNumber} from the archiver with all components in sync`, logData);
|
|
885
|
-
return undefined;
|
|
886
|
-
}
|
|
887
|
-
|
|
888
|
-
return {
|
|
889
|
-
block,
|
|
890
|
-
blockNumber: block.number,
|
|
891
|
-
archive: block.archive.root,
|
|
892
|
-
l1Timestamp,
|
|
893
|
-
pendingChainValidationStatus,
|
|
894
|
-
};
|
|
895
|
-
} else {
|
|
855
|
+
if (blockNumber < INITIAL_L2_BLOCK_NUM) {
|
|
896
856
|
const archive = new Fr((await this.worldState.getCommitted().getTreeInfo(MerkleTreeId.ARCHIVE)).root);
|
|
897
857
|
return { blockNumber: INITIAL_L2_BLOCK_NUM - 1, archive, l1Timestamp, pendingChainValidationStatus };
|
|
898
858
|
}
|
|
859
|
+
|
|
860
|
+
const block = await this.l2BlockSource.getBlock(blockNumber);
|
|
861
|
+
if (!block) {
|
|
862
|
+
// this shouldn't really happen because a moment ago we checked that all components were in sync
|
|
863
|
+
this.log.error(`Failed to get L2 block ${blockNumber} from the archiver with all components in sync`);
|
|
864
|
+
return undefined;
|
|
865
|
+
}
|
|
866
|
+
|
|
867
|
+
return {
|
|
868
|
+
block,
|
|
869
|
+
blockNumber: block.number,
|
|
870
|
+
archive: block.archive.root,
|
|
871
|
+
l1Timestamp,
|
|
872
|
+
pendingChainValidationStatus,
|
|
873
|
+
};
|
|
874
|
+
}
|
|
875
|
+
|
|
876
|
+
/**
|
|
877
|
+
* Enqueues governance and slashing votes with the publisher. Does not block.
|
|
878
|
+
* @param publisher - The publisher to enqueue votes with
|
|
879
|
+
* @param attestorAddress - The attestor address to use for signing
|
|
880
|
+
* @param slot - The slot number
|
|
881
|
+
* @param timestamp - The timestamp for the votes
|
|
882
|
+
* @param context - Optional context for logging (e.g., block number)
|
|
883
|
+
* @returns A tuple of [governanceEnqueued, slashingEnqueued]
|
|
884
|
+
*/
|
|
885
|
+
protected enqueueGovernanceAndSlashingVotes(
|
|
886
|
+
publisher: SequencerPublisher,
|
|
887
|
+
attestorAddress: EthAddress,
|
|
888
|
+
slot: bigint,
|
|
889
|
+
timestamp: bigint,
|
|
890
|
+
): [Promise<boolean> | undefined, Promise<boolean> | undefined] {
|
|
891
|
+
try {
|
|
892
|
+
const signerFn = (msg: TypedDataDefinition) =>
|
|
893
|
+
this.validatorClient!.signWithAddress(attestorAddress, msg).then(s => s.toString());
|
|
894
|
+
|
|
895
|
+
const enqueueGovernancePromise =
|
|
896
|
+
this.governanceProposerPayload && !this.governanceProposerPayload.isZero()
|
|
897
|
+
? publisher
|
|
898
|
+
.enqueueGovernanceCastSignal(this.governanceProposerPayload, slot, timestamp, attestorAddress, signerFn)
|
|
899
|
+
.catch(err => {
|
|
900
|
+
this.log.error(`Error enqueuing governance vote`, err, { slot });
|
|
901
|
+
return false;
|
|
902
|
+
})
|
|
903
|
+
: undefined;
|
|
904
|
+
|
|
905
|
+
const enqueueSlashingPromise = this.slasherClient
|
|
906
|
+
? this.slasherClient
|
|
907
|
+
.getProposerActions(slot)
|
|
908
|
+
.then(actions => publisher.enqueueSlashingActions(actions, slot, timestamp, attestorAddress, signerFn))
|
|
909
|
+
.catch(err => {
|
|
910
|
+
this.log.error(`Error enqueuing slashing actions`, err, { slot });
|
|
911
|
+
return false;
|
|
912
|
+
})
|
|
913
|
+
: undefined;
|
|
914
|
+
|
|
915
|
+
return [enqueueGovernancePromise, enqueueSlashingPromise];
|
|
916
|
+
} catch (err) {
|
|
917
|
+
this.log.error(`Error enqueueing governance and slashing votes`, err);
|
|
918
|
+
return [undefined, undefined];
|
|
919
|
+
}
|
|
920
|
+
}
|
|
921
|
+
|
|
922
|
+
/**
|
|
923
|
+
* Checks if we are the proposer for the next slot.
|
|
924
|
+
* @returns True if we can propose, and the proposer address (undefined if anyone can propose)
|
|
925
|
+
*/
|
|
926
|
+
protected async checkCanPropose(slot: bigint): Promise<[boolean, EthAddress | undefined]> {
|
|
927
|
+
let proposer: EthAddress | undefined;
|
|
928
|
+
try {
|
|
929
|
+
proposer = await this.epochCache.getProposerAttesterAddressInSlot(slot);
|
|
930
|
+
} catch (e) {
|
|
931
|
+
if (e instanceof NoCommitteeError) {
|
|
932
|
+
this.log.warn(`Cannot propose at next L2 slot ${slot} since the committee does not exist on L1`);
|
|
933
|
+
return [false, undefined];
|
|
934
|
+
}
|
|
935
|
+
this.log.error(`Error getting proposer for slot ${slot}`, e);
|
|
936
|
+
return [false, undefined];
|
|
937
|
+
}
|
|
938
|
+
|
|
939
|
+
// If proposer is undefined, then the committee is empty and anyone may propose
|
|
940
|
+
if (proposer === undefined) {
|
|
941
|
+
return [true, undefined];
|
|
942
|
+
}
|
|
943
|
+
|
|
944
|
+
const validatorAddresses = this.validatorClient!.getValidatorAddresses();
|
|
945
|
+
const weAreProposer = validatorAddresses.some(addr => addr.equals(proposer));
|
|
946
|
+
|
|
947
|
+
if (!weAreProposer) {
|
|
948
|
+
this.log.debug(`Cannot propose at slot ${slot} since we are not a proposer`, { validatorAddresses, proposer });
|
|
949
|
+
return [false, proposer];
|
|
950
|
+
}
|
|
951
|
+
|
|
952
|
+
return [true, proposer];
|
|
953
|
+
}
|
|
954
|
+
|
|
955
|
+
/**
|
|
956
|
+
* Tries to vote on slashing actions and governance when the sync check fails but we're past the max time for initializing a proposal.
|
|
957
|
+
* This allows the sequencer to participate in governance/slashing votes even when it cannot build blocks.
|
|
958
|
+
*/
|
|
959
|
+
protected async tryVoteWhenSyncFails(args: { slot: bigint; ts: bigint }): Promise<void> {
|
|
960
|
+
const { slot, ts } = args;
|
|
961
|
+
|
|
962
|
+
// Prevent duplicate attempts in the same slot
|
|
963
|
+
if (this.lastSlotForVoteWhenSyncFailed === slot) {
|
|
964
|
+
this.log.debug(`Already attempted to vote in slot ${slot} (skipping)`);
|
|
965
|
+
return;
|
|
966
|
+
}
|
|
967
|
+
|
|
968
|
+
// Check if we're past the max time for initializing a proposal
|
|
969
|
+
const secondsIntoSlot = this.getSecondsIntoSlot(slot);
|
|
970
|
+
const maxAllowedTime = this.timetable.getMaxAllowedTime(SequencerState.INITIALIZING_PROPOSAL);
|
|
971
|
+
|
|
972
|
+
// If we haven't exceeded the time limit for initializing a proposal, don't proceed with voting
|
|
973
|
+
// We use INITIALIZING_PROPOSAL time limit because if we're past that, we can't build a block anyway
|
|
974
|
+
if (maxAllowedTime === undefined || secondsIntoSlot <= maxAllowedTime) {
|
|
975
|
+
this.log.trace(`Not attempting to vote since there is still for block building`, {
|
|
976
|
+
secondsIntoSlot,
|
|
977
|
+
maxAllowedTime,
|
|
978
|
+
});
|
|
979
|
+
return;
|
|
980
|
+
}
|
|
981
|
+
|
|
982
|
+
this.log.debug(`Sync for slot ${slot} failed, checking for voting opportunities`, {
|
|
983
|
+
secondsIntoSlot,
|
|
984
|
+
maxAllowedTime,
|
|
985
|
+
});
|
|
986
|
+
|
|
987
|
+
// Check if we're a proposer or proposal is open
|
|
988
|
+
const [canPropose, proposer] = await this.checkCanPropose(slot);
|
|
989
|
+
if (!canPropose) {
|
|
990
|
+
this.log.debug(`Cannot vote in slot ${slot} since we are not a proposer`, { slot, proposer });
|
|
991
|
+
return;
|
|
992
|
+
}
|
|
993
|
+
|
|
994
|
+
// Mark this slot as attempted
|
|
995
|
+
this.lastSlotForVoteWhenSyncFailed = slot;
|
|
996
|
+
|
|
997
|
+
// Get a publisher for voting
|
|
998
|
+
const { attestorAddress, publisher } = await this.publisherFactory.create(proposer);
|
|
999
|
+
|
|
1000
|
+
this.log.debug(`Attempting to vote despite sync failure at slot ${slot}`, {
|
|
1001
|
+
attestorAddress,
|
|
1002
|
+
slot,
|
|
1003
|
+
});
|
|
1004
|
+
|
|
1005
|
+
// Enqueue governance and slashing votes using the shared helper method
|
|
1006
|
+
const votesPromises = this.enqueueGovernanceAndSlashingVotes(publisher, attestorAddress, slot, ts);
|
|
1007
|
+
await Promise.all(votesPromises);
|
|
1008
|
+
|
|
1009
|
+
if (votesPromises.every(p => !p)) {
|
|
1010
|
+
this.log.debug(`No votes to enqueue for slot ${slot}`);
|
|
1011
|
+
return;
|
|
1012
|
+
}
|
|
1013
|
+
|
|
1014
|
+
this.log.info(`Voting in slot ${slot} despite sync failure`, { slot });
|
|
1015
|
+
await publisher.sendRequests();
|
|
899
1016
|
}
|
|
900
1017
|
|
|
901
1018
|
/**
|
|
@@ -905,19 +1022,19 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
|
|
|
905
1022
|
* and if they fail, any sequencer will try as well.
|
|
906
1023
|
*/
|
|
907
1024
|
protected async considerInvalidatingBlock(
|
|
908
|
-
syncedTo: NonNullable<Awaited<ReturnType<Sequencer['
|
|
1025
|
+
syncedTo: NonNullable<Awaited<ReturnType<Sequencer['checkSync']>>>,
|
|
909
1026
|
currentSlot: bigint,
|
|
910
|
-
ourValidatorAddresses: EthAddress[],
|
|
911
|
-
publisher: SequencerPublisher,
|
|
912
1027
|
): Promise<void> {
|
|
913
1028
|
const { pendingChainValidationStatus, l1Timestamp } = syncedTo;
|
|
914
1029
|
if (pendingChainValidationStatus.valid) {
|
|
915
1030
|
return;
|
|
916
1031
|
}
|
|
917
1032
|
|
|
1033
|
+
const { publisher } = await this.publisherFactory.create(undefined);
|
|
918
1034
|
const invalidBlockNumber = pendingChainValidationStatus.block.blockNumber;
|
|
919
1035
|
const invalidBlockTimestamp = pendingChainValidationStatus.block.timestamp;
|
|
920
1036
|
const timeSinceChainInvalid = this.dateProvider.nowInSeconds() - Number(invalidBlockTimestamp);
|
|
1037
|
+
const ourValidatorAddresses = this.validatorClient!.getValidatorAddresses();
|
|
921
1038
|
|
|
922
1039
|
const { secondsBeforeInvalidatingBlockAsCommitteeMember, secondsBeforeInvalidatingBlockAsNonCommitteeMember } =
|
|
923
1040
|
this.config;
|
|
@@ -137,6 +137,13 @@ export class SequencerTimetable {
|
|
|
137
137
|
return validationTimeEnd;
|
|
138
138
|
}
|
|
139
139
|
|
|
140
|
+
public getMaxAllowedTime(
|
|
141
|
+
state: Extract<SequencerState, SequencerState.STOPPED | SequencerState.IDLE | SequencerState.SYNCHRONIZING>,
|
|
142
|
+
): undefined;
|
|
143
|
+
public getMaxAllowedTime(
|
|
144
|
+
state: Exclude<SequencerState, SequencerState.STOPPED | SequencerState.IDLE | SequencerState.SYNCHRONIZING>,
|
|
145
|
+
): number;
|
|
146
|
+
public getMaxAllowedTime(state: SequencerState): number | undefined;
|
|
140
147
|
public getMaxAllowedTime(state: SequencerState): number | undefined {
|
|
141
148
|
switch (state) {
|
|
142
149
|
case SequencerState.STOPPED:
|