@aztec/sequencer-client 0.71.0 → 0.73.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/client/sequencer-client.d.ts +11 -6
- package/dest/client/sequencer-client.d.ts.map +1 -1
- package/dest/client/sequencer-client.js +41 -10
- package/dest/config.d.ts +1 -1
- package/dest/config.d.ts.map +1 -1
- package/dest/config.js +3 -4
- package/dest/publisher/config.d.ts +5 -0
- package/dest/publisher/config.d.ts.map +1 -1
- package/dest/publisher/config.js +9 -2
- package/dest/publisher/index.d.ts +1 -1
- package/dest/publisher/index.d.ts.map +1 -1
- package/dest/publisher/index.js +2 -2
- package/dest/publisher/{l1-publisher-metrics.d.ts → sequencer-publisher-metrics.d.ts} +6 -2
- package/dest/publisher/sequencer-publisher-metrics.d.ts.map +1 -0
- package/dest/publisher/sequencer-publisher-metrics.js +111 -0
- package/dest/publisher/sequencer-publisher.d.ts +158 -0
- package/dest/publisher/sequencer-publisher.d.ts.map +1 -0
- package/dest/publisher/sequencer-publisher.js +555 -0
- package/dest/sequencer/allowed.d.ts +1 -1
- package/dest/sequencer/allowed.d.ts.map +1 -1
- package/dest/sequencer/allowed.js +6 -6
- package/dest/sequencer/metrics.d.ts +1 -1
- package/dest/sequencer/metrics.d.ts.map +1 -1
- package/dest/sequencer/metrics.js +3 -4
- package/dest/sequencer/sequencer.d.ts +18 -12
- package/dest/sequencer/sequencer.d.ts.map +1 -1
- package/dest/sequencer/sequencer.js +118 -125
- package/dest/sequencer/utils.d.ts +1 -1
- package/dest/sequencer/utils.d.ts.map +1 -1
- package/dest/sequencer/utils.js +3 -3
- package/dest/slasher/slasher_client.d.ts.map +1 -1
- package/dest/slasher/slasher_client.js +6 -3
- package/dest/test/index.d.ts +3 -3
- package/dest/test/index.d.ts.map +1 -1
- package/dest/test/index.js +1 -2
- package/dest/tx_validator/gas_validator.d.ts +1 -1
- package/dest/tx_validator/gas_validator.d.ts.map +1 -1
- package/dest/tx_validator/gas_validator.js +10 -5
- package/dest/tx_validator/test_utils.d.ts +4 -4
- package/dest/tx_validator/test_utils.d.ts.map +1 -1
- package/dest/tx_validator/test_utils.js +3 -3
- package/package.json +22 -21
- package/src/client/sequencer-client.ts +60 -14
- package/src/config.ts +3 -3
- package/src/publisher/config.ts +13 -1
- package/src/publisher/index.ts +1 -1
- package/src/publisher/{l1-publisher-metrics.ts → sequencer-publisher-metrics.ts} +41 -2
- package/src/publisher/sequencer-publisher.ts +730 -0
- package/src/sequencer/allowed.ts +5 -5
- package/src/sequencer/metrics.ts +2 -3
- package/src/sequencer/sequencer.ts +153 -150
- package/src/sequencer/utils.ts +5 -2
- package/src/slasher/slasher_client.ts +7 -2
- package/src/test/index.ts +2 -4
- package/src/tx_validator/gas_validator.ts +17 -4
- package/src/tx_validator/test_utils.ts +5 -5
- package/dest/publisher/l1-publisher-metrics.d.ts.map +0 -1
- package/dest/publisher/l1-publisher-metrics.js +0 -85
- package/dest/publisher/l1-publisher.d.ts +0 -195
- package/dest/publisher/l1-publisher.d.ts.map +0 -1
- package/dest/publisher/l1-publisher.js +0 -864
- package/dest/test/test-l1-publisher.d.ts +0 -9
- package/dest/test/test-l1-publisher.d.ts.map +0 -1
- package/dest/test/test-l1-publisher.js +0 -11
- package/src/publisher/l1-publisher.ts +0 -1208
- package/src/test/test-l1-publisher.ts +0 -20
package/src/sequencer/allowed.ts
CHANGED
|
@@ -6,7 +6,7 @@ import { ProtocolContractAddress } from '@aztec/protocol-contracts';
|
|
|
6
6
|
|
|
7
7
|
let defaultAllowedSetupFunctions: AllowedElement[] | undefined = undefined;
|
|
8
8
|
|
|
9
|
-
export function getDefaultAllowedSetupFunctions(): AllowedElement[] {
|
|
9
|
+
export async function getDefaultAllowedSetupFunctions(): Promise<AllowedElement[]> {
|
|
10
10
|
if (defaultAllowedSetupFunctions === undefined) {
|
|
11
11
|
defaultAllowedSetupFunctions = [
|
|
12
12
|
// needed for authwit support
|
|
@@ -17,16 +17,16 @@ export function getDefaultAllowedSetupFunctions(): AllowedElement[] {
|
|
|
17
17
|
{
|
|
18
18
|
address: ProtocolContractAddress.FeeJuice,
|
|
19
19
|
// We can't restrict the selector because public functions get routed via dispatch.
|
|
20
|
-
// selector: FunctionSelector.fromSignature('_increase_public_balance((Field),Field)'),
|
|
20
|
+
// selector: FunctionSelector.fromSignature('_increase_public_balance((Field),(Field,Field))'),
|
|
21
21
|
},
|
|
22
22
|
// needed for private transfers via FPC
|
|
23
23
|
{
|
|
24
|
-
classId: getContractClassFromArtifact(TokenContractArtifact).id,
|
|
24
|
+
classId: (await getContractClassFromArtifact(TokenContractArtifact)).id,
|
|
25
25
|
// We can't restrict the selector because public functions get routed via dispatch.
|
|
26
|
-
// selector: FunctionSelector.fromSignature('_increase_public_balance((Field),Field)'),
|
|
26
|
+
// selector: FunctionSelector.fromSignature('_increase_public_balance((Field),(Field,Field))'),
|
|
27
27
|
},
|
|
28
28
|
{
|
|
29
|
-
classId: getContractClassFromArtifact(FPCContract.artifact).id,
|
|
29
|
+
classId: (await getContractClassFromArtifact(FPCContract.artifact)).id,
|
|
30
30
|
// We can't restrict the selector because public functions get routed via dispatch.
|
|
31
31
|
// selector: FunctionSelector.fromSignature('prepare_fee((Field),Field,(Field),Field)'),
|
|
32
32
|
},
|
package/src/sequencer/metrics.ts
CHANGED
|
@@ -105,13 +105,12 @@ export class SequencerMetrics {
|
|
|
105
105
|
this.setCurrentBlock(0, 0);
|
|
106
106
|
}
|
|
107
107
|
|
|
108
|
-
|
|
108
|
+
recordBuiltBlock(buildDurationMs: number, totalMana: number) {
|
|
109
109
|
this.blockCounter.add(1, {
|
|
110
|
-
[Attributes.STATUS]: '
|
|
110
|
+
[Attributes.STATUS]: 'built',
|
|
111
111
|
});
|
|
112
112
|
this.blockBuildDuration.record(Math.ceil(buildDurationMs));
|
|
113
113
|
this.blockBuildManaPerSecond.record(Math.ceil((totalMana * 1000) / buildDurationMs));
|
|
114
|
-
this.setCurrentBlock(0, 0);
|
|
115
114
|
}
|
|
116
115
|
|
|
117
116
|
recordFailedBlock() {
|
|
@@ -21,7 +21,6 @@ import {
|
|
|
21
21
|
type GlobalVariables,
|
|
22
22
|
StateReference,
|
|
23
23
|
} from '@aztec/circuits.js';
|
|
24
|
-
import { prettyLogViemErrorMsg } from '@aztec/ethereum';
|
|
25
24
|
import { AztecAddress } from '@aztec/foundation/aztec-address';
|
|
26
25
|
import { omit } from '@aztec/foundation/collection';
|
|
27
26
|
import { EthAddress } from '@aztec/foundation/eth-address';
|
|
@@ -37,7 +36,7 @@ import { Attributes, type TelemetryClient, type Tracer, getTelemetryClient, trac
|
|
|
37
36
|
import { type ValidatorClient } from '@aztec/validator-client';
|
|
38
37
|
|
|
39
38
|
import { type GlobalVariableBuilder } from '../global_variable_builder/global_builder.js';
|
|
40
|
-
import { type
|
|
39
|
+
import { type SequencerPublisher, VoteType } from '../publisher/sequencer-publisher.js';
|
|
41
40
|
import { type SlasherClient } from '../slasher/slasher_client.js';
|
|
42
41
|
import { createValidatorsForBlockBuilding } from '../tx_validator/tx_validator_factory.js';
|
|
43
42
|
import { getDefaultAllowedSetupFunctions } from './allowed.js';
|
|
@@ -69,7 +68,7 @@ export class Sequencer {
|
|
|
69
68
|
private _coinbase = EthAddress.ZERO;
|
|
70
69
|
private _feeRecipient = AztecAddress.ZERO;
|
|
71
70
|
private state = SequencerState.STOPPED;
|
|
72
|
-
private allowedInSetup: AllowedElement[] =
|
|
71
|
+
private allowedInSetup: AllowedElement[] = [];
|
|
73
72
|
private maxBlockSizeInBytes: number = 1024 * 1024;
|
|
74
73
|
private maxBlockGas: Gas = new Gas(100e9, 100e9);
|
|
75
74
|
private metrics: SequencerMetrics;
|
|
@@ -81,7 +80,7 @@ export class Sequencer {
|
|
|
81
80
|
protected enforceTimeTable: boolean = false;
|
|
82
81
|
|
|
83
82
|
constructor(
|
|
84
|
-
protected publisher:
|
|
83
|
+
protected publisher: SequencerPublisher,
|
|
85
84
|
protected validatorClient: ValidatorClient | undefined, // During migration the validator client can be inactive
|
|
86
85
|
protected globalsBuilder: GlobalVariableBuilder,
|
|
87
86
|
protected p2pClient: P2P,
|
|
@@ -98,7 +97,6 @@ export class Sequencer {
|
|
|
98
97
|
telemetry: TelemetryClient = getTelemetryClient(),
|
|
99
98
|
protected log = createLogger('sequencer'),
|
|
100
99
|
) {
|
|
101
|
-
this.updateConfig(config);
|
|
102
100
|
this.metrics = new SequencerMetrics(telemetry, () => this.state, 'Sequencer');
|
|
103
101
|
|
|
104
102
|
// Register the block builder with the validator client for re-execution
|
|
@@ -116,7 +114,7 @@ export class Sequencer {
|
|
|
116
114
|
* Updates sequencer config.
|
|
117
115
|
* @param config - New parameters.
|
|
118
116
|
*/
|
|
119
|
-
public updateConfig(config: SequencerConfig) {
|
|
117
|
+
public async updateConfig(config: SequencerConfig) {
|
|
120
118
|
this.log.info(`Sequencer config set`, omit(pickFromSchema(config, SequencerConfigSchema), 'allowedInSetup'));
|
|
121
119
|
|
|
122
120
|
if (config.transactionPollingIntervalMS !== undefined) {
|
|
@@ -142,6 +140,8 @@ export class Sequencer {
|
|
|
142
140
|
}
|
|
143
141
|
if (config.allowedInSetup) {
|
|
144
142
|
this.allowedInSetup = config.allowedInSetup;
|
|
143
|
+
} else {
|
|
144
|
+
this.allowedInSetup = await getDefaultAllowedSetupFunctions();
|
|
145
145
|
}
|
|
146
146
|
if (config.maxBlockSizeInBytes !== undefined) {
|
|
147
147
|
this.maxBlockSizeInBytes = config.maxBlockSizeInBytes;
|
|
@@ -177,12 +177,12 @@ export class Sequencer {
|
|
|
177
177
|
/**
|
|
178
178
|
* Starts the sequencer and moves to IDLE state.
|
|
179
179
|
*/
|
|
180
|
-
public start() {
|
|
180
|
+
public async start() {
|
|
181
|
+
await this.updateConfig(this.config);
|
|
181
182
|
this.runningPromise = new RunningPromise(this.work.bind(this), this.log, this.pollingIntervalMs);
|
|
182
183
|
this.setState(SequencerState.IDLE, 0n, true /** force */);
|
|
183
184
|
this.runningPromise.start();
|
|
184
185
|
this.log.info(`Sequencer started with address ${this.publisher.getSenderAddress().toString()}`);
|
|
185
|
-
return Promise.resolve();
|
|
186
186
|
}
|
|
187
187
|
|
|
188
188
|
/**
|
|
@@ -216,6 +216,11 @@ export class Sequencer {
|
|
|
216
216
|
return { state: this.state };
|
|
217
217
|
}
|
|
218
218
|
|
|
219
|
+
/** Forces the sequencer to bypass all time and tx count checks for the next block and build anyway. */
|
|
220
|
+
public flush() {
|
|
221
|
+
this.isFlushing = true;
|
|
222
|
+
}
|
|
223
|
+
|
|
219
224
|
/**
|
|
220
225
|
* @notice Performs most of the sequencer duties:
|
|
221
226
|
* - Checks if we are up to date
|
|
@@ -236,25 +241,20 @@ export class Sequencer {
|
|
|
236
241
|
this.setState(SequencerState.PROPOSER_CHECK, 0n);
|
|
237
242
|
|
|
238
243
|
const chainTip = await this.l2BlockSource.getBlock(-1);
|
|
239
|
-
const historicalHeader = chainTip?.header;
|
|
240
244
|
|
|
241
|
-
const newBlockNumber =
|
|
242
|
-
(historicalHeader === undefined
|
|
243
|
-
? await this.l2BlockSource.getBlockNumber()
|
|
244
|
-
: Number(historicalHeader.globalVariables.blockNumber.toBigInt())) + 1;
|
|
245
|
+
const newBlockNumber = (chainTip?.header.globalVariables.blockNumber.toNumber() ?? 0) + 1;
|
|
245
246
|
|
|
246
247
|
// If we cannot find a tip archive, assume genesis.
|
|
247
|
-
const chainTipArchive =
|
|
248
|
-
chainTip == undefined ? new Fr(GENESIS_ARCHIVE_ROOT).toBuffer() : chainTip?.archive.root.toBuffer();
|
|
248
|
+
const chainTipArchive = chainTip?.archive.root ?? new Fr(GENESIS_ARCHIVE_ROOT);
|
|
249
249
|
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
} catch (err) {
|
|
254
|
-
this.log.debug(`Cannot propose for block ${newBlockNumber}`);
|
|
250
|
+
const slot = await this.slotForProposal(chainTipArchive.toBuffer(), BigInt(newBlockNumber));
|
|
251
|
+
if (!slot) {
|
|
252
|
+
this.log.debug(`Cannot propose block ${newBlockNumber}`);
|
|
255
253
|
return;
|
|
256
254
|
}
|
|
257
255
|
|
|
256
|
+
this.log.info(`Can propose block ${newBlockNumber} at slot ${slot}`);
|
|
257
|
+
|
|
258
258
|
const newGlobalVariables = await this.globalsBuilder.buildGlobalVariables(
|
|
259
259
|
new Fr(newBlockNumber),
|
|
260
260
|
this._coinbase,
|
|
@@ -262,34 +262,30 @@ export class Sequencer {
|
|
|
262
262
|
slot,
|
|
263
263
|
);
|
|
264
264
|
|
|
265
|
-
|
|
266
|
-
|
|
265
|
+
const enqueueGovernanceVotePromise = this.publisher.enqueueCastVote(
|
|
266
|
+
slot,
|
|
267
|
+
newGlobalVariables.timestamp.toBigInt(),
|
|
268
|
+
VoteType.GOVERNANCE,
|
|
269
|
+
);
|
|
270
|
+
const enqueueSlashingVotePromise = this.publisher.enqueueCastVote(
|
|
271
|
+
slot,
|
|
272
|
+
newGlobalVariables.timestamp.toBigInt(),
|
|
273
|
+
VoteType.SLASHING,
|
|
274
|
+
);
|
|
267
275
|
|
|
268
|
-
//
|
|
269
|
-
const
|
|
270
|
-
if (pendingTxCount < this.minTxsPerBlock && !this.isFlushing) {
|
|
271
|
-
this.log.verbose(`Not enough txs to propose block. Got ${pendingTxCount} min ${this.minTxsPerBlock}.`, {
|
|
272
|
-
slot,
|
|
273
|
-
blockNumber: newBlockNumber,
|
|
274
|
-
});
|
|
275
|
-
await this.claimEpochProofRightIfAvailable(slot);
|
|
276
|
-
return;
|
|
277
|
-
}
|
|
276
|
+
// Start collecting proof quotes for the previous epoch if needed in the background
|
|
277
|
+
const createProofQuotePromise = this.createProofClaimForPreviousEpoch(slot);
|
|
278
278
|
|
|
279
279
|
this.setState(SequencerState.INITIALIZING_PROPOSAL, slot);
|
|
280
280
|
this.log.verbose(`Preparing proposal for block ${newBlockNumber} at slot ${slot}`, {
|
|
281
|
-
chainTipArchive
|
|
281
|
+
chainTipArchive,
|
|
282
282
|
blockNumber: newBlockNumber,
|
|
283
283
|
slot,
|
|
284
284
|
});
|
|
285
285
|
|
|
286
|
-
// We don't fetch exactly maxTxsPerBlock txs here because we may not need all of them if we hit a limit before,
|
|
287
|
-
// and also we may need to fetch more if we don't have enough valid txs.
|
|
288
|
-
const pendingTxs = this.p2pClient.iteratePendingTxs();
|
|
289
|
-
|
|
290
286
|
// If I created a "partial" header here that should make our job much easier.
|
|
291
287
|
const proposalHeader = new BlockHeader(
|
|
292
|
-
new AppendOnlyTreeSnapshot(
|
|
288
|
+
new AppendOnlyTreeSnapshot(chainTipArchive, 1),
|
|
293
289
|
ContentCommitment.empty(),
|
|
294
290
|
StateReference.empty(),
|
|
295
291
|
newGlobalVariables,
|
|
@@ -297,15 +293,41 @@ export class Sequencer {
|
|
|
297
293
|
Fr.ZERO,
|
|
298
294
|
);
|
|
299
295
|
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
//
|
|
304
|
-
//
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
this.
|
|
296
|
+
let finishedFlushing = false;
|
|
297
|
+
const pendingTxCount = await this.p2pClient.getPendingTxCount();
|
|
298
|
+
if (pendingTxCount >= this.minTxsPerBlock || this.isFlushing) {
|
|
299
|
+
// We don't fetch exactly maxTxsPerBlock txs here because we may not need all of them if we hit a limit before,
|
|
300
|
+
// and also we may need to fetch more if we don't have enough valid txs.
|
|
301
|
+
const pendingTxs = this.p2pClient.iteratePendingTxs();
|
|
302
|
+
|
|
303
|
+
await this.buildBlockAndEnqueuePublish(pendingTxs, proposalHeader).catch(err => {
|
|
304
|
+
this.log.error(`Error building/enqueuing block`, err, { blockNumber: newBlockNumber, slot });
|
|
305
|
+
});
|
|
306
|
+
finishedFlushing = true;
|
|
307
|
+
} else {
|
|
308
|
+
this.log.debug(
|
|
309
|
+
`Not enough txs to build block ${newBlockNumber} at slot ${slot}: got ${pendingTxCount} txs, need ${this.minTxsPerBlock}`,
|
|
310
|
+
);
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
await enqueueGovernanceVotePromise.catch(err => {
|
|
314
|
+
this.log.error(`Error enqueuing governance vote`, err, { blockNumber: newBlockNumber, slot });
|
|
315
|
+
});
|
|
316
|
+
await enqueueSlashingVotePromise.catch(err => {
|
|
317
|
+
this.log.error(`Error enqueuing slashing vote`, err, { blockNumber: newBlockNumber, slot });
|
|
318
|
+
});
|
|
319
|
+
await createProofQuotePromise
|
|
320
|
+
.then(quote => (quote ? this.publisher.enqueueClaimEpochProofRight(quote) : undefined))
|
|
321
|
+
.catch(err => {
|
|
322
|
+
this.log.error(`Error creating proof quote`, err, { blockNumber: newBlockNumber, slot });
|
|
323
|
+
});
|
|
324
|
+
|
|
325
|
+
await this.publisher.sendRequests();
|
|
326
|
+
|
|
327
|
+
if (finishedFlushing) {
|
|
328
|
+
this.isFlushing = false;
|
|
308
329
|
}
|
|
330
|
+
|
|
309
331
|
this.setState(SequencerState.IDLE, 0n);
|
|
310
332
|
}
|
|
311
333
|
|
|
@@ -325,24 +347,31 @@ export class Sequencer {
|
|
|
325
347
|
}
|
|
326
348
|
}
|
|
327
349
|
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
const [slot, blockNumber] = await this.publisher.canProposeAtNextEthBlock(tipArchive);
|
|
350
|
+
public getForwarderAddress() {
|
|
351
|
+
return this.publisher.getForwarderAddress();
|
|
352
|
+
}
|
|
332
353
|
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
354
|
+
/**
|
|
355
|
+
* Checks if we can propose at the next block and returns the slot number if we can.
|
|
356
|
+
* @param tipArchive - The archive of the previous block.
|
|
357
|
+
* @param proposalBlockNumber - The block number of the proposal.
|
|
358
|
+
* @returns The slot number if we can propose at the next block, otherwise undefined.
|
|
359
|
+
*/
|
|
360
|
+
async slotForProposal(tipArchive: Buffer, proposalBlockNumber: bigint): Promise<bigint | undefined> {
|
|
361
|
+
const result = await this.publisher.canProposeAtNextEthBlock(tipArchive);
|
|
362
|
+
|
|
363
|
+
if (!result) {
|
|
364
|
+
return undefined;
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
const [slot, blockNumber] = result;
|
|
368
|
+
|
|
369
|
+
if (proposalBlockNumber !== blockNumber) {
|
|
370
|
+
const msg = `Sequencer block number mismatch. Expected ${proposalBlockNumber} but got ${blockNumber}.`;
|
|
371
|
+
this.log.warn(msg);
|
|
372
|
+
throw new Error(msg);
|
|
345
373
|
}
|
|
374
|
+
return slot;
|
|
346
375
|
}
|
|
347
376
|
|
|
348
377
|
/**
|
|
@@ -376,16 +405,14 @@ export class Sequencer {
|
|
|
376
405
|
* @param opts - Whether to just validate the block as a validator, as opposed to building it as a proposal
|
|
377
406
|
*/
|
|
378
407
|
protected async buildBlock(
|
|
379
|
-
pendingTxs: Iterable<Tx>,
|
|
408
|
+
pendingTxs: Iterable<Tx> | AsyncIterable<Tx>,
|
|
380
409
|
newGlobalVariables: GlobalVariables,
|
|
381
|
-
historicalHeader?: BlockHeader,
|
|
382
410
|
opts: { validateOnly?: boolean } = {},
|
|
383
411
|
) {
|
|
384
|
-
const blockNumber = newGlobalVariables.blockNumber.
|
|
412
|
+
const blockNumber = newGlobalVariables.blockNumber.toNumber();
|
|
385
413
|
const slot = newGlobalVariables.slotNumber.toBigInt();
|
|
386
|
-
|
|
387
414
|
this.log.debug(`Requesting L1 to L2 messages from contract for block ${blockNumber}`);
|
|
388
|
-
const l1ToL2Messages = await this.l1ToL2MessageSource.getL1ToL2Messages(blockNumber);
|
|
415
|
+
const l1ToL2Messages = await this.l1ToL2MessageSource.getL1ToL2Messages(BigInt(blockNumber));
|
|
389
416
|
const msgCount = l1ToL2Messages.length;
|
|
390
417
|
|
|
391
418
|
this.log.verbose(`Building block ${blockNumber} for slot ${slot}`, {
|
|
@@ -396,23 +423,21 @@ export class Sequencer {
|
|
|
396
423
|
});
|
|
397
424
|
|
|
398
425
|
// Sync to the previous block at least
|
|
399
|
-
await this.worldState.syncImmediate(
|
|
400
|
-
this.log.debug(`Synced to previous block ${
|
|
426
|
+
await this.worldState.syncImmediate(blockNumber - 1);
|
|
427
|
+
this.log.debug(`Synced to previous block ${blockNumber - 1}`);
|
|
401
428
|
|
|
402
429
|
// NB: separating the dbs because both should update the state
|
|
403
430
|
const publicProcessorFork = await this.worldState.fork();
|
|
404
431
|
const orchestratorFork = await this.worldState.fork();
|
|
405
432
|
|
|
433
|
+
const previousBlockHeader =
|
|
434
|
+
(await this.l2BlockSource.getBlock(blockNumber - 1))?.header ?? orchestratorFork.getInitialHeader();
|
|
435
|
+
|
|
406
436
|
try {
|
|
407
|
-
const processor = this.publicProcessorFactory.create(
|
|
408
|
-
publicProcessorFork,
|
|
409
|
-
historicalHeader,
|
|
410
|
-
newGlobalVariables,
|
|
411
|
-
true,
|
|
412
|
-
);
|
|
437
|
+
const processor = this.publicProcessorFactory.create(publicProcessorFork, newGlobalVariables, true);
|
|
413
438
|
const blockBuildingTimer = new Timer();
|
|
414
439
|
const blockBuilder = this.blockBuilderFactory.create(orchestratorFork);
|
|
415
|
-
await blockBuilder.startNewBlock(newGlobalVariables, l1ToL2Messages);
|
|
440
|
+
await blockBuilder.startNewBlock(newGlobalVariables, l1ToL2Messages, previousBlockHeader);
|
|
416
441
|
|
|
417
442
|
// Deadline for processing depends on whether we're proposing a block
|
|
418
443
|
const secondsIntoSlot = this.getSecondsIntoSlot(slot);
|
|
@@ -455,8 +480,9 @@ export class Sequencer {
|
|
|
455
480
|
|
|
456
481
|
if (!opts.validateOnly && failedTxs.length > 0) {
|
|
457
482
|
const failedTxData = failedTxs.map(fail => fail.tx);
|
|
458
|
-
|
|
459
|
-
|
|
483
|
+
const failedTxHashes = await Tx.getHashes(failedTxData);
|
|
484
|
+
this.log.verbose(`Dropping failed txs ${failedTxHashes.join(', ')}`);
|
|
485
|
+
await this.p2pClient.deleteTxs(failedTxHashes);
|
|
460
486
|
}
|
|
461
487
|
|
|
462
488
|
if (
|
|
@@ -497,6 +523,7 @@ export class Sequencer {
|
|
|
497
523
|
// We create a fresh processor each time to reset any cached state (eg storage writes)
|
|
498
524
|
// We wait a bit to close the forks since the processor may still be working on a dangling tx
|
|
499
525
|
// which was interrupted due to the processingDeadline being hit.
|
|
526
|
+
// eslint-disable-next-line @typescript-eslint/no-misused-promises
|
|
500
527
|
setTimeout(async () => {
|
|
501
528
|
try {
|
|
502
529
|
await publicProcessorFork.close();
|
|
@@ -517,15 +544,13 @@ export class Sequencer {
|
|
|
517
544
|
*
|
|
518
545
|
* @param pendingTxs - Iterable of pending transactions to construct the block from
|
|
519
546
|
* @param proposalHeader - The partial header constructed for the proposal
|
|
520
|
-
* @param historicalHeader - The historical header of the parent
|
|
521
547
|
*/
|
|
522
|
-
@trackSpan('Sequencer.
|
|
548
|
+
@trackSpan('Sequencer.buildBlockAndEnqueuePublish', (_validTxs, proposalHeader) => ({
|
|
523
549
|
[Attributes.BLOCK_NUMBER]: proposalHeader.globalVariables.blockNumber.toNumber(),
|
|
524
550
|
}))
|
|
525
|
-
private async
|
|
526
|
-
pendingTxs: Iterable<Tx>,
|
|
551
|
+
private async buildBlockAndEnqueuePublish(
|
|
552
|
+
pendingTxs: Iterable<Tx> | AsyncIterable<Tx>,
|
|
527
553
|
proposalHeader: BlockHeader,
|
|
528
|
-
historicalHeader: BlockHeader | undefined,
|
|
529
554
|
): Promise<void> {
|
|
530
555
|
await this.publisher.validateBlockForSubmission(proposalHeader);
|
|
531
556
|
|
|
@@ -537,41 +562,38 @@ export class Sequencer {
|
|
|
537
562
|
const workTimer = new Timer();
|
|
538
563
|
this.setState(SequencerState.CREATING_BLOCK, slot);
|
|
539
564
|
|
|
540
|
-
// Start collecting proof quotes for the previous epoch if needed in the background
|
|
541
|
-
const proofQuotePromise = this.createProofClaimForPreviousEpoch(slot);
|
|
542
|
-
|
|
543
565
|
try {
|
|
544
|
-
const buildBlockRes = await this.buildBlock(pendingTxs, newGlobalVariables
|
|
566
|
+
const buildBlockRes = await this.buildBlock(pendingTxs, newGlobalVariables);
|
|
545
567
|
const { publicGas, block, publicProcessorDuration, numTxs, numMsgs, blockBuildingTimer } = buildBlockRes;
|
|
568
|
+
this.metrics.recordBuiltBlock(workTimer.ms(), publicGas.l2Gas);
|
|
546
569
|
|
|
547
570
|
// TODO(@PhilWindle) We should probably periodically check for things like another
|
|
548
571
|
// block being published before ours instead of just waiting on our block
|
|
549
572
|
await this.publisher.validateBlockForSubmission(block.header);
|
|
550
573
|
|
|
551
|
-
const workDuration = workTimer.ms();
|
|
552
574
|
const blockStats: L2BlockBuiltStats = {
|
|
553
575
|
eventName: 'l2-block-built',
|
|
554
576
|
creator: this.publisher.getSenderAddress().toString(),
|
|
555
|
-
duration:
|
|
577
|
+
duration: workTimer.ms(),
|
|
556
578
|
publicProcessDuration: publicProcessorDuration,
|
|
557
579
|
rollupCircuitsDuration: blockBuildingTimer.ms(),
|
|
558
580
|
...block.getStats(),
|
|
559
581
|
};
|
|
560
582
|
|
|
561
|
-
const blockHash = block.hash();
|
|
583
|
+
const blockHash = await block.hash();
|
|
562
584
|
const txHashes = block.body.txEffects.map(tx => tx.txHash);
|
|
563
|
-
this.log.info(
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
585
|
+
this.log.info(
|
|
586
|
+
`Built block ${block.number} for slot ${slot} with ${numTxs} txs and ${numMsgs} messages. ${
|
|
587
|
+
publicGas.l2Gas / workTimer.s()
|
|
588
|
+
} mana/s`,
|
|
589
|
+
{
|
|
590
|
+
blockHash,
|
|
591
|
+
globalVariables: block.header.globalVariables.toInspect(),
|
|
592
|
+
txHashes,
|
|
593
|
+
...blockStats,
|
|
594
|
+
},
|
|
595
|
+
);
|
|
573
596
|
|
|
574
|
-
this.isFlushing = false;
|
|
575
597
|
this.log.debug('Collecting attestations');
|
|
576
598
|
const stopCollectingAttestationsTimer = this.metrics.startCollectingAttestationsTimer();
|
|
577
599
|
const attestations = await this.collectAttestations(block, txHashes);
|
|
@@ -580,37 +602,13 @@ export class Sequencer {
|
|
|
580
602
|
}
|
|
581
603
|
stopCollectingAttestationsTimer();
|
|
582
604
|
|
|
583
|
-
|
|
584
|
-
const proofQuote = await proofQuotePromise;
|
|
585
|
-
|
|
586
|
-
await this.publishL2Block(block, attestations, txHashes, proofQuote);
|
|
587
|
-
this.metrics.recordPublishedBlock(workDuration, publicGas.l2Gas);
|
|
588
|
-
const duration = Math.ceil(workDuration);
|
|
589
|
-
const manaPerSecond = Math.ceil((publicGas.l2Gas * 1000) / duration);
|
|
590
|
-
this.log.info(
|
|
591
|
-
`Published block ${block.number} with ${numTxs} txs and ${numMsgs} messages in ${duration} ms at ${manaPerSecond} mana/s`,
|
|
592
|
-
{
|
|
593
|
-
publicGas,
|
|
594
|
-
blockNumber: block.number,
|
|
595
|
-
blockHash: blockHash,
|
|
596
|
-
slot,
|
|
597
|
-
txCount: txHashes.length,
|
|
598
|
-
msgCount: numMsgs,
|
|
599
|
-
duration,
|
|
600
|
-
submitter: this.publisher.getSenderAddress().toString(),
|
|
601
|
-
},
|
|
602
|
-
);
|
|
605
|
+
return this.enqueuePublishL2Block(block, attestations, txHashes);
|
|
603
606
|
} catch (err) {
|
|
604
607
|
this.metrics.recordFailedBlock();
|
|
605
608
|
throw err;
|
|
606
609
|
}
|
|
607
610
|
}
|
|
608
611
|
|
|
609
|
-
/** Forces the sequencer to bypass all time and tx count checks for the next block and build anyway. */
|
|
610
|
-
public flush() {
|
|
611
|
-
this.isFlushing = true;
|
|
612
|
-
}
|
|
613
|
-
|
|
614
612
|
@trackSpan('Sequencer.collectAttestations', (block, txHashes) => ({
|
|
615
613
|
[Attributes.BLOCK_NUMBER]: block.number,
|
|
616
614
|
[Attributes.BLOCK_ARCHIVE]: block.archive.toString(),
|
|
@@ -640,8 +638,8 @@ export class Sequencer {
|
|
|
640
638
|
this.log.debug('Creating block proposal for validators');
|
|
641
639
|
const proposal = await this.validatorClient.createBlockProposal(block.header, block.archive.root, txHashes);
|
|
642
640
|
if (!proposal) {
|
|
643
|
-
|
|
644
|
-
|
|
641
|
+
const msg = `Failed to create block proposal`;
|
|
642
|
+
throw new Error(msg);
|
|
645
643
|
}
|
|
646
644
|
|
|
647
645
|
this.log.debug('Broadcasting block proposal to validators');
|
|
@@ -665,28 +663,33 @@ export class Sequencer {
|
|
|
665
663
|
try {
|
|
666
664
|
// Find out which epoch we are currently in
|
|
667
665
|
const epochToProve = await this.publisher.getClaimableEpoch();
|
|
666
|
+
|
|
668
667
|
if (epochToProve === undefined) {
|
|
669
|
-
this.log.trace(`No epoch to
|
|
668
|
+
this.log.trace(`No epoch to claim at slot ${slotNumber}`);
|
|
670
669
|
return undefined;
|
|
671
670
|
}
|
|
672
671
|
|
|
673
672
|
// Get quotes for the epoch to be proven
|
|
674
673
|
this.log.debug(`Collecting proof quotes for epoch ${epochToProve}`);
|
|
675
|
-
const
|
|
676
|
-
|
|
674
|
+
const p2pQuotes = await this.p2pClient
|
|
675
|
+
.getEpochProofQuotes(epochToProve)
|
|
676
|
+
.then(quotes =>
|
|
677
|
+
quotes
|
|
678
|
+
.filter(x => x.payload.validUntilSlot >= slotNumber)
|
|
679
|
+
.filter(x => x.payload.epochToProve === epochToProve),
|
|
680
|
+
);
|
|
681
|
+
this.log.verbose(`Retrieved ${p2pQuotes.length} quotes for slot ${slotNumber} epoch ${epochToProve}`, {
|
|
677
682
|
epochToProve,
|
|
678
683
|
slotNumber,
|
|
679
|
-
quotes:
|
|
684
|
+
quotes: p2pQuotes.map(q => q.payload),
|
|
680
685
|
});
|
|
686
|
+
if (!p2pQuotes.length) {
|
|
687
|
+
return undefined;
|
|
688
|
+
}
|
|
689
|
+
|
|
681
690
|
// ensure these quotes are still valid for the slot and have the contract validate them
|
|
682
|
-
const
|
|
683
|
-
quotes
|
|
684
|
-
.filter(x => x.payload.validUntilSlot >= slotNumber)
|
|
685
|
-
.filter(x => x.payload.epochToProve === epochToProve)
|
|
686
|
-
.map(x => this.publisher.validateProofQuote(x)),
|
|
687
|
-
);
|
|
691
|
+
const validQuotes = await this.publisher.filterValidQuotes(p2pQuotes);
|
|
688
692
|
|
|
689
|
-
const validQuotes = (await validQuotesPromise).filter((q): q is EpochProofQuote => !!q);
|
|
690
693
|
if (!validQuotes.length) {
|
|
691
694
|
this.log.warn(`Failed to find any valid proof quotes`);
|
|
692
695
|
return undefined;
|
|
@@ -708,15 +711,14 @@ export class Sequencer {
|
|
|
708
711
|
* Publishes the L2Block to the rollup contract.
|
|
709
712
|
* @param block - The L2Block to be published.
|
|
710
713
|
*/
|
|
711
|
-
@trackSpan('Sequencer.
|
|
714
|
+
@trackSpan('Sequencer.enqueuePublishL2Block', block => ({
|
|
712
715
|
[Attributes.BLOCK_NUMBER]: block.number,
|
|
713
716
|
}))
|
|
714
|
-
protected async
|
|
717
|
+
protected async enqueuePublishL2Block(
|
|
715
718
|
block: L2Block,
|
|
716
719
|
attestations?: Signature[],
|
|
717
720
|
txHashes?: TxHash[],
|
|
718
|
-
|
|
719
|
-
) {
|
|
721
|
+
): Promise<void> {
|
|
720
722
|
// Publishes new block to the network and awaits the tx to be mined
|
|
721
723
|
this.setState(SequencerState.PUBLISHING_BLOCK, block.header.globalVariables.slotNumber.toBigInt());
|
|
722
724
|
|
|
@@ -724,11 +726,12 @@ export class Sequencer {
|
|
|
724
726
|
const slot = block.header.globalVariables.slotNumber.toNumber();
|
|
725
727
|
const txTimeoutAt = new Date((this.getSlotStartTimestamp(slot) + this.aztecSlotDuration) * 1000);
|
|
726
728
|
|
|
727
|
-
const
|
|
729
|
+
const enqueued = await this.publisher.enqueueProposeL2Block(block, attestations, txHashes, {
|
|
728
730
|
txTimeoutAt,
|
|
729
731
|
});
|
|
730
|
-
|
|
731
|
-
|
|
732
|
+
|
|
733
|
+
if (!enqueued) {
|
|
734
|
+
throw new Error(`Failed to enqueue publish of block ${block.number}`);
|
|
732
735
|
}
|
|
733
736
|
}
|
|
734
737
|
|
|
@@ -747,11 +750,11 @@ export class Sequencer {
|
|
|
747
750
|
const epoch = proofQuote.payload.epochToProve;
|
|
748
751
|
const ctx = { slotNumber, epoch, quote: proofQuote.toInspect() };
|
|
749
752
|
this.log.verbose(`Claiming proof right for epoch ${epoch}`, ctx);
|
|
750
|
-
const
|
|
751
|
-
if (!
|
|
752
|
-
throw new Error(`Failed to claim proof right for epoch ${epoch}`);
|
|
753
|
+
const enqueued = this.publisher.enqueueClaimEpochProofRight(proofQuote);
|
|
754
|
+
if (!enqueued) {
|
|
755
|
+
throw new Error(`Failed to enqueue claim of proof right for epoch ${epoch}`);
|
|
753
756
|
}
|
|
754
|
-
this.log.info(`
|
|
757
|
+
this.log.info(`Enqueued claim of proof right for epoch ${epoch}`, ctx);
|
|
755
758
|
return epoch;
|
|
756
759
|
}
|
|
757
760
|
|
package/src/sequencer/utils.ts
CHANGED
|
@@ -49,12 +49,15 @@ export function sequencerStateToNumber(state: SequencerState): number {
|
|
|
49
49
|
*
|
|
50
50
|
* @todo: perform this logic within the memory attestation store instead?
|
|
51
51
|
*/
|
|
52
|
-
export function orderAttestations(
|
|
52
|
+
export async function orderAttestations(
|
|
53
|
+
attestations: BlockAttestation[],
|
|
54
|
+
orderAddresses: EthAddress[],
|
|
55
|
+
): Promise<Signature[]> {
|
|
53
56
|
// Create a map of sender addresses to BlockAttestations
|
|
54
57
|
const attestationMap = new Map<string, BlockAttestation>();
|
|
55
58
|
|
|
56
59
|
for (const attestation of attestations) {
|
|
57
|
-
const sender = attestation.getSender();
|
|
60
|
+
const sender = await attestation.getSender();
|
|
58
61
|
if (sender) {
|
|
59
62
|
attestationMap.set(sender.toString(), attestation);
|
|
60
63
|
}
|
|
@@ -315,7 +315,10 @@ export class SlasherClient extends WithTracer {
|
|
|
315
315
|
const blockHash =
|
|
316
316
|
blockNumber == 0
|
|
317
317
|
? ''
|
|
318
|
-
: await this.l2BlockSource
|
|
318
|
+
: await this.l2BlockSource
|
|
319
|
+
.getBlockHeader(blockNumber)
|
|
320
|
+
.then(header => header?.hash())
|
|
321
|
+
.then(hash => hash?.toString());
|
|
319
322
|
return Promise.resolve({
|
|
320
323
|
state: this.currentState,
|
|
321
324
|
syncedToL2Block: { number: blockNumber, hash: blockHash },
|
|
@@ -333,7 +336,9 @@ export class SlasherClient extends WithTracer {
|
|
|
333
336
|
}
|
|
334
337
|
|
|
335
338
|
const lastBlockNum = blocks[blocks.length - 1].number;
|
|
336
|
-
await Promise.all(
|
|
339
|
+
await Promise.all(
|
|
340
|
+
blocks.map(async block => this.synchedBlockHashes.set(block.number, (await block.hash()).toString())),
|
|
341
|
+
);
|
|
337
342
|
await this.synchedLatestBlockNumber.set(lastBlockNum);
|
|
338
343
|
this.log.debug(`Synched to latest block ${lastBlockNum}`);
|
|
339
344
|
this.startServiceIfSynched();
|
package/src/test/index.ts
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
import { type PublicProcessorFactory } from '@aztec/simulator/server';
|
|
2
2
|
|
|
3
3
|
import { SequencerClient } from '../client/sequencer-client.js';
|
|
4
|
-
import { type
|
|
4
|
+
import { type SequencerPublisher } from '../publisher/sequencer-publisher.js';
|
|
5
5
|
import { Sequencer } from '../sequencer/sequencer.js';
|
|
6
6
|
import { type SequencerTimetable } from '../sequencer/timetable.js';
|
|
7
7
|
|
|
8
8
|
class TestSequencer_ extends Sequencer {
|
|
9
9
|
public override publicProcessorFactory!: PublicProcessorFactory;
|
|
10
10
|
public override timetable!: SequencerTimetable;
|
|
11
|
-
public override publisher!:
|
|
11
|
+
public override publisher!: SequencerPublisher;
|
|
12
12
|
}
|
|
13
13
|
|
|
14
14
|
export type TestSequencer = TestSequencer_;
|
|
@@ -18,5 +18,3 @@ class TestSequencerClient_ extends SequencerClient {
|
|
|
18
18
|
}
|
|
19
19
|
|
|
20
20
|
export type TestSequencerClient = TestSequencerClient_;
|
|
21
|
-
|
|
22
|
-
export * from './test-l1-publisher.js';
|