@aztec/sequencer-client 1.2.1 → 2.0.0-nightly.20250814
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/client/sequencer-client.d.ts +0 -2
- package/dest/client/sequencer-client.d.ts.map +1 -1
- package/dest/client/sequencer-client.js +5 -7
- package/dest/config.d.ts +1 -0
- package/dest/config.d.ts.map +1 -1
- package/dest/config.js +27 -0
- package/dest/publisher/index.d.ts +1 -1
- package/dest/publisher/index.d.ts.map +1 -1
- package/dest/publisher/index.js +1 -1
- package/dest/publisher/sequencer-publisher.d.ts +48 -33
- package/dest/publisher/sequencer-publisher.d.ts.map +1 -1
- package/dest/publisher/sequencer-publisher.js +272 -114
- package/dest/sequencer/block_builder.d.ts +2 -7
- package/dest/sequencer/block_builder.d.ts.map +1 -1
- package/dest/sequencer/block_builder.js +3 -7
- package/dest/sequencer/sequencer.d.ts +27 -15
- package/dest/sequencer/sequencer.d.ts.map +1 -1
- package/dest/sequencer/sequencer.js +141 -85
- package/dest/sequencer/timetable.d.ts +15 -5
- package/dest/sequencer/timetable.d.ts.map +1 -1
- package/dest/sequencer/timetable.js +25 -12
- package/dest/sequencer/utils.d.ts +1 -11
- package/dest/sequencer/utils.d.ts.map +1 -1
- package/dest/sequencer/utils.js +0 -23
- package/package.json +26 -26
- package/src/client/sequencer-client.ts +4 -8
- package/src/config.ts +33 -0
- package/src/publisher/index.ts +1 -1
- package/src/publisher/sequencer-publisher.ts +318 -131
- package/src/sequencer/block_builder.ts +15 -8
- package/src/sequencer/sequencer.ts +217 -87
- package/src/sequencer/timetable.ts +43 -7
- package/src/sequencer/utils.ts +6 -37
|
@@ -6,8 +6,7 @@ function _ts_decorate(decorators, target, key, desc) {
|
|
|
6
6
|
}
|
|
7
7
|
import { INITIAL_L2_BLOCK_NUM } from '@aztec/constants';
|
|
8
8
|
import { FormattedViemError, NoCommitteeError } from '@aztec/ethereum';
|
|
9
|
-
import {
|
|
10
|
-
import { omit } from '@aztec/foundation/collection';
|
|
9
|
+
import { omit, pick } from '@aztec/foundation/collection';
|
|
11
10
|
import { EthAddress } from '@aztec/foundation/eth-address';
|
|
12
11
|
import { Fr } from '@aztec/foundation/fields';
|
|
13
12
|
import { createLogger } from '@aztec/foundation/log';
|
|
@@ -17,16 +16,17 @@ import { AztecAddress } from '@aztec/stdlib/aztec-address';
|
|
|
17
16
|
import { getSlotAtTimestamp } from '@aztec/stdlib/epoch-helpers';
|
|
18
17
|
import { Gas } from '@aztec/stdlib/gas';
|
|
19
18
|
import { SequencerConfigSchema } from '@aztec/stdlib/interfaces/server';
|
|
19
|
+
import { orderAttestations } from '@aztec/stdlib/p2p';
|
|
20
20
|
import { pickFromSchema } from '@aztec/stdlib/schemas';
|
|
21
21
|
import { MerkleTreeId } from '@aztec/stdlib/trees';
|
|
22
|
-
import { ContentCommitment, ProposedBlockHeader
|
|
22
|
+
import { ContentCommitment, ProposedBlockHeader } from '@aztec/stdlib/tx';
|
|
23
23
|
import { AttestationTimeoutError } from '@aztec/stdlib/validators';
|
|
24
24
|
import { Attributes, L1Metrics, getTelemetryClient, trackSpan } from '@aztec/telemetry-client';
|
|
25
25
|
import EventEmitter from 'node:events';
|
|
26
|
-
import {
|
|
26
|
+
import { SignalType } from '../publisher/sequencer-publisher.js';
|
|
27
27
|
import { SequencerMetrics } from './metrics.js';
|
|
28
28
|
import { SequencerTimetable, SequencerTooSlowError } from './timetable.js';
|
|
29
|
-
import { SequencerState
|
|
29
|
+
import { SequencerState } from './utils.js';
|
|
30
30
|
export { SequencerState };
|
|
31
31
|
/**
|
|
32
32
|
* Sequencer client
|
|
@@ -65,17 +65,18 @@ export { SequencerState };
|
|
|
65
65
|
metrics;
|
|
66
66
|
l1Metrics;
|
|
67
67
|
lastBlockPublished;
|
|
68
|
-
isFlushing;
|
|
69
68
|
/** The maximum number of seconds that the sequencer can be into a slot to transition to a particular state. */ timetable;
|
|
70
69
|
enforceTimeTable;
|
|
71
70
|
constructor(publisher, validatorClient, globalsBuilder, p2pClient, worldState, slasherClient, l2BlockSource, l1ToL2MessageSource, blockBuilder, l1Constants, dateProvider, config = {}, telemetry = getTelemetryClient(), log = createLogger('sequencer')){
|
|
72
|
-
super(), this.publisher = publisher, this.validatorClient = validatorClient, this.globalsBuilder = globalsBuilder, this.p2pClient = p2pClient, this.worldState = worldState, this.slasherClient = slasherClient, this.l2BlockSource = l2BlockSource, this.l1ToL2MessageSource = l1ToL2MessageSource, this.blockBuilder = blockBuilder, this.l1Constants = l1Constants, this.dateProvider = dateProvider, this.config = config, this.telemetry = telemetry, this.log = log, this.pollingIntervalMs = 1000, this.maxTxsPerBlock = 32, this.minTxsPerBlock = 1, this.maxL1TxInclusionTimeIntoSlot = 0, this._coinbase = EthAddress.ZERO, this._feeRecipient = AztecAddress.ZERO, this.state = SequencerState.STOPPED, this.maxBlockSizeInBytes = 1024 * 1024, this.maxBlockGas = new Gas(100e9, 100e9), this.
|
|
71
|
+
super(), this.publisher = publisher, this.validatorClient = validatorClient, this.globalsBuilder = globalsBuilder, this.p2pClient = p2pClient, this.worldState = worldState, this.slasherClient = slasherClient, this.l2BlockSource = l2BlockSource, this.l1ToL2MessageSource = l1ToL2MessageSource, this.blockBuilder = blockBuilder, this.l1Constants = l1Constants, this.dateProvider = dateProvider, this.config = config, this.telemetry = telemetry, this.log = log, this.pollingIntervalMs = 1000, this.maxTxsPerBlock = 32, this.minTxsPerBlock = 1, this.maxL1TxInclusionTimeIntoSlot = 0, this._coinbase = EthAddress.ZERO, this._feeRecipient = AztecAddress.ZERO, this.state = SequencerState.STOPPED, this.maxBlockSizeInBytes = 1024 * 1024, this.maxBlockGas = new Gas(100e9, 100e9), this.enforceTimeTable = false;
|
|
73
72
|
this.metrics = new SequencerMetrics(telemetry, ()=>this.state, this.config.coinbase ?? this.publisher.getSenderAddress(), this.publisher.getRollupContract(), 'Sequencer');
|
|
74
73
|
this.l1Metrics = new L1Metrics(telemetry.getMeter('SequencerL1Metrics'), publisher.l1TxUtils.client, [
|
|
75
74
|
publisher.getSenderAddress()
|
|
76
75
|
]);
|
|
77
76
|
// Register the slasher on the publisher to fetch slashing payloads
|
|
78
77
|
this.publisher.registerSlashPayloadGetter(this.slasherClient.getSlashPayload.bind(this.slasherClient));
|
|
78
|
+
// Initialize config
|
|
79
|
+
this.updateConfig(this.config);
|
|
79
80
|
}
|
|
80
81
|
get tracer() {
|
|
81
82
|
return this.metrics.tracer;
|
|
@@ -83,6 +84,9 @@ export { SequencerState };
|
|
|
83
84
|
getValidatorAddresses() {
|
|
84
85
|
return this.validatorClient?.getValidatorAddresses();
|
|
85
86
|
}
|
|
87
|
+
getConfig() {
|
|
88
|
+
return this.config;
|
|
89
|
+
}
|
|
86
90
|
/**
|
|
87
91
|
* Updates sequencer config by the defined values in the config on input.
|
|
88
92
|
* @param config - New parameters.
|
|
@@ -128,18 +132,22 @@ export { SequencerState };
|
|
|
128
132
|
Object.assign(this.config, config);
|
|
129
133
|
}
|
|
130
134
|
setTimeTable() {
|
|
131
|
-
this.timetable = new SequencerTimetable(
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
+
this.timetable = new SequencerTimetable({
|
|
136
|
+
ethereumSlotDuration: this.l1Constants.ethereumSlotDuration,
|
|
137
|
+
aztecSlotDuration: this.aztecSlotDuration,
|
|
138
|
+
maxL1TxInclusionTimeIntoSlot: this.maxL1TxInclusionTimeIntoSlot,
|
|
139
|
+
attestationPropagationTime: this.config.attestationPropagationTime,
|
|
140
|
+
enforce: this.enforceTimeTable
|
|
141
|
+
}, this.metrics, this.log);
|
|
135
142
|
}
|
|
136
143
|
/**
|
|
137
144
|
* Starts the sequencer and moves to IDLE state.
|
|
138
145
|
*/ start() {
|
|
139
|
-
this.updateConfig(this.config);
|
|
140
146
|
this.metrics.start();
|
|
141
147
|
this.runningPromise = new RunningPromise(this.work.bind(this), this.log, this.pollingIntervalMs);
|
|
142
|
-
this.setState(SequencerState.IDLE,
|
|
148
|
+
this.setState(SequencerState.IDLE, undefined, {
|
|
149
|
+
force: true
|
|
150
|
+
});
|
|
143
151
|
this.runningPromise.start();
|
|
144
152
|
this.l1Metrics.start();
|
|
145
153
|
this.log.info(`Sequencer started with address ${this.publisher.getSenderAddress().toString()}`);
|
|
@@ -147,12 +155,14 @@ export { SequencerState };
|
|
|
147
155
|
/**
|
|
148
156
|
* Stops the sequencer from processing txs and moves to STOPPED state.
|
|
149
157
|
*/ async stop() {
|
|
150
|
-
this.log.
|
|
158
|
+
this.log.info(`Stopping sequencer`);
|
|
151
159
|
this.metrics.stop();
|
|
152
160
|
await this.validatorClient?.stop();
|
|
153
161
|
await this.runningPromise?.stop();
|
|
154
162
|
this.publisher.interrupt();
|
|
155
|
-
this.setState(SequencerState.STOPPED,
|
|
163
|
+
this.setState(SequencerState.STOPPED, undefined, {
|
|
164
|
+
force: true
|
|
165
|
+
});
|
|
156
166
|
this.l1Metrics.stop();
|
|
157
167
|
this.log.info('Stopped sequencer');
|
|
158
168
|
}
|
|
@@ -162,7 +172,9 @@ export { SequencerState };
|
|
|
162
172
|
this.log.info('Restarting sequencer');
|
|
163
173
|
this.publisher.restart();
|
|
164
174
|
this.runningPromise.start();
|
|
165
|
-
this.setState(SequencerState.IDLE,
|
|
175
|
+
this.setState(SequencerState.IDLE, undefined, {
|
|
176
|
+
force: true
|
|
177
|
+
});
|
|
166
178
|
}
|
|
167
179
|
/**
|
|
168
180
|
* Returns the current state of the sequencer.
|
|
@@ -172,9 +184,6 @@ export { SequencerState };
|
|
|
172
184
|
state: this.state
|
|
173
185
|
};
|
|
174
186
|
}
|
|
175
|
-
/** Forces the sequencer to bypass all time and tx count checks for the next block and build anyway. */ flush() {
|
|
176
|
-
this.isFlushing = true;
|
|
177
|
-
}
|
|
178
187
|
/**
|
|
179
188
|
* @notice Performs most of the sequencer duties:
|
|
180
189
|
* - Checks if we are up to date
|
|
@@ -183,14 +192,14 @@ export { SequencerState };
|
|
|
183
192
|
* - Submit block
|
|
184
193
|
* - If our block for some reason is not included, revert the state
|
|
185
194
|
*/ async doRealWork() {
|
|
186
|
-
this.setState(SequencerState.SYNCHRONIZING,
|
|
195
|
+
this.setState(SequencerState.SYNCHRONIZING, undefined);
|
|
187
196
|
// Check all components are synced to latest as seen by the archiver
|
|
188
197
|
const syncedTo = await this.getChainTip();
|
|
189
198
|
// Do not go forward with new block if the previous one has not been mined and processed
|
|
190
199
|
if (!syncedTo) {
|
|
191
200
|
return;
|
|
192
201
|
}
|
|
193
|
-
this.setState(SequencerState.PROPOSER_CHECK,
|
|
202
|
+
this.setState(SequencerState.PROPOSER_CHECK, undefined);
|
|
194
203
|
const chainTipArchive = syncedTo.archive;
|
|
195
204
|
const newBlockNumber = syncedTo.blockNumber + 1;
|
|
196
205
|
const { slot, ts, now } = this.publisher.epochCache.getEpochAndSlotInNextL1Slot();
|
|
@@ -204,7 +213,9 @@ export { SequencerState };
|
|
|
204
213
|
syncedToL2Slot: getSlotAtTimestamp(syncedTo.l1Timestamp, this.l1Constants),
|
|
205
214
|
nextL2Slot: slot,
|
|
206
215
|
nextL2SlotTs: ts,
|
|
207
|
-
l1SlotDuration: this.l1Constants.ethereumSlotDuration
|
|
216
|
+
l1SlotDuration: this.l1Constants.ethereumSlotDuration,
|
|
217
|
+
newBlockNumber,
|
|
218
|
+
isPendingChainValid: pick(syncedTo.pendingChainValidationStatus, 'valid', 'reason', 'invalidIndex')
|
|
208
219
|
};
|
|
209
220
|
if (syncedTo.l1Timestamp + BigInt(this.l1Constants.ethereumSlotDuration) < ts) {
|
|
210
221
|
this.log.debug(`Cannot propose block ${newBlockNumber} at next L2 slot ${slot} due to pending sync from L1`, syncLogData);
|
|
@@ -226,32 +237,37 @@ export { SequencerState };
|
|
|
226
237
|
});
|
|
227
238
|
return;
|
|
228
239
|
}
|
|
240
|
+
// Check that we are a proposer for the next slot
|
|
229
241
|
let proposerInNextSlot;
|
|
230
242
|
try {
|
|
231
|
-
// Check that we are a proposer for the next slot
|
|
232
243
|
proposerInNextSlot = await this.publisher.epochCache.getProposerAttesterAddressInNextSlot();
|
|
233
244
|
} catch (e) {
|
|
234
245
|
if (e instanceof NoCommitteeError) {
|
|
235
|
-
this.log.warn(`Cannot propose block ${newBlockNumber} since the committee does not exist on L1`);
|
|
246
|
+
this.log.warn(`Cannot propose block ${newBlockNumber} at next L2 slot ${slot} since the committee does not exist on L1`);
|
|
236
247
|
return;
|
|
237
248
|
}
|
|
238
249
|
}
|
|
239
|
-
const validatorAddresses = this.validatorClient.getValidatorAddresses();
|
|
240
250
|
// If get proposer in next slot is undefined, then the committee is empty and anyone may propose.
|
|
241
|
-
// If the committee is defined and not empty, but none of our validators are the proposer,
|
|
242
|
-
|
|
251
|
+
// If the committee is defined and not empty, but none of our validators are the proposer, then stop.
|
|
252
|
+
const validatorAddresses = this.validatorClient.getValidatorAddresses();
|
|
243
253
|
if (proposerInNextSlot !== undefined && !validatorAddresses.some((addr)=>addr.equals(proposerInNextSlot))) {
|
|
244
254
|
this.log.debug(`Cannot propose block ${newBlockNumber} since we are not a proposer`, {
|
|
245
255
|
us: validatorAddresses,
|
|
246
256
|
proposer: proposerInNextSlot,
|
|
247
257
|
...syncLogData
|
|
248
258
|
});
|
|
259
|
+
// If the pending chain is invalid, we may need to invalidate the block if no one else is doing it.
|
|
260
|
+
if (!syncedTo.pendingChainValidationStatus.valid) {
|
|
261
|
+
await this.considerInvalidatingBlock(syncedTo, slot, validatorAddresses);
|
|
262
|
+
}
|
|
249
263
|
return;
|
|
250
264
|
}
|
|
251
|
-
//
|
|
252
|
-
|
|
265
|
+
// Prepare invalidation request if the pending chain is invalid (returns undefined if no need)
|
|
266
|
+
const invalidateBlock = await this.publisher.simulateInvalidateBlock(syncedTo.pendingChainValidationStatus);
|
|
267
|
+
// Check with the rollup if we can indeed propose at the next L2 slot. This check should not fail
|
|
268
|
+
// if all the previous checks are good, but we do it just in case.
|
|
253
269
|
const proposerAddress = proposerInNextSlot ?? EthAddress.ZERO;
|
|
254
|
-
const canProposeCheck = await this.publisher.canProposeAtNextEthBlock(chainTipArchive
|
|
270
|
+
const canProposeCheck = await this.publisher.canProposeAtNextEthBlock(chainTipArchive, proposerAddress, invalidateBlock);
|
|
255
271
|
if (canProposeCheck === undefined) {
|
|
256
272
|
this.log.warn(`Cannot propose block ${newBlockNumber} at slot ${slot} due to failed rollup contract check`, syncLogData);
|
|
257
273
|
this.emit('proposer-rollup-check-failed', {
|
|
@@ -281,13 +297,16 @@ export { SequencerState };
|
|
|
281
297
|
});
|
|
282
298
|
return;
|
|
283
299
|
}
|
|
284
|
-
this.log.debug(
|
|
300
|
+
this.log.debug(`Can propose block ${newBlockNumber} at slot ${slot}` + (proposerInNextSlot ? ` as ${proposerInNextSlot}` : ''), {
|
|
285
301
|
...syncLogData,
|
|
286
302
|
validatorAddresses
|
|
287
303
|
});
|
|
288
304
|
const newGlobalVariables = await this.globalsBuilder.buildGlobalVariables(newBlockNumber, this.coinbase, this._feeRecipient, slot);
|
|
289
|
-
const enqueueGovernanceVotePromise = this.publisher.
|
|
290
|
-
const enqueueSlashingVotePromise = this.publisher.
|
|
305
|
+
const enqueueGovernanceVotePromise = this.publisher.enqueueCastSignal(slot, newGlobalVariables.timestamp, SignalType.GOVERNANCE, proposerAddress, (msg)=>this.validatorClient.signWithAddress(proposerAddress, msg).then((s)=>s.toString()));
|
|
306
|
+
const enqueueSlashingVotePromise = this.publisher.enqueueCastSignal(slot, newGlobalVariables.timestamp, SignalType.SLASHING, proposerAddress, (msg)=>this.validatorClient.signWithAddress(proposerAddress, msg).then((s)=>s.toString()));
|
|
307
|
+
if (invalidateBlock && !this.config.skipInvalidateBlockAsProposer) {
|
|
308
|
+
this.publisher.enqueueInvalidateBlock(invalidateBlock);
|
|
309
|
+
}
|
|
291
310
|
this.setState(SequencerState.INITIALIZING_PROPOSAL, slot);
|
|
292
311
|
this.log.verbose(`Preparing proposal for block ${newBlockNumber} at slot ${slot}`, {
|
|
293
312
|
proposer: proposerInNextSlot?.toString(),
|
|
@@ -304,15 +323,14 @@ export { SequencerState };
|
|
|
304
323
|
contentCommitment: ContentCommitment.empty(),
|
|
305
324
|
totalManaUsed: Fr.ZERO
|
|
306
325
|
});
|
|
307
|
-
let finishedFlushing = false;
|
|
308
326
|
let block;
|
|
309
327
|
const pendingTxCount = await this.p2pClient.getPendingTxCount();
|
|
310
|
-
if (pendingTxCount >= this.minTxsPerBlock
|
|
328
|
+
if (pendingTxCount >= this.minTxsPerBlock) {
|
|
311
329
|
// We don't fetch exactly maxTxsPerBlock txs here because we may not need all of them if we hit a limit before,
|
|
312
330
|
// and also we may need to fetch more if we don't have enough valid txs.
|
|
313
331
|
const pendingTxs = this.p2pClient.iteratePendingTxs();
|
|
314
332
|
try {
|
|
315
|
-
block = await this.buildBlockAndEnqueuePublish(pendingTxs, proposalHeader, newGlobalVariables, proposerInNextSlot);
|
|
333
|
+
block = await this.buildBlockAndEnqueuePublish(pendingTxs, proposalHeader, newGlobalVariables, proposerInNextSlot, invalidateBlock);
|
|
316
334
|
} catch (err) {
|
|
317
335
|
this.emit('block-build-failed', {
|
|
318
336
|
reason: err.message
|
|
@@ -325,8 +343,6 @@ export { SequencerState };
|
|
|
325
343
|
slot
|
|
326
344
|
});
|
|
327
345
|
}
|
|
328
|
-
} finally{
|
|
329
|
-
finishedFlushing = true;
|
|
330
346
|
}
|
|
331
347
|
} else {
|
|
332
348
|
this.log.verbose(`Not enough txs to build block ${newBlockNumber} at slot ${slot} (got ${pendingTxCount} txs, need ${this.minTxsPerBlock})`, {
|
|
@@ -352,7 +368,7 @@ export { SequencerState };
|
|
|
352
368
|
});
|
|
353
369
|
});
|
|
354
370
|
const l1Response = await this.publisher.sendRequests();
|
|
355
|
-
const proposedBlock = l1Response?.
|
|
371
|
+
const proposedBlock = l1Response?.successfulActions.find((a)=>a === 'propose');
|
|
356
372
|
if (proposedBlock) {
|
|
357
373
|
this.lastBlockPublished = block;
|
|
358
374
|
this.emit('block-published', {
|
|
@@ -360,16 +376,10 @@ export { SequencerState };
|
|
|
360
376
|
slot: Number(slot)
|
|
361
377
|
});
|
|
362
378
|
this.metrics.incFilledSlot(this.publisher.getSenderAddress().toString());
|
|
363
|
-
if (finishedFlushing) {
|
|
364
|
-
this.isFlushing = false;
|
|
365
|
-
}
|
|
366
379
|
} else if (block) {
|
|
367
|
-
this.emit('block-publish-failed', {
|
|
368
|
-
validActions: l1Response?.validActions,
|
|
369
|
-
expiredActions: l1Response?.expiredActions
|
|
370
|
-
});
|
|
380
|
+
this.emit('block-publish-failed', l1Response ?? {});
|
|
371
381
|
}
|
|
372
|
-
this.setState(SequencerState.IDLE,
|
|
382
|
+
this.setState(SequencerState.IDLE, undefined);
|
|
373
383
|
}
|
|
374
384
|
async work() {
|
|
375
385
|
try {
|
|
@@ -382,28 +392,28 @@ export { SequencerState };
|
|
|
382
392
|
throw err;
|
|
383
393
|
}
|
|
384
394
|
} finally{
|
|
385
|
-
this.setState(SequencerState.IDLE,
|
|
395
|
+
this.setState(SequencerState.IDLE, undefined);
|
|
386
396
|
}
|
|
387
397
|
}
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
* @param proposedState - The new state to transition to.
|
|
391
|
-
* @param currentSlotNumber - The current slot number.
|
|
392
|
-
* @param force - Whether to force the transition even if the sequencer is stopped.
|
|
393
|
-
*
|
|
394
|
-
* @dev If the `currentSlotNumber` doesn't matter (e.g. transitioning to IDLE), pass in `0n`;
|
|
395
|
-
* it is only used to check if we have enough time left in the slot to transition to the new state.
|
|
396
|
-
*/ setState(proposedState, currentSlotNumber, force = false) {
|
|
397
|
-
if (this.state === SequencerState.STOPPED && force !== true) {
|
|
398
|
+
setState(proposedState, slotNumber, opts = {}) {
|
|
399
|
+
if (this.state === SequencerState.STOPPED && !opts.force) {
|
|
398
400
|
this.log.warn(`Cannot set sequencer from ${this.state} to ${proposedState} as it is stopped.`);
|
|
399
401
|
return;
|
|
400
402
|
}
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
403
|
+
let secondsIntoSlot = undefined;
|
|
404
|
+
if (slotNumber !== undefined) {
|
|
405
|
+
secondsIntoSlot = this.getSecondsIntoSlot(slotNumber);
|
|
406
|
+
this.timetable.assertTimeLeft(proposedState, secondsIntoSlot);
|
|
407
|
+
}
|
|
408
|
+
this.log.debug(`Transitioning from ${this.state} to ${proposedState}`, {
|
|
409
|
+
slotNumber,
|
|
410
|
+
secondsIntoSlot
|
|
411
|
+
});
|
|
404
412
|
this.emit('state-changed', {
|
|
405
413
|
oldState: this.state,
|
|
406
|
-
newState: proposedState
|
|
414
|
+
newState: proposedState,
|
|
415
|
+
secondsIntoSlot,
|
|
416
|
+
slotNumber
|
|
407
417
|
});
|
|
408
418
|
this.state = proposedState;
|
|
409
419
|
}
|
|
@@ -412,16 +422,16 @@ export { SequencerState };
|
|
|
412
422
|
return;
|
|
413
423
|
}
|
|
414
424
|
const failedTxData = failedTxs.map((fail)=>fail.tx);
|
|
415
|
-
const failedTxHashes =
|
|
425
|
+
const failedTxHashes = failedTxData.map((tx)=>tx.getTxHash());
|
|
416
426
|
this.log.verbose(`Dropping failed txs ${failedTxHashes.join(', ')}`);
|
|
417
427
|
await this.p2pClient.deleteTxs(failedTxHashes);
|
|
418
428
|
}
|
|
419
|
-
|
|
429
|
+
getBlockBuilderOptions(slot) {
|
|
420
430
|
// Deadline for processing depends on whether we're proposing a block
|
|
421
431
|
const secondsIntoSlot = this.getSecondsIntoSlot(slot);
|
|
422
432
|
const processingEndTimeWithinSlot = this.timetable.getBlockProposalExecTimeEnd(secondsIntoSlot);
|
|
423
433
|
// Deadline is only set if enforceTimeTable is enabled.
|
|
424
|
-
const deadline = this.enforceTimeTable ? new Date((this.
|
|
434
|
+
const deadline = this.enforceTimeTable ? new Date((this.getSlotStartBuildTimestamp(slot) + processingEndTimeWithinSlot) * 1000) : undefined;
|
|
425
435
|
return {
|
|
426
436
|
maxTransactions: this.maxTxsPerBlock,
|
|
427
437
|
maxBlockSize: this.maxBlockSizeInBytes,
|
|
@@ -439,8 +449,8 @@ export { SequencerState };
|
|
|
439
449
|
* @param proposalHeader - The partial header constructed for the proposal
|
|
440
450
|
* @param newGlobalVariables - The global variables for the new block
|
|
441
451
|
* @param proposerAddress - The address of the proposer
|
|
442
|
-
*/ async buildBlockAndEnqueuePublish(pendingTxs, proposalHeader, newGlobalVariables, proposerAddress) {
|
|
443
|
-
await this.publisher.validateBlockHeader(proposalHeader);
|
|
452
|
+
*/ async buildBlockAndEnqueuePublish(pendingTxs, proposalHeader, newGlobalVariables, proposerAddress, invalidateBlock) {
|
|
453
|
+
await this.publisher.validateBlockHeader(proposalHeader, invalidateBlock);
|
|
444
454
|
const blockNumber = newGlobalVariables.blockNumber;
|
|
445
455
|
const slot = proposalHeader.slotNumber.toBigInt();
|
|
446
456
|
const l1ToL2Messages = await this.l1ToL2MessageSource.getL1ToL2Messages(blockNumber);
|
|
@@ -448,12 +458,12 @@ export { SequencerState };
|
|
|
448
458
|
const workTimer = new Timer();
|
|
449
459
|
this.setState(SequencerState.CREATING_BLOCK, slot);
|
|
450
460
|
try {
|
|
451
|
-
const blockBuilderOptions = this.
|
|
461
|
+
const blockBuilderOptions = this.getBlockBuilderOptions(Number(slot));
|
|
452
462
|
const buildBlockRes = await this.blockBuilder.buildBlock(pendingTxs, l1ToL2Messages, newGlobalVariables, blockBuilderOptions);
|
|
453
463
|
const { publicGas, block, publicProcessorDuration, numTxs, numMsgs, blockBuildingTimer, usedTxs, failedTxs } = buildBlockRes;
|
|
454
464
|
const blockBuildDuration = workTimer.ms();
|
|
455
465
|
await this.dropFailedTxsFromP2P(failedTxs);
|
|
456
|
-
const minTxsPerBlock = this.
|
|
466
|
+
const minTxsPerBlock = this.minTxsPerBlock;
|
|
457
467
|
if (numTxs < minTxsPerBlock) {
|
|
458
468
|
this.log.warn(`Block ${blockNumber} has too few txs to be proposed (got ${numTxs} but required ${minTxsPerBlock})`, {
|
|
459
469
|
slot,
|
|
@@ -464,7 +474,7 @@ export { SequencerState };
|
|
|
464
474
|
}
|
|
465
475
|
// TODO(@PhilWindle) We should probably periodically check for things like another
|
|
466
476
|
// block being published before ours instead of just waiting on our block
|
|
467
|
-
await this.publisher.validateBlockHeader(block.header.toPropose());
|
|
477
|
+
await this.publisher.validateBlockHeader(block.header.toPropose(), invalidateBlock);
|
|
468
478
|
const blockStats = {
|
|
469
479
|
eventName: 'l2-block-built',
|
|
470
480
|
creator: this.publisher.getSenderAddress().toString(),
|
|
@@ -489,7 +499,7 @@ export { SequencerState };
|
|
|
489
499
|
blockNumber
|
|
490
500
|
});
|
|
491
501
|
}
|
|
492
|
-
await this.enqueuePublishL2Block(block, attestations, txHashes);
|
|
502
|
+
await this.enqueuePublishL2Block(block, attestations, txHashes, invalidateBlock);
|
|
493
503
|
this.metrics.recordBuiltBlock(blockBuildDuration, publicGas.l2Gas);
|
|
494
504
|
return block;
|
|
495
505
|
} catch (err) {
|
|
@@ -498,8 +508,7 @@ export { SequencerState };
|
|
|
498
508
|
}
|
|
499
509
|
}
|
|
500
510
|
async collectAttestations(block, txs, proposerAddress) {
|
|
501
|
-
|
|
502
|
-
const committee = await this.publisher.getCurrentEpochCommittee();
|
|
511
|
+
const { committee } = await this.publisher.epochCache.getCommittee(block.header.getSlot());
|
|
503
512
|
// We checked above that the committee is defined, so this should never happen.
|
|
504
513
|
if (!committee) {
|
|
505
514
|
throw new Error('No committee when collecting attestations');
|
|
@@ -524,8 +533,12 @@ export { SequencerState };
|
|
|
524
533
|
};
|
|
525
534
|
const proposal = await this.validatorClient.createBlockProposal(block.header.globalVariables.blockNumber, block.header.toPropose(), block.archive.root, block.header.state, txs, proposerAddress, blockProposalOptions);
|
|
526
535
|
if (!proposal) {
|
|
527
|
-
|
|
528
|
-
|
|
536
|
+
throw new Error(`Failed to create block proposal`);
|
|
537
|
+
}
|
|
538
|
+
if (this.config.skipCollectingAttestations) {
|
|
539
|
+
this.log.warn('Skipping attestation collection as per config (attesting with own keys only)');
|
|
540
|
+
const attestations = await this.validatorClient?.collectOwnAttestations(proposal);
|
|
541
|
+
return orderAttestations(attestations ?? [], committee);
|
|
529
542
|
}
|
|
530
543
|
this.log.debug('Broadcasting block proposal to validators');
|
|
531
544
|
await this.validatorClient.broadcastBlockProposal(proposal);
|
|
@@ -551,14 +564,15 @@ export { SequencerState };
|
|
|
551
564
|
/**
|
|
552
565
|
* Publishes the L2Block to the rollup contract.
|
|
553
566
|
* @param block - The L2Block to be published.
|
|
554
|
-
*/ async enqueuePublishL2Block(block, attestations, txHashes) {
|
|
567
|
+
*/ async enqueuePublishL2Block(block, attestations, txHashes, invalidateBlock) {
|
|
555
568
|
// Publishes new block to the network and awaits the tx to be mined
|
|
556
569
|
this.setState(SequencerState.PUBLISHING_BLOCK, block.header.globalVariables.slotNumber.toBigInt());
|
|
557
570
|
// Time out tx at the end of the slot
|
|
558
571
|
const slot = block.header.globalVariables.slotNumber.toNumber();
|
|
559
|
-
const txTimeoutAt = new Date((this.
|
|
572
|
+
const txTimeoutAt = new Date((this.getSlotStartBuildTimestamp(slot) + this.aztecSlotDuration) * 1000);
|
|
560
573
|
const enqueued = await this.publisher.enqueueProposeL2Block(block, attestations, txHashes, {
|
|
561
|
-
txTimeoutAt
|
|
574
|
+
txTimeoutAt,
|
|
575
|
+
forcePendingBlockNumber: invalidateBlock?.forcePendingBlockNumber
|
|
562
576
|
});
|
|
563
577
|
if (!enqueued) {
|
|
564
578
|
throw new Error(`Failed to enqueue publish of block ${block.number}`);
|
|
@@ -577,9 +591,10 @@ export { SequencerState };
|
|
|
577
591
|
this.l2BlockSource.getL2Tips().then((t)=>t.latest),
|
|
578
592
|
this.p2pClient.getStatus().then((p2p)=>p2p.syncedToL2Block),
|
|
579
593
|
this.l1ToL2MessageSource.getL2Tips().then((t)=>t.latest),
|
|
580
|
-
this.l2BlockSource.getL1Timestamp()
|
|
594
|
+
this.l2BlockSource.getL1Timestamp(),
|
|
595
|
+
this.l2BlockSource.getPendingChainValidationStatus()
|
|
581
596
|
]);
|
|
582
|
-
const [worldState, l2BlockSource, p2p, l1ToL2MessageSource, l1Timestamp] = syncedBlocks;
|
|
597
|
+
const [worldState, l2BlockSource, p2p, l1ToL2MessageSource, l1Timestamp, pendingChainValidationStatus] = syncedBlocks;
|
|
583
598
|
// The archiver reports 'undefined' hash for the genesis block
|
|
584
599
|
// because it doesn't have access to world state to compute it (facepalm)
|
|
585
600
|
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;
|
|
@@ -605,22 +620,63 @@ export { SequencerState };
|
|
|
605
620
|
block,
|
|
606
621
|
blockNumber: block.number,
|
|
607
622
|
archive: block.archive.root,
|
|
608
|
-
l1Timestamp
|
|
623
|
+
l1Timestamp,
|
|
624
|
+
pendingChainValidationStatus
|
|
609
625
|
};
|
|
610
626
|
} else {
|
|
611
627
|
const archive = new Fr((await this.worldState.getCommitted().getTreeInfo(MerkleTreeId.ARCHIVE)).root);
|
|
612
628
|
return {
|
|
613
629
|
blockNumber: INITIAL_L2_BLOCK_NUM - 1,
|
|
614
630
|
archive,
|
|
615
|
-
l1Timestamp
|
|
631
|
+
l1Timestamp,
|
|
632
|
+
pendingChainValidationStatus
|
|
616
633
|
};
|
|
617
634
|
}
|
|
618
635
|
}
|
|
619
|
-
|
|
620
|
-
|
|
636
|
+
/**
|
|
637
|
+
* Considers invalidating a block if the pending chain is invalid. Depends on how long the invalid block
|
|
638
|
+
* has been there without being invalidated and whether the sequencer is in the committee or not. We always
|
|
639
|
+
* have the proposer try to invalidate, but if they fail, the sequencers in the committee are expected to try,
|
|
640
|
+
* and if they fail, any sequencer will try as well.
|
|
641
|
+
*/ async considerInvalidatingBlock(syncedTo, currentSlot, ourValidatorAddresses) {
|
|
642
|
+
const { pendingChainValidationStatus, l1Timestamp } = syncedTo;
|
|
643
|
+
if (pendingChainValidationStatus.valid) {
|
|
644
|
+
return;
|
|
645
|
+
}
|
|
646
|
+
const invalidL1Timestamp = pendingChainValidationStatus.block.l1.timestamp;
|
|
647
|
+
const timeSinceChainInvalid = this.dateProvider.nowInSeconds() - Number(invalidL1Timestamp);
|
|
648
|
+
const invalidBlockNumber = pendingChainValidationStatus.block.block.number;
|
|
649
|
+
const { secondsBeforeInvalidatingBlockAsCommitteeMember, secondsBeforeInvalidatingBlockAsNonCommitteeMember } = this.config;
|
|
650
|
+
const logData = {
|
|
651
|
+
invalidL1Timestamp,
|
|
652
|
+
l1Timestamp,
|
|
653
|
+
invalidBlock: pendingChainValidationStatus.block.block.toBlockInfo(),
|
|
654
|
+
secondsBeforeInvalidatingBlockAsCommitteeMember,
|
|
655
|
+
secondsBeforeInvalidatingBlockAsNonCommitteeMember,
|
|
656
|
+
ourValidatorAddresses,
|
|
657
|
+
currentSlot
|
|
658
|
+
};
|
|
659
|
+
const inCurrentCommittee = ()=>this.publisher.epochCache.getCommittee(currentSlot).then((c)=>c?.committee?.some((member)=>ourValidatorAddresses.some((addr)=>addr.equals(member))));
|
|
660
|
+
const invalidateAsCommitteeMember = secondsBeforeInvalidatingBlockAsCommitteeMember !== undefined && secondsBeforeInvalidatingBlockAsCommitteeMember > 0 && timeSinceChainInvalid > secondsBeforeInvalidatingBlockAsCommitteeMember && await inCurrentCommittee();
|
|
661
|
+
const invalidateAsNonCommitteeMember = secondsBeforeInvalidatingBlockAsNonCommitteeMember !== undefined && secondsBeforeInvalidatingBlockAsNonCommitteeMember > 0 && timeSinceChainInvalid > secondsBeforeInvalidatingBlockAsNonCommitteeMember;
|
|
662
|
+
if (!invalidateAsCommitteeMember && !invalidateAsNonCommitteeMember) {
|
|
663
|
+
this.log.debug(`Not invalidating pending chain`, logData);
|
|
664
|
+
return;
|
|
665
|
+
}
|
|
666
|
+
const invalidateBlock = await this.publisher.simulateInvalidateBlock(pendingChainValidationStatus);
|
|
667
|
+
if (!invalidateBlock) {
|
|
668
|
+
this.log.warn(`Failed to simulate invalidate block`, logData);
|
|
669
|
+
return;
|
|
670
|
+
}
|
|
671
|
+
this.log.info(invalidateAsCommitteeMember ? `Invalidating block ${invalidBlockNumber} as committee member` : `Invalidating block ${invalidBlockNumber} as non-committee member`, logData);
|
|
672
|
+
this.publisher.enqueueInvalidateBlock(invalidateBlock);
|
|
673
|
+
await this.publisher.sendRequests();
|
|
674
|
+
}
|
|
675
|
+
getSlotStartBuildTimestamp(slotNumber) {
|
|
676
|
+
return Number(this.l1Constants.l1GenesisTime) + Number(slotNumber) * this.l1Constants.slotDuration - this.l1Constants.ethereumSlotDuration;
|
|
621
677
|
}
|
|
622
678
|
getSecondsIntoSlot(slotNumber) {
|
|
623
|
-
const slotStartTimestamp = this.
|
|
679
|
+
const slotStartTimestamp = this.getSlotStartBuildTimestamp(slotNumber);
|
|
624
680
|
return Number((this.dateProvider.now() / 1000 - slotStartTimestamp).toFixed(3));
|
|
625
681
|
}
|
|
626
682
|
get aztecSlotDuration() {
|
|
@@ -1,10 +1,6 @@
|
|
|
1
1
|
import type { SequencerMetrics } from './metrics.js';
|
|
2
2
|
import { SequencerState } from './utils.js';
|
|
3
3
|
export declare class SequencerTimetable {
|
|
4
|
-
private readonly ethereumSlotDuration;
|
|
5
|
-
private readonly aztecSlotDuration;
|
|
6
|
-
private readonly maxL1TxInclusionTimeIntoSlot;
|
|
7
|
-
private readonly enforce;
|
|
8
4
|
private readonly metrics?;
|
|
9
5
|
private readonly log;
|
|
10
6
|
/**
|
|
@@ -27,7 +23,21 @@ export declare class SequencerTimetable {
|
|
|
27
23
|
readonly attestationPropagationTime: number;
|
|
28
24
|
/** How much time we spend validating and processing a block after building it, and assembling the proposal to send to attestors */
|
|
29
25
|
readonly blockValidationTime: number;
|
|
30
|
-
|
|
26
|
+
/** Ethereum slot duration in seconds */
|
|
27
|
+
readonly ethereumSlotDuration: number;
|
|
28
|
+
/** Aztec slot duration in seconds (must be multiple of ethereum slot duration) */
|
|
29
|
+
readonly aztecSlotDuration: number;
|
|
30
|
+
/** How late into an L1 slot we can send a tx to make sure it gets included in the immediate next block. Complement of l1PublishingTime. */
|
|
31
|
+
readonly maxL1TxInclusionTimeIntoSlot: number;
|
|
32
|
+
/** Whether assertTimeLeft will throw if not enough time. */
|
|
33
|
+
readonly enforce: boolean;
|
|
34
|
+
constructor(opts: {
|
|
35
|
+
ethereumSlotDuration: number;
|
|
36
|
+
aztecSlotDuration: number;
|
|
37
|
+
maxL1TxInclusionTimeIntoSlot: number;
|
|
38
|
+
attestationPropagationTime?: number;
|
|
39
|
+
enforce: boolean;
|
|
40
|
+
}, metrics?: SequencerMetrics | undefined, log?: import("@aztec/aztec.js").Logger);
|
|
31
41
|
private get afterBlockBuildingTimeNeededWithoutReexec();
|
|
32
42
|
getBlockProposalExecTimeEnd(secondsIntoSlot: number): number;
|
|
33
43
|
private get afterBlockReexecTimeNeeded();
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"timetable.d.ts","sourceRoot":"","sources":["../../src/sequencer/timetable.ts"],"names":[],"mappings":"
|
|
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;IAsB5D,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"}
|
|
@@ -1,14 +1,10 @@
|
|
|
1
1
|
import { createLogger } from '@aztec/aztec.js';
|
|
2
|
+
import { DEFAULT_ATTESTATION_PROPAGATION_TIME } from '../config.js';
|
|
2
3
|
import { SequencerState } from './utils.js';
|
|
3
4
|
const MIN_EXECUTION_TIME = 1;
|
|
4
5
|
const BLOCK_PREPARE_TIME = 1;
|
|
5
6
|
const BLOCK_VALIDATION_TIME = 1;
|
|
6
|
-
const ATTESTATION_PROPAGATION_TIME = 2;
|
|
7
7
|
export class SequencerTimetable {
|
|
8
|
-
ethereumSlotDuration;
|
|
9
|
-
aztecSlotDuration;
|
|
10
|
-
maxL1TxInclusionTimeIntoSlot;
|
|
11
|
-
enforce;
|
|
12
8
|
metrics;
|
|
13
9
|
log;
|
|
14
10
|
/**
|
|
@@ -25,18 +21,22 @@ export class SequencerTimetable {
|
|
|
25
21
|
/** How long it takes to get ready to start building */ blockPrepareTime;
|
|
26
22
|
/** How long it takes to for proposals and attestations to travel across the p2p layer (one-way) */ attestationPropagationTime;
|
|
27
23
|
/** How much time we spend validating and processing a block after building it, and assembling the proposal to send to attestors */ blockValidationTime;
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
24
|
+
/** Ethereum slot duration in seconds */ ethereumSlotDuration;
|
|
25
|
+
/** Aztec slot duration in seconds (must be multiple of ethereum slot duration) */ aztecSlotDuration;
|
|
26
|
+
/** How late into an L1 slot we can send a tx to make sure it gets included in the immediate next block. Complement of l1PublishingTime. */ maxL1TxInclusionTimeIntoSlot;
|
|
27
|
+
/** Whether assertTimeLeft will throw if not enough time. */ enforce;
|
|
28
|
+
constructor(opts, metrics, log = createLogger('sequencer:timetable')){
|
|
33
29
|
this.metrics = metrics;
|
|
34
30
|
this.log = log;
|
|
35
31
|
this.minExecutionTime = MIN_EXECUTION_TIME;
|
|
36
32
|
this.blockPrepareTime = BLOCK_PREPARE_TIME;
|
|
37
|
-
this.attestationPropagationTime = ATTESTATION_PROPAGATION_TIME;
|
|
38
33
|
this.blockValidationTime = BLOCK_VALIDATION_TIME;
|
|
34
|
+
this.ethereumSlotDuration = opts.ethereumSlotDuration;
|
|
35
|
+
this.aztecSlotDuration = opts.aztecSlotDuration;
|
|
36
|
+
this.maxL1TxInclusionTimeIntoSlot = opts.maxL1TxInclusionTimeIntoSlot;
|
|
37
|
+
this.attestationPropagationTime = opts.attestationPropagationTime ?? DEFAULT_ATTESTATION_PROPAGATION_TIME;
|
|
39
38
|
this.l1PublishingTime = this.ethereumSlotDuration - this.maxL1TxInclusionTimeIntoSlot;
|
|
39
|
+
this.enforce = opts.enforce;
|
|
40
40
|
// Assume zero-cost propagation time and faster runs in test environments where L1 slot duration is shortened
|
|
41
41
|
if (this.ethereumSlotDuration < 8) {
|
|
42
42
|
this.attestationPropagationTime = 0;
|
|
@@ -45,10 +45,23 @@ export class SequencerTimetable {
|
|
|
45
45
|
}
|
|
46
46
|
const allWorkToDo = this.blockPrepareTime + this.minExecutionTime * 2 + this.attestationPropagationTime * 2 + this.blockValidationTime + this.l1PublishingTime;
|
|
47
47
|
const initializeDeadline = this.aztecSlotDuration - allWorkToDo;
|
|
48
|
+
this.initializeDeadline = initializeDeadline;
|
|
49
|
+
this.log.verbose(`Sequencer timetable initialized (${this.enforce ? 'enforced' : 'not enforced'})`, {
|
|
50
|
+
ethereumSlotDuration: this.ethereumSlotDuration,
|
|
51
|
+
aztecSlotDuration: this.aztecSlotDuration,
|
|
52
|
+
maxL1TxInclusionTimeIntoSlot: this.maxL1TxInclusionTimeIntoSlot,
|
|
53
|
+
l1PublishingTime: this.l1PublishingTime,
|
|
54
|
+
minExecutionTime: this.minExecutionTime,
|
|
55
|
+
blockPrepareTime: this.blockPrepareTime,
|
|
56
|
+
attestationPropagationTime: this.attestationPropagationTime,
|
|
57
|
+
blockValidationTime: this.blockValidationTime,
|
|
58
|
+
initializeDeadline: this.initializeDeadline,
|
|
59
|
+
enforce: this.enforce,
|
|
60
|
+
allWorkToDo
|
|
61
|
+
});
|
|
48
62
|
if (initializeDeadline <= 0) {
|
|
49
63
|
throw new Error(`Block proposal initialize deadline cannot be negative (got ${initializeDeadline} from total time needed ${allWorkToDo} and a slot duration of ${this.aztecSlotDuration}).`);
|
|
50
64
|
}
|
|
51
|
-
this.initializeDeadline = initializeDeadline;
|
|
52
65
|
}
|
|
53
66
|
get afterBlockBuildingTimeNeededWithoutReexec() {
|
|
54
67
|
return this.blockValidationTime + this.attestationPropagationTime * 2 + this.l1PublishingTime;
|
|
@@ -1,6 +1,3 @@
|
|
|
1
|
-
import type { EthAddress } from '@aztec/foundation/eth-address';
|
|
2
|
-
import { CommitteeAttestation } from '@aztec/stdlib/block';
|
|
3
|
-
import type { BlockAttestation } from '@aztec/stdlib/p2p';
|
|
4
1
|
export declare enum SequencerState {
|
|
5
2
|
/**
|
|
6
3
|
* Sequencer is stopped and not processing any txs from the pool.
|
|
@@ -35,14 +32,7 @@ export declare enum SequencerState {
|
|
|
35
32
|
*/
|
|
36
33
|
PUBLISHING_BLOCK = "PUBLISHING_BLOCK"
|
|
37
34
|
}
|
|
35
|
+
export type SequencerStateWithSlot = SequencerState.INITIALIZING_PROPOSAL | SequencerState.CREATING_BLOCK | SequencerState.COLLECTING_ATTESTATIONS | SequencerState.PUBLISHING_BLOCK;
|
|
38
36
|
export type SequencerStateCallback = () => SequencerState;
|
|
39
37
|
export declare function sequencerStateToNumber(state: SequencerState): number;
|
|
40
|
-
/** Order Attestations
|
|
41
|
-
*
|
|
42
|
-
* Returns attestation signatures in the order of a series of provided ethereum addresses
|
|
43
|
-
* The rollup smart contract expects attestations to appear in the order of the committee
|
|
44
|
-
*
|
|
45
|
-
* @todo: perform this logic within the memory attestation store instead?
|
|
46
|
-
*/
|
|
47
|
-
export declare function orderAttestations(attestations: BlockAttestation[], orderAddresses: EthAddress[]): CommitteeAttestation[];
|
|
48
38
|
//# sourceMappingURL=utils.d.ts.map
|