@aztec/sequencer-client 2.1.0-rc.20 → 2.1.0-rc.21
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 +233 -123
- 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 +257 -138
- 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":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,iBAAiB,CAAC;AAE/C,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAC;AACrD,OAAO,EAAwC,KAAK,cAAc,EAAE,MAAM,iBAAiB,CAAC;AAG5F,OAAO,EAAE,UAAU,EAAE,MAAM,+BAA+B,CAAC;AAC3D,OAAO,EAAE,SAAS,EAAE,MAAM,iCAAiC,CAAC;AAC5D,OAAO,EAAE,EAAE,EAAE,MAAM,0BAA0B,CAAC;AAG9C,OAAO,EAAE,KAAK,YAAY,EAAS,MAAM,yBAAyB,CAAC;AACnE,OAAO,EAAE,KAAK,iBAAiB,EAAY,MAAM,yBAAyB,CAAC;AAC3E,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,YAAY,CAAC;AACtC,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,gBAAgB,CAAC;AAC7D,OAAO,EACL,oBAAoB,EACpB,+BAA+B,EAC/B,KAAK,aAAa,EAClB,KAAK,mBAAmB,EACzB,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EAAE,KAAK,iBAAiB,EAAkD,MAAM,6BAA6B,CAAC;AAErH,OAAO,EACL,KAAK,qBAAqB,EAC1B,KAAK,qBAAqB,EAE1B,KAAK,sBAAsB,EAC5B,MAAM,iCAAiC,CAAC;AACzC,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,yBAAyB,CAAC;AAKnE,OAAO,EAA0E,EAAE,EAAE,MAAM,kBAAkB,CAAC;AAE9G,OAAO,EAAc,KAAK,eAAe,EAAE,KAAK,MAAM,EAAiC,MAAM,yBAAyB,CAAC;AACvH,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,yBAAyB,CAAC;AAK/D,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,8CAA8C,CAAC;AAC1F,OAAO,KAAK,EAAE,yBAAyB,EAAE,MAAM,6CAA6C,CAAC;AAC7F,OAAO,KAAK,EAAE,MAAM,EAAE,sBAAsB,EAAE,kBAAkB,EAAE,MAAM,qCAAqC,CAAC;AAC9G,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAGnD,OAAO,EAAE,kBAAkB,EAAE,MAAM,gBAAgB,CAAC;AACpD,OAAO,EAAE,cAAc,EAAE,KAAK,sBAAsB,EAAE,MAAM,YAAY,CAAC;AAEzE,OAAO,EAAE,cAAc,EAAE,CAAC;AAE1B,KAAK,wBAAwB,GAAG,IAAI,CAAC,iBAAiB,EAAE,sBAAsB,GAAG,eAAe,GAAG,cAAc,CAAC,CAAC;AAEnH,MAAM,MAAM,eAAe,GAAG;IAC5B,CAAC,eAAe,CAAC,EAAE,CAAC,IAAI,EAAE;QACxB,QAAQ,EAAE,cAAc,CAAC;QACzB,QAAQ,EAAE,cAAc,CAAC;QACzB,eAAe,CAAC,EAAE,MAAM,CAAC;QACzB,UAAU,CAAC,EAAE,MAAM,CAAC;KACrB,KAAK,IAAI,CAAC;IACX,CAAC,8BAA8B,CAAC,EAAE,CAAC,IAAI,EAAE;QAAE,MAAM,EAAE,MAAM,CAAA;KAAE,KAAK,IAAI,CAAC;IACrE,CAAC,uBAAuB,CAAC,EAAE,CAAC,IAAI,EAAE;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,YAAY,EAAE,MAAM,CAAA;KAAE,KAAK,IAAI,CAAC;IACpF,CAAC,oBAAoB,CAAC,EAAE,CAAC,IAAI,EAAE;QAAE,MAAM,EAAE,MAAM,CAAA;KAAE,KAAK,IAAI,CAAC;IAC3D,CAAC,sBAAsB,CAAC,EAAE,CAAC,IAAI,EAAE;QAC/B,iBAAiB,CAAC,EAAE,MAAM,EAAE,CAAC;QAC7B,aAAa,CAAC,EAAE,MAAM,EAAE,CAAC;QACzB,WAAW,CAAC,EAAE,MAAM,EAAE,CAAC;QACvB,cAAc,CAAC,EAAE,MAAM,EAAE,CAAC;KAC3B,KAAK,IAAI,CAAC;IACX,CAAC,iBAAiB,CAAC,EAAE,CAAC,IAAI,EAAE;QAAE,WAAW,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,KAAK,IAAI,CAAC;CAC5E,CAAC;8BAW8C,UAAU,iBAAiB,CAAC,eAAe,CAAC;AAT5F;;;;;;;;GAQG;AACH,qBAAa,SAAU,SAAQ,cAA8D;IA8BzF,SAAS,CAAC,gBAAgB,EAAE,yBAAyB;IACrD,SAAS,CAAC,eAAe,EAAE,eAAe,GAAG,SAAS;IACtD,SAAS,CAAC,cAAc,EAAE,qBAAqB;IAC/C,SAAS,CAAC,SAAS,EAAE,GAAG;IACxB,SAAS,CAAC,UAAU,EAAE,sBAAsB;IAC5C,SAAS,CAAC,aAAa,EAAE,sBAAsB,GAAG,SAAS;IAC3D,SAAS,CAAC,aAAa,EAAE,aAAa;IACtC,SAAS,CAAC,mBAAmB,EAAE,mBAAmB;IAClD,SAAS,CAAC,YAAY,EAAE,qBAAqB;IAC7C,SAAS,CAAC,WAAW,EAAE,wBAAwB;IAC/C,SAAS,CAAC,YAAY,EAAE,YAAY;IACpC,SAAS,CAAC,UAAU,EAAE,UAAU;IAChC,SAAS,CAAC,cAAc,EAAE,cAAc;IACxC,SAAS,CAAC,MAAM,EAAE,eAAe;IACjC,SAAS,CAAC,SAAS,EAAE,eAAe;IACpC,SAAS,CAAC,GAAG;IA5Cf,OAAO,CAAC,cAAc,CAAC,CAAiB;IACxC,OAAO,CAAC,iBAAiB,CAAgB;IACzC,OAAO,CAAC,cAAc,CAAM;IAC5B,OAAO,CAAC,cAAc,CAAK;IAC3B,OAAO,CAAC,4BAA4B,CAAK;IACzC,OAAO,CAAC,KAAK,CAA0B;IACvC,OAAO,CAAC,mBAAmB,CAAuB;IAClD,OAAO,CAAC,WAAW,CAA8B;IACjD,OAAO,CAAC,OAAO,CAAmB;IAElC,OAAO,CAAC,kBAAkB,CAAsB;IAEhD,OAAO,CAAC,yBAAyB,CAAyB;IAE1D,oGAAoG;IACpG,OAAO,CAAC,6BAA6B,CAAqB;IAE1D,+GAA+G;IAC/G,SAAS,CAAC,SAAS,EAAG,kBAAkB,CAAC;IACzC,SAAS,CAAC,gBAAgB,EAAE,OAAO,CAAS;IAO5C,SAAS,CAAC,SAAS,EAAE,kBAAkB,GAAG,SAAS,CAAC;gBAGxC,gBAAgB,EAAE,yBAAyB,EAC3C,eAAe,EAAE,eAAe,GAAG,SAAS,EAAE,wDAAwD;IACtG,cAAc,EAAE,qBAAqB,EACrC,SAAS,EAAE,GAAG,EACd,UAAU,EAAE,sBAAsB,EAClC,aAAa,EAAE,sBAAsB,GAAG,SAAS,EACjD,aAAa,EAAE,aAAa,EAC5B,mBAAmB,EAAE,mBAAmB,EACxC,YAAY,EAAE,qBAAqB,EACnC,WAAW,EAAE,wBAAwB,EACrC,YAAY,EAAE,YAAY,EAC1B,UAAU,EAAE,UAAU,EACtB,cAAc,EAAE,cAAc,EAC9B,MAAM,EAAE,eAAe,EACvB,SAAS,GAAE,eAAsC,EACjD,GAAG,mCAA4B;IAS3C,IAAI,MAAM,IAAI,MAAM,CAEnB;IAEM,qBAAqB;IAIrB,SAAS;IAIhB;;;OAGG;IACI,YAAY,CAAC,MAAM,EAAE,eAAe;IA0C3C,OAAO,CAAC,YAAY;IAcP,IAAI;IAIjB;;OAEG;IACI,KAAK;IAOZ;;OAEG;IACU,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IASlC;;;OAGG;IACI,MAAM;;;IAIb;;;;;;;OAOG;cACa,IAAI;IAmJpB,sFAAsF;YACxE,8BAA8B;cA8D5B,QAAQ;IAmBxB;;;;;OAKG;IACH,QAAQ,CAAC,aAAa,EAAE,sBAAsB,EAAE,UAAU,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE;QAAE,KAAK,CAAC,EAAE,OAAO,CAAA;KAAE,GAAG,IAAI;IACrG,QAAQ,CACN,aAAa,EAAE,OAAO,CAAC,cAAc,EAAE,sBAAsB,CAAC,EAC9D,UAAU,CAAC,EAAE,SAAS,EACtB,IAAI,CAAC,EAAE;QAAE,KAAK,CAAC,EAAE,OAAO,CAAA;KAAE,GACzB,IAAI;YAgCO,oBAAoB;IAUlC,SAAS,CAAC,sBAAsB,CAAC,IAAI,EAAE,MAAM,GAAG,qBAAqB;IAkBrE;;;;;;;;;;OAUG;YAIW,2BAA2B;cAiGzB,mBAAmB,CACjC,KAAK,EAAE,OAAO,EACd,GAAG,EAAE,EAAE,EAAE,EACT,eAAe,EAAE,UAAU,GAAG,SAAS,GACtC,OAAO,CAAC,oBAAoB,EAAE,GAAG,SAAS,CAAC;IAwF9C;;;OAGG;cAIa,qBAAqB,CACnC,KAAK,EAAE,OAAO,EACd,sBAAsB,EAAE,+BAA+B,EACvD,+BAA+B,EAAE,SAAS,EAC1C,eAAe,EAAE,sBAAsB,GAAG,SAAS,EACnD,SAAS,EAAE,kBAAkB,GAC5B,OAAO,CAAC,IAAI,CAAC;IAuBhB;;;OAGG;cACa,SAAS,CAAC,IAAI,EAAE;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,GAAG,OAAO,CAClE;QACE,KAAK,CAAC,EAAE,OAAO,CAAC;QAChB,WAAW,EAAE,MAAM,CAAC;QACpB,OAAO,EAAE,EAAE,CAAC;QACZ,WAAW,EAAE,MAAM,CAAC;QACpB,4BAA4B,EAAE,mBAAmB,CAAC;KACnD,GACD,SAAS,CACZ;IAkED;;;;;;;;OAQG;IACH,SAAS,CAAC,iCAAiC,CACzC,SAAS,EAAE,kBAAkB,EAC7B,eAAe,EAAE,UAAU,EAC3B,IAAI,EAAE,MAAM,EACZ,SAAS,EAAE,MAAM,GAChB,CAAC,OAAO,CAAC,OAAO,CAAC,GAAG,SAAS,EAAE,OAAO,CAAC,OAAO,CAAC,GAAG,SAAS,CAAC;IAgC/D;;;OAGG;cACa,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,CAAC,OAAO,EAAE,UAAU,GAAG,SAAS,CAAC,CAAC;IA6BzF;;;OAGG;cACa,oBAAoB,CAAC,IAAI,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,EAAE,EAAE,MAAM,CAAA;KAAE,GAAG,OAAO,CAAC,IAAI,CAAC;IA2DvF;;;;;OAKG;cACa,yBAAyB,CACvC,QAAQ,EAAE,WAAW,CAAC,OAAO,CAAC,UAAU,CAAC,SAAS,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC,EAClE,WAAW,EAAE,MAAM,GAClB,OAAO,CAAC,IAAI,CAAC;IA+DhB,OAAO,CAAC,0BAA0B;IAIlC,OAAO,CAAC,kBAAkB;IAK1B,IAAI,iBAAiB,WAEpB;IAED,IAAI,aAAa,IAAI,MAAM,GAAG,SAAS,CAEtC;IAEM,gBAAgB,IAAI,sBAAsB,GAAG,SAAS;CAG9D"}
|
|
@@ -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
|
-
this.log.debug(`Cannot propose block ${newBlockNumber} at next L2 slot ${slot} due to pending sync from L1`, syncLogData);
|
|
208
|
-
return;
|
|
209
|
-
}
|
|
210
|
-
// Check that the slot is not taken by a block already
|
|
214
|
+
// Check that the slot is not taken by a block already (should never happen, since only us can propose for this slot)
|
|
211
215
|
if (syncedTo.block && syncedTo.block.header.getSlot() >= slot) {
|
|
212
|
-
this.log.
|
|
216
|
+
this.log.warn(`Cannot propose block at next L2 slot ${slot} since that slot was taken by block ${syncedTo.blockNumber}`, {
|
|
213
217
|
...syncLogData,
|
|
214
218
|
block: syncedTo.block.header.toInspect()
|
|
215
219
|
});
|
|
216
220
|
return;
|
|
217
221
|
}
|
|
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
222
|
// Check that we are a proposer for the next slot
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
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
|
-
}
|
|
223
|
+
this.setState(SequencerState.PROPOSER_CHECK, slot);
|
|
224
|
+
const [canPropose, proposer] = await this.checkCanPropose(slot);
|
|
225
|
+
// If we are not a proposer, check if we should invalidate a invalid block, and bail
|
|
226
|
+
if (!canPropose) {
|
|
227
|
+
await this.considerInvalidatingBlock(syncedTo, slot);
|
|
251
228
|
return;
|
|
252
229
|
}
|
|
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,7 @@ 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
|
-
*/ async getChainTip() {
|
|
578
|
+
*/ async checkSync(args) {
|
|
617
579
|
const syncedBlocks = await Promise.all([
|
|
618
580
|
this.worldState.status().then(({ syncSummary })=>({
|
|
619
581
|
number: syncSummary.latestBlockNumber,
|
|
@@ -629,32 +591,30 @@ export { SequencerState };
|
|
|
629
591
|
// The archiver reports 'undefined' hash for the genesis block
|
|
630
592
|
// because it doesn't have access to world state to compute it (facepalm)
|
|
631
593
|
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
594
|
if (!result) {
|
|
595
|
+
this.log.debug(`Sequencer sync check failed`, {
|
|
596
|
+
worldState,
|
|
597
|
+
l2BlockSource,
|
|
598
|
+
p2p,
|
|
599
|
+
l1ToL2MessageSource
|
|
600
|
+
});
|
|
601
|
+
return undefined;
|
|
602
|
+
}
|
|
603
|
+
// Check that the archiver and dependencies have synced to the previous L1 slot at least
|
|
604
|
+
// TODO(#14766): Archiver reports L1 timestamp based on L1 blocks seen, which means that a missed L1 block will
|
|
605
|
+
// cause the archiver L1 timestamp to fall behind, and cause this sequencer to start processing one L1 slot later.
|
|
606
|
+
const { slot, ts } = args;
|
|
607
|
+
if (l1Timestamp + BigInt(this.l1Constants.ethereumSlotDuration) < ts) {
|
|
608
|
+
this.log.debug(`Cannot propose block at next L2 slot ${slot} due to pending sync from L1`, {
|
|
609
|
+
slot,
|
|
610
|
+
ts,
|
|
611
|
+
l1Timestamp
|
|
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.0-rc.
|
|
3
|
+
"version": "2.1.0-rc.21",
|
|
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.0-rc.
|
|
30
|
-
"@aztec/bb-prover": "2.1.0-rc.
|
|
31
|
-
"@aztec/blob-lib": "2.1.0-rc.
|
|
32
|
-
"@aztec/blob-sink": "2.1.0-rc.
|
|
33
|
-
"@aztec/constants": "2.1.0-rc.
|
|
34
|
-
"@aztec/epoch-cache": "2.1.0-rc.
|
|
35
|
-
"@aztec/ethereum": "2.1.0-rc.
|
|
36
|
-
"@aztec/foundation": "2.1.0-rc.
|
|
37
|
-
"@aztec/l1-artifacts": "2.1.0-rc.
|
|
38
|
-
"@aztec/merkle-tree": "2.1.0-rc.
|
|
39
|
-
"@aztec/node-keystore": "2.1.0-rc.
|
|
40
|
-
"@aztec/noir-acvm_js": "2.1.0-rc.
|
|
41
|
-
"@aztec/noir-contracts.js": "2.1.0-rc.
|
|
42
|
-
"@aztec/noir-protocol-circuits-types": "2.1.0-rc.
|
|
43
|
-
"@aztec/noir-types": "2.1.0-rc.
|
|
44
|
-
"@aztec/p2p": "2.1.0-rc.
|
|
45
|
-
"@aztec/protocol-contracts": "2.1.0-rc.
|
|
46
|
-
"@aztec/prover-client": "2.1.0-rc.
|
|
47
|
-
"@aztec/simulator": "2.1.0-rc.
|
|
48
|
-
"@aztec/slasher": "2.1.0-rc.
|
|
49
|
-
"@aztec/stdlib": "2.1.0-rc.
|
|
50
|
-
"@aztec/telemetry-client": "2.1.0-rc.
|
|
51
|
-
"@aztec/validator-client": "2.1.0-rc.
|
|
52
|
-
"@aztec/world-state": "2.1.0-rc.
|
|
29
|
+
"@aztec/aztec.js": "2.1.0-rc.21",
|
|
30
|
+
"@aztec/bb-prover": "2.1.0-rc.21",
|
|
31
|
+
"@aztec/blob-lib": "2.1.0-rc.21",
|
|
32
|
+
"@aztec/blob-sink": "2.1.0-rc.21",
|
|
33
|
+
"@aztec/constants": "2.1.0-rc.21",
|
|
34
|
+
"@aztec/epoch-cache": "2.1.0-rc.21",
|
|
35
|
+
"@aztec/ethereum": "2.1.0-rc.21",
|
|
36
|
+
"@aztec/foundation": "2.1.0-rc.21",
|
|
37
|
+
"@aztec/l1-artifacts": "2.1.0-rc.21",
|
|
38
|
+
"@aztec/merkle-tree": "2.1.0-rc.21",
|
|
39
|
+
"@aztec/node-keystore": "2.1.0-rc.21",
|
|
40
|
+
"@aztec/noir-acvm_js": "2.1.0-rc.21",
|
|
41
|
+
"@aztec/noir-contracts.js": "2.1.0-rc.21",
|
|
42
|
+
"@aztec/noir-protocol-circuits-types": "2.1.0-rc.21",
|
|
43
|
+
"@aztec/noir-types": "2.1.0-rc.21",
|
|
44
|
+
"@aztec/p2p": "2.1.0-rc.21",
|
|
45
|
+
"@aztec/protocol-contracts": "2.1.0-rc.21",
|
|
46
|
+
"@aztec/prover-client": "2.1.0-rc.21",
|
|
47
|
+
"@aztec/simulator": "2.1.0-rc.21",
|
|
48
|
+
"@aztec/slasher": "2.1.0-rc.21",
|
|
49
|
+
"@aztec/stdlib": "2.1.0-rc.21",
|
|
50
|
+
"@aztec/telemetry-client": "2.1.0-rc.21",
|
|
51
|
+
"@aztec/validator-client": "2.1.0-rc.21",
|
|
52
|
+
"@aztec/world-state": "2.1.0-rc.21",
|
|
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": "2.1.0-rc.
|
|
59
|
-
"@aztec/kv-store": "2.1.0-rc.
|
|
58
|
+
"@aztec/archiver": "2.1.0-rc.21",
|
|
59
|
+
"@aztec/kv-store": "2.1.0-rc.21",
|
|
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
|
-
this.log.debug(
|
|
283
|
-
`Cannot propose block ${newBlockNumber} at next L2 slot ${slot} due to pending sync from L1`,
|
|
284
|
-
syncLogData,
|
|
285
|
-
);
|
|
286
|
-
return;
|
|
287
|
-
}
|
|
288
|
-
|
|
289
|
-
// Check that the slot is not taken by a block already
|
|
285
|
+
// Check that the slot is not taken by a block already (should never happen, since only us can propose for this slot)
|
|
290
286
|
if (syncedTo.block && syncedTo.block.header.getSlot() >= slot) {
|
|
291
|
-
this.log.
|
|
287
|
+
this.log.warn(
|
|
292
288
|
`Cannot propose block at next L2 slot ${slot} since that slot was taken by block ${syncedTo.blockNumber}`,
|
|
293
289
|
{ ...syncLogData, block: syncedTo.block.header.toInspect() },
|
|
294
290
|
);
|
|
295
291
|
return;
|
|
296
292
|
}
|
|
297
293
|
|
|
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
294
|
// Check that we are a proposer for the next slot
|
|
308
|
-
|
|
309
|
-
|
|
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
|
-
}
|
|
295
|
+
this.setState(SequencerState.PROPOSER_CHECK, slot);
|
|
296
|
+
const [canPropose, proposer] = await this.checkCanPropose(slot);
|
|
319
297
|
|
|
320
|
-
// If
|
|
321
|
-
|
|
322
|
-
|
|
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
|
-
}
|
|
298
|
+
// If we are not a proposer, check if we should invalidate a invalid block, and bail
|
|
299
|
+
if (!canPropose) {
|
|
300
|
+
await this.considerInvalidatingBlock(syncedTo, slot);
|
|
335
301
|
return;
|
|
336
302
|
}
|
|
337
303
|
|
|
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);
|
|
384
|
+
|
|
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
|
+
}
|
|
426
395
|
|
|
427
|
-
this.
|
|
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;
|
|
@@ -861,33 +826,187 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
|
|
|
861
826
|
p2p.hash === l2BlockSource.hash &&
|
|
862
827
|
l1ToL2MessageSource.hash === l2BlockSource.hash;
|
|
863
828
|
|
|
864
|
-
const logData = { worldState, l2BlockSource, p2p, l1ToL2MessageSource };
|
|
865
|
-
this.log.debug(`Sequencer sync check ${result ? 'succeeded' : 'failed'}`, logData);
|
|
866
|
-
|
|
867
829
|
if (!result) {
|
|
830
|
+
this.log.debug(`Sequencer sync check failed`, { worldState, l2BlockSource, p2p, l1ToL2MessageSource });
|
|
868
831
|
return undefined;
|
|
869
832
|
}
|
|
870
833
|
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
return {
|
|
881
|
-
block,
|
|
882
|
-
blockNumber: block.number,
|
|
883
|
-
archive: block.archive.root,
|
|
834
|
+
// Check that the archiver and dependencies have synced to the previous L1 slot at least
|
|
835
|
+
// TODO(#14766): Archiver reports L1 timestamp based on L1 blocks seen, which means that a missed L1 block will
|
|
836
|
+
// cause the archiver L1 timestamp to fall behind, and cause this sequencer to start processing one L1 slot later.
|
|
837
|
+
const { slot, ts } = args;
|
|
838
|
+
if (l1Timestamp + BigInt(this.l1Constants.ethereumSlotDuration) < ts) {
|
|
839
|
+
this.log.debug(`Cannot propose block at next L2 slot ${slot} due to pending sync from L1`, {
|
|
840
|
+
slot,
|
|
841
|
+
ts,
|
|
884
842
|
l1Timestamp,
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
}
|
|
843
|
+
});
|
|
844
|
+
return undefined;
|
|
845
|
+
}
|
|
846
|
+
|
|
847
|
+
// Special case for genesis state
|
|
848
|
+
const blockNumber = worldState.number;
|
|
849
|
+
if (blockNumber < INITIAL_L2_BLOCK_NUM) {
|
|
888
850
|
const archive = new Fr((await this.worldState.getCommitted().getTreeInfo(MerkleTreeId.ARCHIVE)).root);
|
|
889
851
|
return { blockNumber: INITIAL_L2_BLOCK_NUM - 1, archive, l1Timestamp, pendingChainValidationStatus };
|
|
890
852
|
}
|
|
853
|
+
|
|
854
|
+
const block = await this.l2BlockSource.getBlock(blockNumber);
|
|
855
|
+
if (!block) {
|
|
856
|
+
// this shouldn't really happen because a moment ago we checked that all components were in sync
|
|
857
|
+
this.log.error(`Failed to get L2 block ${blockNumber} from the archiver with all components in sync`);
|
|
858
|
+
return undefined;
|
|
859
|
+
}
|
|
860
|
+
|
|
861
|
+
return {
|
|
862
|
+
block,
|
|
863
|
+
blockNumber: block.number,
|
|
864
|
+
archive: block.archive.root,
|
|
865
|
+
l1Timestamp,
|
|
866
|
+
pendingChainValidationStatus,
|
|
867
|
+
};
|
|
868
|
+
}
|
|
869
|
+
|
|
870
|
+
/**
|
|
871
|
+
* Enqueues governance and slashing votes with the publisher. Does not block.
|
|
872
|
+
* @param publisher - The publisher to enqueue votes with
|
|
873
|
+
* @param attestorAddress - The attestor address to use for signing
|
|
874
|
+
* @param slot - The slot number
|
|
875
|
+
* @param timestamp - The timestamp for the votes
|
|
876
|
+
* @param context - Optional context for logging (e.g., block number)
|
|
877
|
+
* @returns A tuple of [governanceEnqueued, slashingEnqueued]
|
|
878
|
+
*/
|
|
879
|
+
protected enqueueGovernanceAndSlashingVotes(
|
|
880
|
+
publisher: SequencerPublisher,
|
|
881
|
+
attestorAddress: EthAddress,
|
|
882
|
+
slot: bigint,
|
|
883
|
+
timestamp: bigint,
|
|
884
|
+
): [Promise<boolean> | undefined, Promise<boolean> | undefined] {
|
|
885
|
+
try {
|
|
886
|
+
const signerFn = (msg: TypedDataDefinition) =>
|
|
887
|
+
this.validatorClient!.signWithAddress(attestorAddress, msg).then(s => s.toString());
|
|
888
|
+
|
|
889
|
+
const enqueueGovernancePromise =
|
|
890
|
+
this.governanceProposerPayload && !this.governanceProposerPayload.isZero()
|
|
891
|
+
? publisher
|
|
892
|
+
.enqueueGovernanceCastSignal(this.governanceProposerPayload, slot, timestamp, attestorAddress, signerFn)
|
|
893
|
+
.catch(err => {
|
|
894
|
+
this.log.error(`Error enqueuing governance vote`, err, { slot });
|
|
895
|
+
return false;
|
|
896
|
+
})
|
|
897
|
+
: undefined;
|
|
898
|
+
|
|
899
|
+
const enqueueSlashingPromise = this.slasherClient
|
|
900
|
+
? this.slasherClient
|
|
901
|
+
.getProposerActions(slot)
|
|
902
|
+
.then(actions => publisher.enqueueSlashingActions(actions, slot, timestamp, attestorAddress, signerFn))
|
|
903
|
+
.catch(err => {
|
|
904
|
+
this.log.error(`Error enqueuing slashing actions`, err, { slot });
|
|
905
|
+
return false;
|
|
906
|
+
})
|
|
907
|
+
: undefined;
|
|
908
|
+
|
|
909
|
+
return [enqueueGovernancePromise, enqueueSlashingPromise];
|
|
910
|
+
} catch (err) {
|
|
911
|
+
this.log.error(`Error enqueueing governance and slashing votes`, err);
|
|
912
|
+
return [undefined, undefined];
|
|
913
|
+
}
|
|
914
|
+
}
|
|
915
|
+
|
|
916
|
+
/**
|
|
917
|
+
* Checks if we are the proposer for the next slot.
|
|
918
|
+
* @returns True if we can propose, and the proposer address (undefined if anyone can propose)
|
|
919
|
+
*/
|
|
920
|
+
protected async checkCanPropose(slot: bigint): Promise<[boolean, EthAddress | undefined]> {
|
|
921
|
+
let proposer: EthAddress | undefined;
|
|
922
|
+
try {
|
|
923
|
+
proposer = await this.epochCache.getProposerAttesterAddressInSlot(slot);
|
|
924
|
+
} catch (e) {
|
|
925
|
+
if (e instanceof NoCommitteeError) {
|
|
926
|
+
this.log.warn(`Cannot propose at next L2 slot ${slot} since the committee does not exist on L1`);
|
|
927
|
+
return [false, undefined];
|
|
928
|
+
}
|
|
929
|
+
this.log.error(`Error getting proposer for slot ${slot}`, e);
|
|
930
|
+
return [false, undefined];
|
|
931
|
+
}
|
|
932
|
+
|
|
933
|
+
// If proposer is undefined, then the committee is empty and anyone may propose
|
|
934
|
+
if (proposer === undefined) {
|
|
935
|
+
return [true, undefined];
|
|
936
|
+
}
|
|
937
|
+
|
|
938
|
+
const validatorAddresses = this.validatorClient!.getValidatorAddresses();
|
|
939
|
+
const weAreProposer = validatorAddresses.some(addr => addr.equals(proposer));
|
|
940
|
+
|
|
941
|
+
if (!weAreProposer) {
|
|
942
|
+
this.log.debug(`Cannot propose at slot ${slot} since we are not a proposer`, { validatorAddresses, proposer });
|
|
943
|
+
return [false, proposer];
|
|
944
|
+
}
|
|
945
|
+
|
|
946
|
+
return [true, proposer];
|
|
947
|
+
}
|
|
948
|
+
|
|
949
|
+
/**
|
|
950
|
+
* Tries to vote on slashing actions and governance when the sync check fails but we're past the max time for initializing a proposal.
|
|
951
|
+
* This allows the sequencer to participate in governance/slashing votes even when it cannot build blocks.
|
|
952
|
+
*/
|
|
953
|
+
protected async tryVoteWhenSyncFails(args: { slot: bigint; ts: bigint }): Promise<void> {
|
|
954
|
+
const { slot, ts } = args;
|
|
955
|
+
|
|
956
|
+
// Prevent duplicate attempts in the same slot
|
|
957
|
+
if (this.lastSlotForVoteWhenSyncFailed === slot) {
|
|
958
|
+
this.log.debug(`Already attempted to vote in slot ${slot} (skipping)`);
|
|
959
|
+
return;
|
|
960
|
+
}
|
|
961
|
+
|
|
962
|
+
// Check if we're past the max time for initializing a proposal
|
|
963
|
+
const secondsIntoSlot = this.getSecondsIntoSlot(slot);
|
|
964
|
+
const maxAllowedTime = this.timetable.getMaxAllowedTime(SequencerState.INITIALIZING_PROPOSAL);
|
|
965
|
+
|
|
966
|
+
// If we haven't exceeded the time limit for initializing a proposal, don't proceed with voting
|
|
967
|
+
// We use INITIALIZING_PROPOSAL time limit because if we're past that, we can't build a block anyway
|
|
968
|
+
if (maxAllowedTime === undefined || secondsIntoSlot <= maxAllowedTime) {
|
|
969
|
+
this.log.trace(`Not attempting to vote since there is still for block building`, {
|
|
970
|
+
secondsIntoSlot,
|
|
971
|
+
maxAllowedTime,
|
|
972
|
+
});
|
|
973
|
+
return;
|
|
974
|
+
}
|
|
975
|
+
|
|
976
|
+
this.log.debug(`Sync for slot ${slot} failed, checking for voting opportunities`, {
|
|
977
|
+
secondsIntoSlot,
|
|
978
|
+
maxAllowedTime,
|
|
979
|
+
});
|
|
980
|
+
|
|
981
|
+
// Check if we're a proposer or proposal is open
|
|
982
|
+
const [canPropose, proposer] = await this.checkCanPropose(slot);
|
|
983
|
+
if (!canPropose) {
|
|
984
|
+
this.log.debug(`Cannot vote in slot ${slot} since we are not a proposer`, { slot, proposer });
|
|
985
|
+
return;
|
|
986
|
+
}
|
|
987
|
+
|
|
988
|
+
// Mark this slot as attempted
|
|
989
|
+
this.lastSlotForVoteWhenSyncFailed = slot;
|
|
990
|
+
|
|
991
|
+
// Get a publisher for voting
|
|
992
|
+
const { attestorAddress, publisher } = await this.publisherFactory.create(proposer);
|
|
993
|
+
|
|
994
|
+
this.log.debug(`Attempting to vote despite sync failure at slot ${slot}`, {
|
|
995
|
+
attestorAddress,
|
|
996
|
+
slot,
|
|
997
|
+
});
|
|
998
|
+
|
|
999
|
+
// Enqueue governance and slashing votes using the shared helper method
|
|
1000
|
+
const votesPromises = this.enqueueGovernanceAndSlashingVotes(publisher, attestorAddress, slot, ts);
|
|
1001
|
+
await Promise.all(votesPromises);
|
|
1002
|
+
|
|
1003
|
+
if (votesPromises.every(p => !p)) {
|
|
1004
|
+
this.log.debug(`No votes to enqueue for slot ${slot}`);
|
|
1005
|
+
return;
|
|
1006
|
+
}
|
|
1007
|
+
|
|
1008
|
+
this.log.info(`Voting in slot ${slot} despite sync failure`, { slot });
|
|
1009
|
+
await publisher.sendRequests();
|
|
891
1010
|
}
|
|
892
1011
|
|
|
893
1012
|
/**
|
|
@@ -897,19 +1016,19 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
|
|
|
897
1016
|
* and if they fail, any sequencer will try as well.
|
|
898
1017
|
*/
|
|
899
1018
|
protected async considerInvalidatingBlock(
|
|
900
|
-
syncedTo: NonNullable<Awaited<ReturnType<Sequencer['
|
|
1019
|
+
syncedTo: NonNullable<Awaited<ReturnType<Sequencer['checkSync']>>>,
|
|
901
1020
|
currentSlot: bigint,
|
|
902
|
-
ourValidatorAddresses: EthAddress[],
|
|
903
|
-
publisher: SequencerPublisher,
|
|
904
1021
|
): Promise<void> {
|
|
905
1022
|
const { pendingChainValidationStatus, l1Timestamp } = syncedTo;
|
|
906
1023
|
if (pendingChainValidationStatus.valid) {
|
|
907
1024
|
return;
|
|
908
1025
|
}
|
|
909
1026
|
|
|
1027
|
+
const { publisher } = await this.publisherFactory.create(undefined);
|
|
910
1028
|
const invalidBlockNumber = pendingChainValidationStatus.block.blockNumber;
|
|
911
1029
|
const invalidBlockTimestamp = pendingChainValidationStatus.block.timestamp;
|
|
912
1030
|
const timeSinceChainInvalid = this.dateProvider.nowInSeconds() - Number(invalidBlockTimestamp);
|
|
1031
|
+
const ourValidatorAddresses = this.validatorClient!.getValidatorAddresses();
|
|
913
1032
|
|
|
914
1033
|
const { secondsBeforeInvalidatingBlockAsCommitteeMember, secondsBeforeInvalidatingBlockAsNonCommitteeMember } =
|
|
915
1034
|
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:
|