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