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