@aztec/sequencer-client 2.1.0-rc.2 → 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/config.d.ts.map +1 -1
- package/dest/config.js +4 -0
- package/dest/sequencer/sequencer.d.ts +35 -6
- package/dest/sequencer/sequencer.d.ts.map +1 -1
- package/dest/sequencer/sequencer.js +243 -124
- package/dest/sequencer/timetable.d.ts +2 -0
- package/dest/sequencer/timetable.d.ts.map +1 -1
- package/package.json +28 -28
- package/src/config.ts +4 -0
- package/src/sequencer/sequencer.ts +268 -141
- package/src/sequencer/timetable.ts +13 -0
package/dest/config.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,KAAK,iBAAiB,EACtB,KAAK,cAAc,EAGpB,MAAM,iBAAiB,CAAC;AACzB,OAAO,EACL,KAAK,kBAAkB,EAKxB,MAAM,0BAA0B,CAAC;AAElC,OAAO,EAAE,KAAK,cAAc,EAA0B,MAAM,sBAAsB,CAAC;AACnF,OAAO,EAAE,KAAK,SAAS,EAAqB,MAAM,YAAY,CAAC;AAE/D,OAAO,EAAE,KAAK,WAAW,EAAE,KAAK,eAAe,EAAuB,MAAM,sBAAsB,CAAC;AACnG,OAAO,EAAE,KAAK,qBAAqB,EAAiC,MAAM,yBAAyB,CAAC;AAEpG,OAAO,EACL,KAAK,eAAe,EACpB,KAAK,cAAc,EAGpB,MAAM,uBAAuB,CAAC;AAE/B,cAAc,uBAAuB,CAAC;AACtC,YAAY,EAAE,eAAe,EAAE,CAAC;AAEhC,eAAO,MAAM,oCAAoC,IAAI,CAAC;AAEtD;;GAEG;AACH,MAAM,MAAM,qBAAqB,GAAG,eAAe,GACjD,cAAc,GACd,qBAAqB,GACrB,cAAc,GACd,eAAe,GACf,cAAc,GACd,WAAW,GACX,IAAI,CAAC,SAAS,EAAE,wBAAwB,CAAC,GACzC,IAAI,CAAC,iBAAiB,EAAE,sBAAsB,GAAG,mBAAmB,GAAG,oBAAoB,CAAC,CAAC;AAE/F,eAAO,MAAM,uBAAuB,EAAE,kBAAkB,CAAC,eAAe,
|
|
1
|
+
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,KAAK,iBAAiB,EACtB,KAAK,cAAc,EAGpB,MAAM,iBAAiB,CAAC;AACzB,OAAO,EACL,KAAK,kBAAkB,EAKxB,MAAM,0BAA0B,CAAC;AAElC,OAAO,EAAE,KAAK,cAAc,EAA0B,MAAM,sBAAsB,CAAC;AACnF,OAAO,EAAE,KAAK,SAAS,EAAqB,MAAM,YAAY,CAAC;AAE/D,OAAO,EAAE,KAAK,WAAW,EAAE,KAAK,eAAe,EAAuB,MAAM,sBAAsB,CAAC;AACnG,OAAO,EAAE,KAAK,qBAAqB,EAAiC,MAAM,yBAAyB,CAAC;AAEpG,OAAO,EACL,KAAK,eAAe,EACpB,KAAK,cAAc,EAGpB,MAAM,uBAAuB,CAAC;AAE/B,cAAc,uBAAuB,CAAC;AACtC,YAAY,EAAE,eAAe,EAAE,CAAC;AAEhC,eAAO,MAAM,oCAAoC,IAAI,CAAC;AAEtD;;GAEG;AACH,MAAM,MAAM,qBAAqB,GAAG,eAAe,GACjD,cAAc,GACd,qBAAqB,GACrB,cAAc,GACd,eAAe,GACf,cAAc,GACd,WAAW,GACX,IAAI,CAAC,SAAS,EAAE,wBAAwB,CAAC,GACzC,IAAI,CAAC,iBAAiB,EAAE,sBAAsB,GAAG,mBAAmB,GAAG,oBAAoB,CAAC,CAAC;AAE/F,eAAO,MAAM,uBAAuB,EAAE,kBAAkB,CAAC,eAAe,CA2GvE,CAAC;AAEF,eAAO,MAAM,6BAA6B,EAAE,kBAAkB,CAAC,qBAAqB,CASnF,CAAC;AAEF;;GAEG;AACH,wBAAgB,gBAAgB,IAAI,qBAAqB,CAExD"}
|
package/dest/config.js
CHANGED
|
@@ -106,6 +106,10 @@ export const sequencerConfigMappings = {
|
|
|
106
106
|
description: 'Do not invalidate the previous block if invalid when we are the proposer (for testing only)',
|
|
107
107
|
...booleanConfigHelper(false)
|
|
108
108
|
},
|
|
109
|
+
injectFakeAttestation: {
|
|
110
|
+
description: 'Inject a fake attestation (for testing only)',
|
|
111
|
+
...booleanConfigHelper(false)
|
|
112
|
+
},
|
|
109
113
|
...pickConfigMappings(p2pConfigMappings, [
|
|
110
114
|
'txPublicSetupAllowList'
|
|
111
115
|
])
|
|
@@ -5,10 +5,10 @@ import { EthAddress } from '@aztec/foundation/eth-address';
|
|
|
5
5
|
import { Signature } from '@aztec/foundation/eth-signature';
|
|
6
6
|
import { Fr } from '@aztec/foundation/fields';
|
|
7
7
|
import { type DateProvider } from '@aztec/foundation/timer';
|
|
8
|
-
import type
|
|
8
|
+
import { type TypedEventEmitter } from '@aztec/foundation/types';
|
|
9
9
|
import type { P2P } from '@aztec/p2p';
|
|
10
10
|
import type { SlasherClientInterface } from '@aztec/slasher';
|
|
11
|
-
import {
|
|
11
|
+
import { CommitteeAttestation, CommitteeAttestationsAndSigners, type L2BlockSource, type ValidateBlockResult } from '@aztec/stdlib/block';
|
|
12
12
|
import { type L1RollupConstants } from '@aztec/stdlib/epoch-helpers';
|
|
13
13
|
import { type IFullNodeBlockBuilder, type PublicProcessorLimits, type WorldStateSynchronizer } from '@aztec/stdlib/interfaces/server';
|
|
14
14
|
import type { L1ToL2MessageSource } from '@aztec/stdlib/messaging';
|
|
@@ -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":"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;
|
|
1
|
+
{"version":3,"file":"sequencer.d.ts","sourceRoot":"","sources":["../../src/sequencer/sequencer.ts"],"names":[],"mappings":"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"}
|
|
@@ -7,12 +7,14 @@ function _ts_decorate(decorators, target, key, desc) {
|
|
|
7
7
|
import { BLOBS_PER_BLOCK, FIELDS_PER_BLOB, INITIAL_L2_BLOCK_NUM } from '@aztec/constants';
|
|
8
8
|
import { FormattedViemError, NoCommitteeError } from '@aztec/ethereum';
|
|
9
9
|
import { omit, pick } from '@aztec/foundation/collection';
|
|
10
|
+
import { randomInt } from '@aztec/foundation/crypto';
|
|
10
11
|
import { EthAddress } from '@aztec/foundation/eth-address';
|
|
11
12
|
import { Signature } from '@aztec/foundation/eth-signature';
|
|
12
13
|
import { Fr } from '@aztec/foundation/fields';
|
|
13
14
|
import { createLogger } from '@aztec/foundation/log';
|
|
14
15
|
import { RunningPromise } from '@aztec/foundation/running-promise';
|
|
15
16
|
import { Timer } from '@aztec/foundation/timer';
|
|
17
|
+
import { unfreeze } from '@aztec/foundation/types';
|
|
16
18
|
import { CommitteeAttestationsAndSigners } from '@aztec/stdlib/block';
|
|
17
19
|
import { getSlotAtTimestamp, getSlotStartBuildTimestamp } from '@aztec/stdlib/epoch-helpers';
|
|
18
20
|
import { Gas } from '@aztec/stdlib/gas';
|
|
@@ -65,6 +67,7 @@ export { SequencerState };
|
|
|
65
67
|
metrics;
|
|
66
68
|
lastBlockPublished;
|
|
67
69
|
governanceProposerPayload;
|
|
70
|
+
/** The last slot for which we attempted to vote when sync failed, to prevent duplicate attempts. */ lastSlotForVoteWhenSyncFailed;
|
|
68
71
|
/** The maximum number of seconds that the sequencer can be into a slot to transition to a particular state. */ timetable;
|
|
69
72
|
enforceTimeTable;
|
|
70
73
|
// This shouldn't be here as this gets re-created each time we build/propose a block.
|
|
@@ -140,7 +143,7 @@ export { SequencerState };
|
|
|
140
143
|
/**
|
|
141
144
|
* Starts the sequencer and moves to IDLE state.
|
|
142
145
|
*/ start() {
|
|
143
|
-
this.runningPromise = new RunningPromise(this.
|
|
146
|
+
this.runningPromise = new RunningPromise(this.safeWork.bind(this), this.log, this.pollingIntervalMs);
|
|
144
147
|
this.setState(SequencerState.IDLE, undefined, {
|
|
145
148
|
force: true
|
|
146
149
|
});
|
|
@@ -176,21 +179,28 @@ export { SequencerState };
|
|
|
176
179
|
* - Collect attestations for the block
|
|
177
180
|
* - Submit block
|
|
178
181
|
* - If our block for some reason is not included, revert the state
|
|
179
|
-
*/ async
|
|
182
|
+
*/ async work() {
|
|
180
183
|
this.setState(SequencerState.SYNCHRONIZING, undefined);
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
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
|
+
});
|
|
184
195
|
if (!syncedTo) {
|
|
196
|
+
await this.tryVoteWhenSyncFails({
|
|
197
|
+
slot,
|
|
198
|
+
ts
|
|
199
|
+
});
|
|
185
200
|
return;
|
|
186
201
|
}
|
|
187
202
|
const chainTipArchive = syncedTo.archive;
|
|
188
203
|
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
204
|
const syncLogData = {
|
|
195
205
|
now,
|
|
196
206
|
syncedToL1Ts: syncedTo.l1Timestamp,
|
|
@@ -201,67 +211,35 @@ export { SequencerState };
|
|
|
201
211
|
newBlockNumber,
|
|
202
212
|
isPendingChainValid: pick(syncedTo.pendingChainValidationStatus, 'valid', 'reason', 'invalidIndex')
|
|
203
213
|
};
|
|
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
|
|
214
|
+
// Check that the slot is not taken by a block already (should never happen, since only us can propose for this slot)
|
|
209
215
|
if (syncedTo.block && syncedTo.block.header.getSlot() >= slot) {
|
|
210
|
-
this.log.
|
|
216
|
+
this.log.warn(`Cannot propose block at next L2 slot ${slot} since that slot was taken by block ${syncedTo.blockNumber}`, {
|
|
211
217
|
...syncLogData,
|
|
212
218
|
block: syncedTo.block.header.toInspect()
|
|
213
219
|
});
|
|
214
220
|
return;
|
|
215
221
|
}
|
|
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
222
|
// 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
|
-
}
|
|
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);
|
|
249
228
|
return;
|
|
250
229
|
}
|
|
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
230
|
// We now need to get ourselves a publisher.
|
|
255
231
|
// The returned attestor will be the one we provided if we provided one.
|
|
256
232
|
// Otherwise it will be a valid attestor for the returned publisher.
|
|
257
|
-
const { attestorAddress, publisher } = await this.publisherFactory.create(
|
|
233
|
+
const { attestorAddress, publisher } = await this.publisherFactory.create(proposer);
|
|
258
234
|
this.log.verbose(`Created publisher at address ${publisher.getSenderAddress()} for attestor ${attestorAddress}`);
|
|
259
235
|
this.publisher = publisher;
|
|
260
236
|
const coinbase = this.validatorClient.getCoinbaseForAttestor(attestorAddress);
|
|
261
237
|
const feeRecipient = this.validatorClient.getFeeRecipientForAttestor(attestorAddress);
|
|
262
238
|
// Prepare invalidation request if the pending chain is invalid (returns undefined if no need)
|
|
263
239
|
const invalidateBlock = await publisher.simulateInvalidateBlock(syncedTo.pendingChainValidationStatus);
|
|
264
|
-
|
|
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);
|
|
265
243
|
if (canProposeCheck === undefined) {
|
|
266
244
|
this.log.warn(`Cannot propose block ${newBlockNumber} at slot ${slot} due to failed rollup contract check`, syncLogData);
|
|
267
245
|
this.emit('proposer-rollup-check-failed', {
|
|
@@ -291,43 +269,46 @@ export { SequencerState };
|
|
|
291
269
|
});
|
|
292
270
|
return;
|
|
293
271
|
}
|
|
294
|
-
this.log.debug(`Can propose block ${newBlockNumber} at slot ${slot}
|
|
295
|
-
...syncLogData
|
|
296
|
-
validatorAddresses
|
|
272
|
+
this.log.debug(`Can propose block ${newBlockNumber} at slot ${slot} as ${proposer}`, {
|
|
273
|
+
...syncLogData
|
|
297
274
|
});
|
|
298
275
|
const newGlobalVariables = await this.globalsBuilder.buildGlobalVariables(newBlockNumber, coinbase, feeRecipient, slot);
|
|
299
|
-
|
|
300
|
-
const
|
|
301
|
-
|
|
302
|
-
this.log.error(`Error enqueuing governance vote`, err, {
|
|
303
|
-
blockNumber: newBlockNumber,
|
|
304
|
-
slot
|
|
305
|
-
});
|
|
306
|
-
return false;
|
|
307
|
-
}) : Promise.resolve(false);
|
|
308
|
-
const enqueueSlashingActionsPromise = this.slasherClient ? this.slasherClient.getProposerActions(slot).then((actions)=>publisher.enqueueSlashingActions(actions, slot, timestamp, attestorAddress, signerFn)).catch((err)=>{
|
|
309
|
-
this.log.error(`Error enqueuing slashing actions`, err, {
|
|
310
|
-
blockNumber: newBlockNumber,
|
|
311
|
-
slot
|
|
312
|
-
});
|
|
313
|
-
return false;
|
|
314
|
-
}) : 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
|
|
315
279
|
if (invalidateBlock && !this.config.skipInvalidateBlockAsProposer) {
|
|
316
280
|
publisher.enqueueInvalidateBlock(invalidateBlock);
|
|
317
281
|
}
|
|
282
|
+
// Actual block building
|
|
318
283
|
this.setState(SequencerState.INITIALIZING_PROPOSAL, slot);
|
|
319
|
-
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());
|
|
320
304
|
this.log.verbose(`Preparing proposal for block ${newBlockNumber} at slot ${slot}`, {
|
|
321
|
-
proposer
|
|
322
|
-
coinbase,
|
|
305
|
+
proposer,
|
|
323
306
|
publisher: publisher.getSenderAddress(),
|
|
324
|
-
feeRecipient,
|
|
325
307
|
globalVariables: newGlobalVariables.toInspect(),
|
|
326
308
|
chainTipArchive,
|
|
327
309
|
blockNumber: newBlockNumber,
|
|
328
310
|
slot
|
|
329
311
|
});
|
|
330
|
-
// If I created a "partial" header here that should make our job much easier.
|
|
331
312
|
const proposalHeader = ProposedBlockHeader.from({
|
|
332
313
|
...newGlobalVariables,
|
|
333
314
|
timestamp: newGlobalVariables.timestamp,
|
|
@@ -342,7 +323,7 @@ export { SequencerState };
|
|
|
342
323
|
// and also we may need to fetch more if we don't have enough valid txs.
|
|
343
324
|
const pendingTxs = this.p2pClient.iteratePendingTxs();
|
|
344
325
|
try {
|
|
345
|
-
block = await this.buildBlockAndEnqueuePublish(pendingTxs, proposalHeader, newGlobalVariables,
|
|
326
|
+
block = await this.buildBlockAndEnqueuePublish(pendingTxs, proposalHeader, newGlobalVariables, proposer, invalidateBlock, publisher);
|
|
346
327
|
} catch (err) {
|
|
347
328
|
this.emit('block-build-failed', {
|
|
348
329
|
reason: err.message
|
|
@@ -367,27 +348,11 @@ export { SequencerState };
|
|
|
367
348
|
availableTxs: pendingTxCount
|
|
368
349
|
});
|
|
369
350
|
}
|
|
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
|
-
await this.metrics.incFilledSlot(publisher.getSenderAddress().toString(), coinbase);
|
|
383
|
-
} else if (block) {
|
|
384
|
-
this.emit('block-publish-failed', l1Response ?? {});
|
|
385
|
-
}
|
|
386
|
-
this.setState(SequencerState.IDLE, undefined);
|
|
351
|
+
return block;
|
|
387
352
|
}
|
|
388
|
-
async
|
|
353
|
+
async safeWork() {
|
|
389
354
|
try {
|
|
390
|
-
await this.
|
|
355
|
+
await this.work();
|
|
391
356
|
} catch (err) {
|
|
392
357
|
if (err instanceof SequencerTooSlowError) {
|
|
393
358
|
// Log as warn only if we had to abort halfway through the block proposal
|
|
@@ -573,7 +538,14 @@ export { SequencerState };
|
|
|
573
538
|
const attestations = await this.validatorClient.collectAttestations(proposal, numberOfRequiredAttestations, attestationDeadline);
|
|
574
539
|
collectedAttestationsCount = attestations.length;
|
|
575
540
|
// note: the smart contract requires that the signatures are provided in the order of the committee
|
|
576
|
-
|
|
541
|
+
const sorted = orderAttestations(attestations, committee);
|
|
542
|
+
if (this.config.injectFakeAttestation) {
|
|
543
|
+
const nonEmpty = sorted.filter((a)=>!a.signature.isEmpty());
|
|
544
|
+
const randomIndex = randomInt(nonEmpty.length);
|
|
545
|
+
this.log.warn(`Injecting fake attestation in block ${block.number}`);
|
|
546
|
+
unfreeze(nonEmpty[randomIndex]).signature = Signature.random();
|
|
547
|
+
}
|
|
548
|
+
return sorted;
|
|
577
549
|
} catch (err) {
|
|
578
550
|
if (err && err instanceof AttestationTimeoutError) {
|
|
579
551
|
collectedAttestationsCount = err.collectedCount;
|
|
@@ -603,8 +575,7 @@ export { SequencerState };
|
|
|
603
575
|
/**
|
|
604
576
|
* Returns whether all dependencies have caught up.
|
|
605
577
|
* We don't check against the previous block submitted since it may have been reorg'd out.
|
|
606
|
-
|
|
607
|
-
*/ async getChainTip() {
|
|
578
|
+
*/ async checkSync(args) {
|
|
608
579
|
const syncedBlocks = await Promise.all([
|
|
609
580
|
this.worldState.status().then(({ syncSummary })=>({
|
|
610
581
|
number: syncSummary.latestBlockNumber,
|
|
@@ -620,32 +591,30 @@ export { SequencerState };
|
|
|
620
591
|
// The archiver reports 'undefined' hash for the genesis block
|
|
621
592
|
// because it doesn't have access to world state to compute it (facepalm)
|
|
622
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;
|
|
623
|
-
const logData = {
|
|
624
|
-
worldState,
|
|
625
|
-
l2BlockSource,
|
|
626
|
-
p2p,
|
|
627
|
-
l1ToL2MessageSource
|
|
628
|
-
};
|
|
629
|
-
this.log.debug(`Sequencer sync check ${result ? 'succeeded' : 'failed'}`, logData);
|
|
630
594
|
if (!result) {
|
|
595
|
+
this.log.debug(`Sequencer sync check failed`, {
|
|
596
|
+
worldState,
|
|
597
|
+
l2BlockSource,
|
|
598
|
+
p2p,
|
|
599
|
+
l1ToL2MessageSource
|
|
600
|
+
});
|
|
631
601
|
return undefined;
|
|
632
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
|
+
});
|
|
613
|
+
return undefined;
|
|
614
|
+
}
|
|
615
|
+
// Special case for genesis state
|
|
633
616
|
const blockNumber = worldState.number;
|
|
634
|
-
if (blockNumber
|
|
635
|
-
const block = await this.l2BlockSource.getBlock(blockNumber);
|
|
636
|
-
if (!block) {
|
|
637
|
-
// this shouldn't really happen because a moment ago we checked that all components were in sync
|
|
638
|
-
this.log.warn(`Failed to get L2 block ${blockNumber} from the archiver with all components in sync`, logData);
|
|
639
|
-
return undefined;
|
|
640
|
-
}
|
|
641
|
-
return {
|
|
642
|
-
block,
|
|
643
|
-
blockNumber: block.number,
|
|
644
|
-
archive: block.archive.root,
|
|
645
|
-
l1Timestamp,
|
|
646
|
-
pendingChainValidationStatus
|
|
647
|
-
};
|
|
648
|
-
} else {
|
|
617
|
+
if (blockNumber < INITIAL_L2_BLOCK_NUM) {
|
|
649
618
|
const archive = new Fr((await this.worldState.getCommitted().getTreeInfo(MerkleTreeId.ARCHIVE)).root);
|
|
650
619
|
return {
|
|
651
620
|
blockNumber: INITIAL_L2_BLOCK_NUM - 1,
|
|
@@ -654,20 +623,170 @@ export { SequencerState };
|
|
|
654
623
|
pendingChainValidationStatus
|
|
655
624
|
};
|
|
656
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();
|
|
657
774
|
}
|
|
658
775
|
/**
|
|
659
776
|
* Considers invalidating a block if the pending chain is invalid. Depends on how long the invalid block
|
|
660
777
|
* has been there without being invalidated and whether the sequencer is in the committee or not. We always
|
|
661
778
|
* have the proposer try to invalidate, but if they fail, the sequencers in the committee are expected to try,
|
|
662
779
|
* and if they fail, any sequencer will try as well.
|
|
663
|
-
*/ async considerInvalidatingBlock(syncedTo, currentSlot
|
|
780
|
+
*/ async considerInvalidatingBlock(syncedTo, currentSlot) {
|
|
664
781
|
const { pendingChainValidationStatus, l1Timestamp } = syncedTo;
|
|
665
782
|
if (pendingChainValidationStatus.valid) {
|
|
666
783
|
return;
|
|
667
784
|
}
|
|
785
|
+
const { publisher } = await this.publisherFactory.create(undefined);
|
|
668
786
|
const invalidBlockNumber = pendingChainValidationStatus.block.blockNumber;
|
|
669
787
|
const invalidBlockTimestamp = pendingChainValidationStatus.block.timestamp;
|
|
670
788
|
const timeSinceChainInvalid = this.dateProvider.nowInSeconds() - Number(invalidBlockTimestamp);
|
|
789
|
+
const ourValidatorAddresses = this.validatorClient.getValidatorAddresses();
|
|
671
790
|
const { secondsBeforeInvalidatingBlockAsCommitteeMember, secondsBeforeInvalidatingBlockAsNonCommitteeMember } = this.config;
|
|
672
791
|
const logData = {
|
|
673
792
|
invalidL1Timestamp: invalidBlockTimestamp,
|
|
@@ -713,7 +832,7 @@ export { SequencerState };
|
|
|
713
832
|
}
|
|
714
833
|
_ts_decorate([
|
|
715
834
|
trackSpan('Sequencer.work')
|
|
716
|
-
], Sequencer.prototype, "
|
|
835
|
+
], Sequencer.prototype, "safeWork", null);
|
|
717
836
|
_ts_decorate([
|
|
718
837
|
trackSpan('Sequencer.buildBlockAndEnqueuePublish', (_validTxs, _proposalHeader, newGlobalVariables)=>({
|
|
719
838
|
[Attributes.BLOCK_NUMBER]: newGlobalVariables.blockNumber
|