@aztec/sequencer-client 0.51.0 → 0.52.0
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/global_variable_builder/global_builder.d.ts +2 -1
- package/dest/global_variable_builder/global_builder.d.ts.map +1 -1
- package/dest/global_variable_builder/global_builder.js +9 -7
- package/dest/index.d.ts +2 -0
- package/dest/index.d.ts.map +1 -1
- package/dest/index.js +3 -1
- package/dest/publisher/l1-publisher.d.ts +33 -22
- package/dest/publisher/l1-publisher.d.ts.map +1 -1
- package/dest/publisher/l1-publisher.js +138 -69
- package/dest/sequencer/sequencer.d.ts +25 -2
- package/dest/sequencer/sequencer.d.ts.map +1 -1
- package/dest/sequencer/sequencer.js +174 -119
- package/package.json +19 -18
- package/src/global_variable_builder/global_builder.ts +8 -5
- package/src/index.ts +2 -0
- package/src/publisher/l1-publisher.ts +161 -77
- package/src/sequencer/sequencer.ts +229 -148
|
@@ -10,7 +10,15 @@ import {
|
|
|
10
10
|
} from '@aztec/circuit-types';
|
|
11
11
|
import { type AllowedElement, BlockProofError, PROVING_STATUS } from '@aztec/circuit-types/interfaces';
|
|
12
12
|
import { type L2BlockBuiltStats } from '@aztec/circuit-types/stats';
|
|
13
|
-
import {
|
|
13
|
+
import {
|
|
14
|
+
AppendOnlyTreeSnapshot,
|
|
15
|
+
AztecAddress,
|
|
16
|
+
ContentCommitment,
|
|
17
|
+
EthAddress,
|
|
18
|
+
GENESIS_ARCHIVE_ROOT,
|
|
19
|
+
Header,
|
|
20
|
+
StateReference,
|
|
21
|
+
} from '@aztec/circuits.js';
|
|
14
22
|
import { Fr } from '@aztec/foundation/fields';
|
|
15
23
|
import { createDebugLogger } from '@aztec/foundation/log';
|
|
16
24
|
import { RunningPromise } from '@aztec/foundation/running-promise';
|
|
@@ -21,6 +29,8 @@ import { Attributes, type TelemetryClient, type Tracer, trackSpan } from '@aztec
|
|
|
21
29
|
import { type ValidatorClient } from '@aztec/validator-client';
|
|
22
30
|
import { type WorldStateStatus, type WorldStateSynchronizer } from '@aztec/world-state';
|
|
23
31
|
|
|
32
|
+
import { BaseError, ContractFunctionRevertedError } from 'viem';
|
|
33
|
+
|
|
24
34
|
import { type BlockBuilderFactory } from '../block_builder/index.js';
|
|
25
35
|
import { type GlobalVariableBuilder } from '../global_variable_builder/global_builder.js';
|
|
26
36
|
import { type L1Publisher } from '../publisher/l1-publisher.js';
|
|
@@ -28,6 +38,12 @@ import { type TxValidatorFactory } from '../tx_validator/tx_validator_factory.js
|
|
|
28
38
|
import { type SequencerConfig } from './config.js';
|
|
29
39
|
import { SequencerMetrics } from './metrics.js';
|
|
30
40
|
|
|
41
|
+
export type ShouldProposeArgs = {
|
|
42
|
+
pendingTxsCount?: number;
|
|
43
|
+
validTxsCount?: number;
|
|
44
|
+
processedTxsCount?: number;
|
|
45
|
+
};
|
|
46
|
+
|
|
31
47
|
/**
|
|
32
48
|
* Sequencer client
|
|
33
49
|
* - Wins a period of time to become the sequencer (depending on finalized protocol).
|
|
@@ -166,157 +182,237 @@ export class Sequencer {
|
|
|
166
182
|
}
|
|
167
183
|
|
|
168
184
|
/**
|
|
169
|
-
*
|
|
185
|
+
* @notice Performs most of the sequencer duties:
|
|
186
|
+
* - Checks if we are up to date
|
|
187
|
+
* - If we are and we are the sequencer, collect txs and build a block
|
|
188
|
+
* - Collect attestations for the block
|
|
189
|
+
* - Submit block
|
|
190
|
+
* - If our block for some reason is not included, revert the state
|
|
170
191
|
*/
|
|
171
192
|
protected async work() {
|
|
193
|
+
// Update state when the previous block has been synced
|
|
194
|
+
const prevBlockSynced = await this.isBlockSynced();
|
|
195
|
+
// Do not go forward with new block if the previous one has not been mined and processed
|
|
196
|
+
if (!prevBlockSynced) {
|
|
197
|
+
this.log.debug('Previous block has not been mined and processed yet');
|
|
198
|
+
return;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
if (prevBlockSynced && this.state === SequencerState.PUBLISHING_BLOCK) {
|
|
202
|
+
this.log.debug(`Block has been synced`);
|
|
203
|
+
this.state = SequencerState.IDLE;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
const chainTip = await this.l2BlockSource.getBlock(-1);
|
|
207
|
+
const historicalHeader = chainTip?.header;
|
|
208
|
+
|
|
209
|
+
const newBlockNumber =
|
|
210
|
+
(historicalHeader === undefined
|
|
211
|
+
? await this.l2BlockSource.getBlockNumber()
|
|
212
|
+
: Number(historicalHeader.globalVariables.blockNumber.toBigInt())) + 1;
|
|
213
|
+
|
|
214
|
+
// If we cannot find a tip archive, assume genesis.
|
|
215
|
+
const chainTipArchive =
|
|
216
|
+
chainTip == undefined ? new Fr(GENESIS_ARCHIVE_ROOT).toBuffer() : chainTip?.archive.root.toBuffer();
|
|
217
|
+
|
|
218
|
+
let slot: bigint;
|
|
172
219
|
try {
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
220
|
+
slot = await this.mayProposeBlock(chainTipArchive, BigInt(newBlockNumber));
|
|
221
|
+
} catch (err) {
|
|
222
|
+
this.log.debug(`Cannot propose for block ${newBlockNumber}`);
|
|
223
|
+
return;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
if (!this.shouldProposeBlock(historicalHeader, {})) {
|
|
227
|
+
return;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
this.state = SequencerState.WAITING_FOR_TXS;
|
|
231
|
+
|
|
232
|
+
// Get txs to build the new block.
|
|
233
|
+
const pendingTxs = this.p2pClient.getTxs('pending');
|
|
234
|
+
|
|
235
|
+
if (!this.shouldProposeBlock(historicalHeader, { pendingTxsCount: pendingTxs.length })) {
|
|
236
|
+
return;
|
|
237
|
+
}
|
|
238
|
+
this.log.debug(`Retrieved ${pendingTxs.length} txs from P2P pool`);
|
|
239
|
+
|
|
240
|
+
const newGlobalVariables = await this.globalsBuilder.buildGlobalVariables(
|
|
241
|
+
new Fr(newBlockNumber),
|
|
242
|
+
this._coinbase,
|
|
243
|
+
this._feeRecipient,
|
|
244
|
+
slot,
|
|
245
|
+
);
|
|
246
|
+
|
|
247
|
+
// If I created a "partial" header here that should make our job much easier.
|
|
248
|
+
const proposalHeader = new Header(
|
|
249
|
+
new AppendOnlyTreeSnapshot(Fr.fromBuffer(chainTipArchive), 1),
|
|
250
|
+
ContentCommitment.empty(),
|
|
251
|
+
StateReference.empty(),
|
|
252
|
+
newGlobalVariables,
|
|
253
|
+
Fr.ZERO,
|
|
254
|
+
);
|
|
255
|
+
|
|
256
|
+
// TODO: It should be responsibility of the P2P layer to validate txs before passing them on here
|
|
257
|
+
const allValidTxs = await this.takeValidTxs(
|
|
258
|
+
pendingTxs,
|
|
259
|
+
this.txValidatorFactory.validatorForNewTxs(newGlobalVariables, this.allowedInSetup),
|
|
260
|
+
);
|
|
180
261
|
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
262
|
+
// TODO: We are taking the size of the tx from private-land, but we should be doing this after running
|
|
263
|
+
// public functions. Only reason why we do it here now is because the public processor and orchestrator
|
|
264
|
+
// are set up such that they require knowing the total number of txs in advance. Still, main reason for
|
|
265
|
+
// exceeding max block size in bytes is contract class registration, which happens in private-land. This
|
|
266
|
+
// may break if we start emitting lots of log data from public-land.
|
|
267
|
+
const validTxs = this.takeTxsWithinMaxSize(allValidTxs);
|
|
268
|
+
|
|
269
|
+
// Bail if we don't have enough valid txs
|
|
270
|
+
if (!this.shouldProposeBlock(historicalHeader, { validTxsCount: validTxs.length })) {
|
|
271
|
+
return;
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
try {
|
|
275
|
+
// @note It is very important that the following function will FAIL and not just return early
|
|
276
|
+
// if it have made any state changes. If not, we won't rollback the state, and you will
|
|
277
|
+
// be in for a world of pain.
|
|
278
|
+
await this.buildBlockAndPublish(validTxs, proposalHeader, historicalHeader);
|
|
279
|
+
} catch (err) {
|
|
280
|
+
if (BlockProofError.isBlockProofError(err)) {
|
|
281
|
+
const txHashes = err.txHashes.filter(h => !h.isZero());
|
|
282
|
+
this.log.warn(`Proving block failed, removing ${txHashes.length} txs from pool`);
|
|
283
|
+
await this.p2pClient.deleteTxs(txHashes);
|
|
184
284
|
}
|
|
285
|
+
this.log.error(`Rolling back world state DB due to error assembling block`, (err as any).stack);
|
|
286
|
+
await this.worldState.getLatest().rollback();
|
|
287
|
+
}
|
|
288
|
+
}
|
|
185
289
|
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
290
|
+
/** Whether to skip the check of min txs per block if more than maxSecondsBetweenBlocks has passed since the previous block. */
|
|
291
|
+
private skipMinTxsPerBlockCheck(historicalHeader: Header | undefined): boolean {
|
|
292
|
+
const lastBlockTime = historicalHeader?.globalVariables.timestamp.toNumber() || 0;
|
|
293
|
+
const currentTime = Math.floor(Date.now() / 1000);
|
|
294
|
+
const elapsed = currentTime - lastBlockTime;
|
|
295
|
+
|
|
296
|
+
return this.maxSecondsBetweenBlocks > 0 && elapsed >= this.maxSecondsBetweenBlocks;
|
|
297
|
+
}
|
|
191
298
|
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
299
|
+
async mayProposeBlock(tipArchive: Buffer, proposalBlockNumber: bigint): Promise<bigint> {
|
|
300
|
+
// This checks that we can propose, and gives us the slot that we are to propose for
|
|
301
|
+
try {
|
|
302
|
+
const [slot, blockNumber] = await this.publisher.canProposeAtNextEthBlock(tipArchive);
|
|
303
|
+
|
|
304
|
+
if (proposalBlockNumber !== blockNumber) {
|
|
305
|
+
const msg = `Block number mismatch. Expected ${proposalBlockNumber} but got ${blockNumber}`;
|
|
306
|
+
this.log.debug(msg);
|
|
307
|
+
throw new Error(msg);
|
|
196
308
|
}
|
|
197
309
|
|
|
198
|
-
|
|
199
|
-
|
|
310
|
+
return slot;
|
|
311
|
+
} catch (err) {
|
|
312
|
+
if (err instanceof BaseError) {
|
|
313
|
+
const revertError = err.walk(err => err instanceof ContractFunctionRevertedError);
|
|
314
|
+
if (revertError instanceof ContractFunctionRevertedError) {
|
|
315
|
+
const errorName = revertError.data?.errorName ?? '';
|
|
316
|
+
this.log.debug(`canProposeAtTime failed with "${errorName}"`);
|
|
317
|
+
}
|
|
200
318
|
}
|
|
319
|
+
throw err;
|
|
320
|
+
}
|
|
321
|
+
}
|
|
201
322
|
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
`Last block mined at ${lastBlockTime} current time is ${currentTime} (elapsed ${elapsedSinceLastBlock})`,
|
|
208
|
-
);
|
|
323
|
+
shouldProposeBlock(historicalHeader: Header | undefined, args: ShouldProposeArgs): boolean {
|
|
324
|
+
if (this.isFlushing) {
|
|
325
|
+
this.log.verbose(`Flushing all pending txs in new block`);
|
|
326
|
+
return true;
|
|
327
|
+
}
|
|
209
328
|
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
`Not creating block because not enough time ${this.minSecondsBetweenBlocks} has passed since last block`,
|
|
218
|
-
);
|
|
219
|
-
return;
|
|
220
|
-
}
|
|
329
|
+
// Compute time elapsed since the previous block
|
|
330
|
+
const lastBlockTime = historicalHeader?.globalVariables.timestamp.toNumber() || 0;
|
|
331
|
+
const currentTime = Math.floor(Date.now() / 1000);
|
|
332
|
+
const elapsedSinceLastBlock = currentTime - lastBlockTime;
|
|
333
|
+
this.log.debug(
|
|
334
|
+
`Last block mined at ${lastBlockTime} current time is ${currentTime} (elapsed ${elapsedSinceLastBlock})`,
|
|
335
|
+
);
|
|
221
336
|
|
|
222
|
-
|
|
337
|
+
// If we haven't hit the maxSecondsBetweenBlocks, we need to have at least minTxsPerBLock txs.
|
|
338
|
+
// Do not go forward with new block if not enough time has passed since last block
|
|
339
|
+
if (this.minSecondsBetweenBlocks > 0 && elapsedSinceLastBlock < this.minSecondsBetweenBlocks) {
|
|
340
|
+
this.log.debug(
|
|
341
|
+
`Not creating block because not enough time ${this.minSecondsBetweenBlocks} has passed since last block`,
|
|
342
|
+
);
|
|
343
|
+
return false;
|
|
344
|
+
}
|
|
223
345
|
|
|
224
|
-
|
|
225
|
-
const pendingTxs = this.p2pClient.getTxs('pending');
|
|
346
|
+
const skipCheck = this.skipMinTxsPerBlockCheck(historicalHeader);
|
|
226
347
|
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
348
|
+
// If we haven't hit the maxSecondsBetweenBlocks, we need to have at least minTxsPerBLock txs.
|
|
349
|
+
if (args.pendingTxsCount != undefined) {
|
|
350
|
+
if (args.pendingTxsCount < this.minTxsPerBLock) {
|
|
351
|
+
if (skipCheck) {
|
|
230
352
|
this.log.debug(
|
|
231
|
-
`Creating block with only ${
|
|
353
|
+
`Creating block with only ${args.pendingTxsCount} txs as more than ${this.maxSecondsBetweenBlocks}s have passed since last block`,
|
|
232
354
|
);
|
|
233
355
|
} else {
|
|
234
356
|
this.log.debug(
|
|
235
|
-
`Not creating block because not enough txs in the pool (got ${
|
|
357
|
+
`Not creating block because not enough txs in the pool (got ${args.pendingTxsCount} min ${this.minTxsPerBLock})`,
|
|
236
358
|
);
|
|
237
|
-
return;
|
|
359
|
+
return false;
|
|
238
360
|
}
|
|
239
361
|
}
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
const newGlobalVariables = await this.globalsBuilder.buildGlobalVariables(
|
|
243
|
-
new Fr(newBlockNumber),
|
|
244
|
-
this._coinbase,
|
|
245
|
-
this._feeRecipient,
|
|
246
|
-
);
|
|
247
|
-
|
|
248
|
-
// @todo @LHerskind Include some logic to consider slots
|
|
249
|
-
|
|
250
|
-
// TODO: It should be responsibility of the P2P layer to validate txs before passing them on here
|
|
251
|
-
const allValidTxs = await this.takeValidTxs(
|
|
252
|
-
pendingTxs,
|
|
253
|
-
this.txValidatorFactory.validatorForNewTxs(newGlobalVariables, this.allowedInSetup),
|
|
254
|
-
);
|
|
255
|
-
|
|
256
|
-
// TODO: We are taking the size of the tx from private-land, but we should be doing this after running
|
|
257
|
-
// public functions. Only reason why we do it here now is because the public processor and orchestrator
|
|
258
|
-
// are set up such that they require knowing the total number of txs in advance. Still, main reason for
|
|
259
|
-
// exceeding max block size in bytes is contract class registration, which happens in private-land. This
|
|
260
|
-
// may break if we start emitting lots of log data from public-land.
|
|
261
|
-
const validTxs = this.takeTxsWithinMaxSize(allValidTxs);
|
|
362
|
+
}
|
|
262
363
|
|
|
364
|
+
// Bail if we don't have enough valid txs
|
|
365
|
+
if (args.validTxsCount != undefined) {
|
|
263
366
|
// Bail if we don't have enough valid txs
|
|
264
|
-
if (
|
|
265
|
-
!this.isFlushing &&
|
|
266
|
-
!this.skipMinTxsPerBlockCheck(elapsedSinceLastBlock) &&
|
|
267
|
-
validTxs.length < this.minTxsPerBLock
|
|
268
|
-
) {
|
|
367
|
+
if (!skipCheck && args.validTxsCount < this.minTxsPerBLock) {
|
|
269
368
|
this.log.debug(
|
|
270
|
-
`Not creating block because not enough valid txs loaded from the pool (got ${
|
|
369
|
+
`Not creating block because not enough valid txs loaded from the pool (got ${args.validTxsCount} min ${this.minTxsPerBLock})`,
|
|
271
370
|
);
|
|
272
|
-
return;
|
|
371
|
+
return false;
|
|
273
372
|
}
|
|
373
|
+
}
|
|
274
374
|
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
375
|
+
// TODO: This check should be processedTxs.length < this.minTxsPerBLock, so we don't publish a block with
|
|
376
|
+
// less txs than the minimum. But that'd cause the entire block to be aborted and retried. Instead, we should
|
|
377
|
+
// go back to the p2p pool and load more txs until we hit our minTxsPerBLock target. Only if there are no txs
|
|
378
|
+
// we should bail.
|
|
379
|
+
if (args.processedTxsCount != undefined) {
|
|
380
|
+
if (args.processedTxsCount === 0 && !skipCheck && this.minTxsPerBLock > 0) {
|
|
381
|
+
this.log.verbose('No txs processed correctly to build block. Exiting');
|
|
382
|
+
return false;
|
|
281
383
|
}
|
|
282
|
-
this.log.error(`Rolling back world state DB due to error assembling block`, (err as any).stack);
|
|
283
|
-
await this.worldState.getLatest().rollback();
|
|
284
384
|
}
|
|
285
|
-
}
|
|
286
385
|
|
|
287
|
-
|
|
288
|
-
private skipMinTxsPerBlockCheck(elapsed: number): boolean {
|
|
289
|
-
return this.maxSecondsBetweenBlocks > 0 && elapsed >= this.maxSecondsBetweenBlocks;
|
|
386
|
+
return true;
|
|
290
387
|
}
|
|
291
388
|
|
|
292
|
-
|
|
293
|
-
|
|
389
|
+
/**
|
|
390
|
+
* @notice Build and propose a block to the chain
|
|
391
|
+
*
|
|
392
|
+
* @dev MUST throw instead of exiting early to ensure that world-state
|
|
393
|
+
* is being rolled back if the block is dropped.
|
|
394
|
+
*
|
|
395
|
+
* @param validTxs - The valid transactions to construct the block from
|
|
396
|
+
* @param proposalHeader - The partial header constructed for the proposal
|
|
397
|
+
* @param historicalHeader - The historical header of the parent
|
|
398
|
+
*/
|
|
399
|
+
@trackSpan('Sequencer.buildBlockAndPublish', (_validTxs, proposalHeader, _historicalHeader) => ({
|
|
400
|
+
[Attributes.BLOCK_NUMBER]: proposalHeader.globalVariables.blockNumber.toNumber(),
|
|
294
401
|
}))
|
|
295
402
|
private async buildBlockAndPublish(
|
|
296
403
|
validTxs: Tx[],
|
|
297
|
-
|
|
404
|
+
proposalHeader: Header,
|
|
298
405
|
historicalHeader: Header | undefined,
|
|
299
|
-
elapsedSinceLastBlock: number,
|
|
300
406
|
): Promise<void> {
|
|
407
|
+
await this.publisher.validateBlockForSubmission(proposalHeader);
|
|
408
|
+
|
|
409
|
+
const newGlobalVariables = proposalHeader.globalVariables;
|
|
410
|
+
|
|
301
411
|
this.metrics.recordNewBlock(newGlobalVariables.blockNumber.toNumber(), validTxs.length);
|
|
302
412
|
const workTimer = new Timer();
|
|
303
413
|
this.state = SequencerState.CREATING_BLOCK;
|
|
304
414
|
this.log.info(`Building block ${newGlobalVariables.blockNumber.toNumber()} with ${validTxs.length} transactions`);
|
|
305
415
|
|
|
306
|
-
const assertBlockHeight = async () => {
|
|
307
|
-
const currentBlockNumber = await this.l2BlockSource.getBlockNumber();
|
|
308
|
-
if (currentBlockNumber + 1 !== newGlobalVariables.blockNumber.toNumber()) {
|
|
309
|
-
this.metrics.recordCancelledBlock();
|
|
310
|
-
throw new Error('New block was emitted while building block');
|
|
311
|
-
}
|
|
312
|
-
|
|
313
|
-
if (!(await this.publisher.isItMyTurnToSubmit())) {
|
|
314
|
-
throw new Error(`Not this sequencer turn to submit block`);
|
|
315
|
-
}
|
|
316
|
-
|
|
317
|
-
// @todo @LHerskind Should take into account, block number, proposer and slot number
|
|
318
|
-
};
|
|
319
|
-
|
|
320
416
|
// Get l1 to l2 messages from the contract
|
|
321
417
|
this.log.debug('Requesting L1 to L2 messages from contract');
|
|
322
418
|
const l1ToL2Messages = await this.l1ToL2MessageSource.getL1ToL2Messages(newGlobalVariables.blockNumber.toBigInt());
|
|
@@ -343,27 +439,22 @@ export class Sequencer {
|
|
|
343
439
|
await this.p2pClient.deleteTxs(Tx.getHashes(failedTxData));
|
|
344
440
|
}
|
|
345
441
|
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
// go back to the p2p pool and load more txs until we hit our minTxsPerBLock target. Only if there are no txs
|
|
349
|
-
// we should bail.
|
|
442
|
+
await this.publisher.validateBlockForSubmission(proposalHeader);
|
|
443
|
+
|
|
350
444
|
if (
|
|
351
|
-
!this.
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
445
|
+
!this.shouldProposeBlock(historicalHeader, {
|
|
446
|
+
validTxsCount: validTxs.length,
|
|
447
|
+
processedTxsCount: processedTxs.length,
|
|
448
|
+
})
|
|
355
449
|
) {
|
|
356
|
-
this.log.verbose('No txs processed correctly to build block. Exiting');
|
|
357
450
|
blockBuilder.cancelBlock();
|
|
358
|
-
|
|
451
|
+
throw new Error('Should not propose the block');
|
|
359
452
|
}
|
|
360
453
|
|
|
361
|
-
await assertBlockHeight();
|
|
362
|
-
|
|
363
454
|
// All real transactions have been added, set the block as full and complete the proving.
|
|
364
455
|
await blockBuilder.setBlockCompleted();
|
|
365
456
|
|
|
366
|
-
// Here we are now waiting for the block to be proven.
|
|
457
|
+
// Here we are now waiting for the block to be proven (using simulated[fake] proofs).
|
|
367
458
|
// TODO(@PhilWindle) We should probably periodically check for things like another
|
|
368
459
|
// block being published before ours instead of just waiting on our block
|
|
369
460
|
const result = await blockTicket.provingPromise;
|
|
@@ -371,12 +462,10 @@ export class Sequencer {
|
|
|
371
462
|
throw new Error(`Block proving failed, reason: ${result.reason}`);
|
|
372
463
|
}
|
|
373
464
|
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
// Block is ready, now finalise and publish!
|
|
465
|
+
// Block is ready, now finalise
|
|
377
466
|
const { block } = await blockBuilder.finaliseBlock();
|
|
378
467
|
|
|
379
|
-
await
|
|
468
|
+
await this.publisher.validateBlockForSubmission(block.header);
|
|
380
469
|
|
|
381
470
|
const workDuration = workTimer.ms();
|
|
382
471
|
this.log.verbose(
|
|
@@ -395,10 +484,11 @@ export class Sequencer {
|
|
|
395
484
|
if (this.isFlushing) {
|
|
396
485
|
this.log.verbose(`Flushing completed`);
|
|
397
486
|
}
|
|
487
|
+
|
|
398
488
|
this.isFlushing = false;
|
|
489
|
+
const attestations = await this.collectAttestations(block);
|
|
399
490
|
|
|
400
491
|
try {
|
|
401
|
-
const attestations = await this.collectAttestations(block);
|
|
402
492
|
await this.publishL2Block(block, attestations);
|
|
403
493
|
this.metrics.recordPublishedBlock(workDuration);
|
|
404
494
|
this.log.info(
|
|
@@ -418,25 +508,19 @@ export class Sequencer {
|
|
|
418
508
|
}
|
|
419
509
|
|
|
420
510
|
protected async collectAttestations(block: L2Block): Promise<Signature[] | undefined> {
|
|
421
|
-
//
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
// @note This is quite a sin, but I'm committing war crimes in this code already.
|
|
426
|
-
// _ ._ _ , _ ._
|
|
427
|
-
// (_ ' ( ` )_ .__)
|
|
428
|
-
// ( ( ( ) `) ) _)
|
|
429
|
-
// (__ (_ (_ . _) _) ,__)
|
|
430
|
-
// `~~`\ ' . /`~~`
|
|
431
|
-
// ; ;
|
|
432
|
-
// / \
|
|
433
|
-
// _____________/_ __ \_____________
|
|
434
|
-
if (IS_DEV_NET || !this.validatorClient) {
|
|
511
|
+
// TODO(https://github.com/AztecProtocol/aztec-packages/issues/7962): inefficient to have a round trip in here - this should be cached
|
|
512
|
+
const committee = await this.publisher.getCurrentEpochCommittee();
|
|
513
|
+
|
|
514
|
+
if (committee.length === 0) {
|
|
435
515
|
return undefined;
|
|
436
516
|
}
|
|
437
517
|
|
|
438
|
-
|
|
439
|
-
|
|
518
|
+
if (!this.validatorClient) {
|
|
519
|
+
const msg = 'Missing validator client: Cannot collect attestations';
|
|
520
|
+
this.log.error(msg);
|
|
521
|
+
throw new Error(msg);
|
|
522
|
+
}
|
|
523
|
+
|
|
440
524
|
const numberOfRequiredAttestations = Math.floor((committee.length * 2) / 3) + 1;
|
|
441
525
|
|
|
442
526
|
// TODO(https://github.com/AztecProtocol/aztec-packages/issues/7974): we do not have transaction[] lists in the block for now
|
|
@@ -448,10 +532,7 @@ export class Sequencer {
|
|
|
448
532
|
this.validatorClient.broadcastBlockProposal(proposal);
|
|
449
533
|
|
|
450
534
|
this.state = SequencerState.WAITING_FOR_ATTESTATIONS;
|
|
451
|
-
const attestations = await this.validatorClient.collectAttestations(
|
|
452
|
-
proposal.header.globalVariables.slotNumber.toBigInt(),
|
|
453
|
-
numberOfRequiredAttestations,
|
|
454
|
-
);
|
|
535
|
+
const attestations = await this.validatorClient.collectAttestations(proposal, numberOfRequiredAttestations);
|
|
455
536
|
|
|
456
537
|
// note: the smart contract requires that the signatures are provided in the order of the committee
|
|
457
538
|
return await orderAttestations(attestations, committee);
|
|
@@ -472,7 +553,7 @@ export class Sequencer {
|
|
|
472
553
|
if (publishedL2Block) {
|
|
473
554
|
this.lastPublishedBlock = block.number;
|
|
474
555
|
} else {
|
|
475
|
-
throw new Error(`Failed to publish block`);
|
|
556
|
+
throw new Error(`Failed to publish block ${block.number}`);
|
|
476
557
|
}
|
|
477
558
|
}
|
|
478
559
|
|