@aztec/sequencer-client 0.63.1 → 0.65.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/block_builder/light.js +2 -2
- package/dest/client/sequencer-client.d.ts +11 -3
- package/dest/client/sequencer-client.d.ts.map +1 -1
- package/dest/client/sequencer-client.js +4 -4
- package/dest/index.d.ts.map +1 -1
- package/dest/index.js +2 -1
- package/dest/publisher/index.d.ts +1 -0
- package/dest/publisher/index.d.ts.map +1 -1
- package/dest/publisher/index.js +2 -1
- package/dest/publisher/l1-publisher.d.ts +11 -11
- package/dest/publisher/l1-publisher.d.ts.map +1 -1
- package/dest/publisher/l1-publisher.js +15 -10
- package/dest/publisher/test-l1-publisher.d.ts +9 -0
- package/dest/publisher/test-l1-publisher.d.ts.map +1 -0
- package/dest/publisher/test-l1-publisher.js +11 -0
- package/dest/sequencer/metrics.d.ts +3 -0
- package/dest/sequencer/metrics.d.ts.map +1 -1
- package/dest/sequencer/metrics.js +15 -1
- package/dest/sequencer/sequencer.d.ts +12 -1
- package/dest/sequencer/sequencer.d.ts.map +1 -1
- package/dest/sequencer/sequencer.js +88 -55
- package/dest/sequencer/utils.d.ts +1 -1
- package/dest/sequencer/utils.d.ts.map +1 -1
- package/dest/sequencer/utils.js +4 -3
- package/package.json +19 -19
- package/src/block_builder/light.ts +1 -1
- package/src/client/sequencer-client.ts +21 -10
- package/src/index.ts +1 -0
- package/src/publisher/index.ts +1 -0
- package/src/publisher/l1-publisher.ts +28 -20
- package/src/publisher/test-l1-publisher.ts +20 -0
- package/src/sequencer/metrics.ts +19 -0
- package/src/sequencer/sequencer.ts +140 -72
- package/src/sequencer/utils.ts +3 -2
|
@@ -17,7 +17,7 @@ import {
|
|
|
17
17
|
type Proof,
|
|
18
18
|
type RootRollupPublicInputs,
|
|
19
19
|
} from '@aztec/circuits.js';
|
|
20
|
-
import { type L1ContractsConfig, createEthereumChain } from '@aztec/ethereum';
|
|
20
|
+
import { type EthereumChain, type L1ContractsConfig, createEthereumChain } from '@aztec/ethereum';
|
|
21
21
|
import { makeTuple } from '@aztec/foundation/array';
|
|
22
22
|
import { areArraysEqual, compactArray, times } from '@aztec/foundation/collection';
|
|
23
23
|
import { type Signature } from '@aztec/foundation/eth-signature';
|
|
@@ -58,7 +58,6 @@ import {
|
|
|
58
58
|
publicActions,
|
|
59
59
|
} from 'viem';
|
|
60
60
|
import { privateKeyToAccount } from 'viem/accounts';
|
|
61
|
-
import type * as chains from 'viem/chains';
|
|
62
61
|
|
|
63
62
|
import { type PublisherConfig, type TxSenderConfig } from './config.js';
|
|
64
63
|
import { L1PublisherMetrics } from './l1-publisher-metrics.js';
|
|
@@ -145,19 +144,19 @@ export class L1Publisher {
|
|
|
145
144
|
|
|
146
145
|
protected log = createDebugLogger('aztec:sequencer:publisher');
|
|
147
146
|
|
|
148
|
-
|
|
147
|
+
protected rollupContract: GetContractReturnType<
|
|
149
148
|
typeof RollupAbi,
|
|
150
|
-
WalletClient<HttpTransport,
|
|
149
|
+
WalletClient<HttpTransport, Chain, PrivateKeyAccount>
|
|
151
150
|
>;
|
|
152
|
-
|
|
151
|
+
protected governanceProposerContract?: GetContractReturnType<
|
|
153
152
|
typeof GovernanceProposerAbi,
|
|
154
|
-
WalletClient<HttpTransport,
|
|
153
|
+
WalletClient<HttpTransport, Chain, PrivateKeyAccount>
|
|
155
154
|
> = undefined;
|
|
156
155
|
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
156
|
+
protected publicClient: PublicClient<HttpTransport, Chain>;
|
|
157
|
+
protected walletClient: WalletClient<HttpTransport, Chain, PrivateKeyAccount>;
|
|
158
|
+
protected account: PrivateKeyAccount;
|
|
159
|
+
protected ethereumSlotDuration: bigint;
|
|
161
160
|
|
|
162
161
|
public static PROPOSE_GAS_GUESS: bigint = 12_000_000n;
|
|
163
162
|
public static PROPOSE_AND_CLAIM_GAS_GUESS: bigint = this.PROPOSE_GAS_GUESS + 100_000n;
|
|
@@ -175,11 +174,7 @@ export class L1Publisher {
|
|
|
175
174
|
this.account = privateKeyToAccount(publisherPrivateKey);
|
|
176
175
|
this.log.debug(`Publishing from address ${this.account.address}`);
|
|
177
176
|
|
|
178
|
-
this.walletClient = createWalletClient(
|
|
179
|
-
account: this.account,
|
|
180
|
-
chain: chain.chainInfo,
|
|
181
|
-
transport: http(chain.rpcUrl),
|
|
182
|
-
});
|
|
177
|
+
this.walletClient = this.createWalletClient(this.account, chain);
|
|
183
178
|
|
|
184
179
|
this.publicClient = createPublicClient({
|
|
185
180
|
chain: chain.chainInfo,
|
|
@@ -202,6 +197,17 @@ export class L1Publisher {
|
|
|
202
197
|
}
|
|
203
198
|
}
|
|
204
199
|
|
|
200
|
+
protected createWalletClient(
|
|
201
|
+
account: PrivateKeyAccount,
|
|
202
|
+
chain: EthereumChain,
|
|
203
|
+
): WalletClient<HttpTransport, Chain, PrivateKeyAccount> {
|
|
204
|
+
return createWalletClient({
|
|
205
|
+
account,
|
|
206
|
+
chain: chain.chainInfo,
|
|
207
|
+
transport: http(chain.rpcUrl),
|
|
208
|
+
});
|
|
209
|
+
}
|
|
210
|
+
|
|
205
211
|
public getPayLoad() {
|
|
206
212
|
return this.payload;
|
|
207
213
|
}
|
|
@@ -226,7 +232,7 @@ export class L1Publisher {
|
|
|
226
232
|
|
|
227
233
|
public getRollupContract(): GetContractReturnType<
|
|
228
234
|
typeof RollupAbi,
|
|
229
|
-
WalletClient<HttpTransport,
|
|
235
|
+
WalletClient<HttpTransport, Chain, PrivateKeyAccount>
|
|
230
236
|
> {
|
|
231
237
|
return this.rollupContract;
|
|
232
238
|
}
|
|
@@ -732,10 +738,12 @@ export class L1Publisher {
|
|
|
732
738
|
: [];
|
|
733
739
|
const txHashes = encodedData.txHashes ? encodedData.txHashes.map(txHash => txHash.to0xString()) : [];
|
|
734
740
|
const args = [
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
741
|
+
{
|
|
742
|
+
header: `0x${encodedData.header.toString('hex')}`,
|
|
743
|
+
archive: `0x${encodedData.archive.toString('hex')}`,
|
|
744
|
+
blockHash: `0x${encodedData.blockHash.toString('hex')}`,
|
|
745
|
+
txHashes,
|
|
746
|
+
},
|
|
739
747
|
attestations,
|
|
740
748
|
`0x${encodedData.body.toString('hex')}`,
|
|
741
749
|
] as const;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { type EthereumChain } from '@aztec/ethereum';
|
|
2
|
+
import { type Delayer, withDelayer } from '@aztec/ethereum/test';
|
|
3
|
+
|
|
4
|
+
import { type Chain, type HttpTransport, type PrivateKeyAccount, type WalletClient } from 'viem';
|
|
5
|
+
|
|
6
|
+
import { L1Publisher } from './l1-publisher.js';
|
|
7
|
+
|
|
8
|
+
export class TestL1Publisher extends L1Publisher {
|
|
9
|
+
public delayer: Delayer | undefined;
|
|
10
|
+
|
|
11
|
+
protected override createWalletClient(
|
|
12
|
+
account: PrivateKeyAccount,
|
|
13
|
+
chain: EthereumChain,
|
|
14
|
+
): WalletClient<HttpTransport, Chain, PrivateKeyAccount> {
|
|
15
|
+
const baseClient = super.createWalletClient(account, chain);
|
|
16
|
+
const { client, delayer } = withDelayer(baseClient, { ethereumSlotDuration: this.ethereumSlotDuration });
|
|
17
|
+
this.delayer = delayer;
|
|
18
|
+
return client;
|
|
19
|
+
}
|
|
20
|
+
}
|
package/src/sequencer/metrics.ts
CHANGED
|
@@ -21,6 +21,8 @@ export class SequencerMetrics {
|
|
|
21
21
|
private currentBlockNumber: Gauge;
|
|
22
22
|
private currentBlockSize: Gauge;
|
|
23
23
|
|
|
24
|
+
private timeToCollectAttestations: Gauge;
|
|
25
|
+
|
|
24
26
|
constructor(client: TelemetryClient, getState: SequencerStateCallback, name = 'Sequencer') {
|
|
25
27
|
const meter = client.getMeter(name);
|
|
26
28
|
this.tracer = client.getTracer(name);
|
|
@@ -60,9 +62,26 @@ export class SequencerMetrics {
|
|
|
60
62
|
description: 'Current block number',
|
|
61
63
|
});
|
|
62
64
|
|
|
65
|
+
this.timeToCollectAttestations = meter.createGauge(Metrics.SEQUENCER_TIME_TO_COLLECT_ATTESTATIONS, {
|
|
66
|
+
description: 'The time spent collecting attestations from committee members',
|
|
67
|
+
});
|
|
68
|
+
|
|
63
69
|
this.setCurrentBlock(0, 0);
|
|
64
70
|
}
|
|
65
71
|
|
|
72
|
+
startCollectingAttestationsTimer(): () => void {
|
|
73
|
+
const startTime = Date.now();
|
|
74
|
+
const stop = () => {
|
|
75
|
+
const duration = Date.now() - startTime;
|
|
76
|
+
this.recordTimeToCollectAttestations(duration);
|
|
77
|
+
};
|
|
78
|
+
return stop.bind(this);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
recordTimeToCollectAttestations(time: number) {
|
|
82
|
+
this.timeToCollectAttestations.record(time);
|
|
83
|
+
}
|
|
84
|
+
|
|
66
85
|
recordCancelledBlock() {
|
|
67
86
|
this.blockCounter.add(1, {
|
|
68
87
|
[Attributes.STATUS]: 'cancelled',
|
|
@@ -15,6 +15,7 @@ import {
|
|
|
15
15
|
AppendOnlyTreeSnapshot,
|
|
16
16
|
ContentCommitment,
|
|
17
17
|
GENESIS_ARCHIVE_ROOT,
|
|
18
|
+
type GlobalVariables,
|
|
18
19
|
Header,
|
|
19
20
|
StateReference,
|
|
20
21
|
} from '@aztec/circuits.js';
|
|
@@ -112,6 +113,9 @@ export class Sequencer {
|
|
|
112
113
|
this.updateConfig(config);
|
|
113
114
|
this.metrics = new SequencerMetrics(telemetry, () => this.state, 'Sequencer');
|
|
114
115
|
this.log.verbose(`Initialized sequencer with ${this.minTxsPerBLock}-${this.maxTxsPerBlock} txs per block.`);
|
|
116
|
+
|
|
117
|
+
// Register the block builder with the validator client for re-execution
|
|
118
|
+
this.validatorClient?.registerBlockBuilder(this.buildBlock.bind(this));
|
|
115
119
|
}
|
|
116
120
|
|
|
117
121
|
get tracer(): Tracer {
|
|
@@ -167,11 +171,11 @@ export class Sequencer {
|
|
|
167
171
|
[SequencerState.IDLE]: this.aztecSlotDuration,
|
|
168
172
|
[SequencerState.SYNCHRONIZING]: this.aztecSlotDuration,
|
|
169
173
|
[SequencerState.PROPOSER_CHECK]: this.aztecSlotDuration, // We always want to allow the full slot to check if we are the proposer
|
|
170
|
-
[SequencerState.WAITING_FOR_TXS]:
|
|
171
|
-
[SequencerState.CREATING_BLOCK]:
|
|
172
|
-
[SequencerState.PUBLISHING_BLOCK_TO_PEERS]:
|
|
173
|
-
[SequencerState.WAITING_FOR_ATTESTATIONS]:
|
|
174
|
-
[SequencerState.PUBLISHING_BLOCK]:
|
|
174
|
+
[SequencerState.WAITING_FOR_TXS]: 5,
|
|
175
|
+
[SequencerState.CREATING_BLOCK]: 7,
|
|
176
|
+
[SequencerState.PUBLISHING_BLOCK_TO_PEERS]: 7 + this.maxTxsPerBlock * 2, // if we take 5 seconds to create block, then 4 transactions at 2 seconds each
|
|
177
|
+
[SequencerState.WAITING_FOR_ATTESTATIONS]: 7 + this.maxTxsPerBlock * 2 + 3, // it shouldn't take 3 seconds to publish to peers
|
|
178
|
+
[SequencerState.PUBLISHING_BLOCK]: 7 + this.maxTxsPerBlock * 2 + 3 + 5, // wait 5 seconds for attestations
|
|
175
179
|
};
|
|
176
180
|
if (this.enforceTimeTable && newTimeTable[SequencerState.PUBLISHING_BLOCK] > this.aztecSlotDuration) {
|
|
177
181
|
throw new Error('Sequencer cannot publish block in less than a slot');
|
|
@@ -185,7 +189,7 @@ export class Sequencer {
|
|
|
185
189
|
public start() {
|
|
186
190
|
this.runningPromise = new RunningPromise(this.work.bind(this), this.pollingIntervalMs);
|
|
187
191
|
this.runningPromise.start();
|
|
188
|
-
this.setState(SequencerState.IDLE, true /** force */);
|
|
192
|
+
this.setState(SequencerState.IDLE, 0, true /** force */);
|
|
189
193
|
this.log.info('Sequencer started');
|
|
190
194
|
return Promise.resolve();
|
|
191
195
|
}
|
|
@@ -197,7 +201,7 @@ export class Sequencer {
|
|
|
197
201
|
this.log.debug(`Stopping sequencer`);
|
|
198
202
|
await this.runningPromise?.stop();
|
|
199
203
|
this.publisher.interrupt();
|
|
200
|
-
this.setState(SequencerState.STOPPED, true /** force */);
|
|
204
|
+
this.setState(SequencerState.STOPPED, 0, true /** force */);
|
|
201
205
|
this.log.info('Stopped sequencer');
|
|
202
206
|
}
|
|
203
207
|
|
|
@@ -208,7 +212,7 @@ export class Sequencer {
|
|
|
208
212
|
this.log.info('Restarting sequencer');
|
|
209
213
|
this.publisher.restart();
|
|
210
214
|
this.runningPromise!.start();
|
|
211
|
-
this.setState(SequencerState.IDLE, true /** force */);
|
|
215
|
+
this.setState(SequencerState.IDLE, 0, true /** force */);
|
|
212
216
|
}
|
|
213
217
|
|
|
214
218
|
/**
|
|
@@ -228,7 +232,7 @@ export class Sequencer {
|
|
|
228
232
|
* - If our block for some reason is not included, revert the state
|
|
229
233
|
*/
|
|
230
234
|
protected async doRealWork() {
|
|
231
|
-
this.setState(SequencerState.SYNCHRONIZING);
|
|
235
|
+
this.setState(SequencerState.SYNCHRONIZING, 0);
|
|
232
236
|
// Update state when the previous block has been synced
|
|
233
237
|
const prevBlockSynced = await this.isBlockSynced();
|
|
234
238
|
// Do not go forward with new block if the previous one has not been mined and processed
|
|
@@ -239,7 +243,7 @@ export class Sequencer {
|
|
|
239
243
|
|
|
240
244
|
this.log.debug('Previous block has been mined and processed');
|
|
241
245
|
|
|
242
|
-
this.setState(SequencerState.PROPOSER_CHECK);
|
|
246
|
+
this.setState(SequencerState.PROPOSER_CHECK, 0);
|
|
243
247
|
|
|
244
248
|
const chainTip = await this.l2BlockSource.getBlock(-1);
|
|
245
249
|
const historicalHeader = chainTip?.header;
|
|
@@ -273,8 +277,9 @@ export class Sequencer {
|
|
|
273
277
|
if (!this.shouldProposeBlock(historicalHeader, {})) {
|
|
274
278
|
return;
|
|
275
279
|
}
|
|
280
|
+
const secondsIntoSlot = getSecondsIntoSlot(this.l1GenesisTime, this.aztecSlotDuration, Number(slot));
|
|
276
281
|
|
|
277
|
-
this.setState(SequencerState.WAITING_FOR_TXS);
|
|
282
|
+
this.setState(SequencerState.WAITING_FOR_TXS, secondsIntoSlot);
|
|
278
283
|
|
|
279
284
|
// Get txs to build the new block.
|
|
280
285
|
const pendingTxs = this.p2pClient.getTxs('pending');
|
|
@@ -319,7 +324,7 @@ export class Sequencer {
|
|
|
319
324
|
} catch (err) {
|
|
320
325
|
this.log.error(`Error assembling block`, (err as any).stack);
|
|
321
326
|
}
|
|
322
|
-
this.setState(SequencerState.IDLE);
|
|
327
|
+
this.setState(SequencerState.IDLE, 0);
|
|
323
328
|
}
|
|
324
329
|
|
|
325
330
|
protected async work() {
|
|
@@ -333,7 +338,7 @@ export class Sequencer {
|
|
|
333
338
|
throw err;
|
|
334
339
|
}
|
|
335
340
|
} finally {
|
|
336
|
-
this.setState(SequencerState.IDLE);
|
|
341
|
+
this.setState(SequencerState.IDLE, 0);
|
|
337
342
|
}
|
|
338
343
|
}
|
|
339
344
|
|
|
@@ -392,14 +397,13 @@ export class Sequencer {
|
|
|
392
397
|
return true;
|
|
393
398
|
}
|
|
394
399
|
|
|
395
|
-
setState(proposedState: SequencerState, force: boolean = false) {
|
|
400
|
+
setState(proposedState: SequencerState, secondsIntoSlot: number, force: boolean = false) {
|
|
396
401
|
if (this.state === SequencerState.STOPPED && force !== true) {
|
|
397
402
|
this.log.warn(
|
|
398
403
|
`Cannot set sequencer from ${this.state} to ${proposedState} as it is stopped. Set force=true to override.`,
|
|
399
404
|
);
|
|
400
405
|
return;
|
|
401
406
|
}
|
|
402
|
-
const secondsIntoSlot = getSecondsIntoSlot(this.l1GenesisTime, this.aztecSlotDuration);
|
|
403
407
|
if (!this.doIHaveEnoughTimeLeft(proposedState, secondsIntoSlot)) {
|
|
404
408
|
throw new SequencerTooSlowError(this.state, proposedState, this.timeTable[proposedState], secondsIntoSlot);
|
|
405
409
|
}
|
|
@@ -473,37 +477,21 @@ export class Sequencer {
|
|
|
473
477
|
}
|
|
474
478
|
|
|
475
479
|
/**
|
|
476
|
-
*
|
|
480
|
+
* Build a block
|
|
477
481
|
*
|
|
478
|
-
*
|
|
479
|
-
* is being rolled back if the block is dropped.
|
|
482
|
+
* Shared between the sequencer and the validator for re-execution
|
|
480
483
|
*
|
|
481
484
|
* @param validTxs - The valid transactions to construct the block from
|
|
482
|
-
* @param
|
|
485
|
+
* @param newGlobalVariables - The global variables for the new block
|
|
483
486
|
* @param historicalHeader - The historical header of the parent
|
|
487
|
+
* @param interrupt - The interrupt callback, used to validate the block for submission and check if we should propose the block
|
|
484
488
|
*/
|
|
485
|
-
|
|
486
|
-
[Attributes.BLOCK_NUMBER]: proposalHeader.globalVariables.blockNumber.toNumber(),
|
|
487
|
-
}))
|
|
488
|
-
private async buildBlockAndAttemptToPublish(
|
|
489
|
+
private async buildBlock(
|
|
489
490
|
validTxs: Tx[],
|
|
490
|
-
|
|
491
|
-
historicalHeader
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
const newGlobalVariables = proposalHeader.globalVariables;
|
|
496
|
-
|
|
497
|
-
this.metrics.recordNewBlock(newGlobalVariables.blockNumber.toNumber(), validTxs.length);
|
|
498
|
-
const workTimer = new Timer();
|
|
499
|
-
this.setState(SequencerState.CREATING_BLOCK);
|
|
500
|
-
this.log.info(
|
|
501
|
-
`Building blockNumber=${newGlobalVariables.blockNumber.toNumber()} txCount=${
|
|
502
|
-
validTxs.length
|
|
503
|
-
} slotNumber=${newGlobalVariables.slotNumber.toNumber()}`,
|
|
504
|
-
);
|
|
505
|
-
|
|
506
|
-
// Get l1 to l2 messages from the contract
|
|
491
|
+
newGlobalVariables: GlobalVariables,
|
|
492
|
+
historicalHeader?: Header,
|
|
493
|
+
interrupt?: (processedTxs: ProcessedTx[]) => Promise<void>,
|
|
494
|
+
) {
|
|
507
495
|
this.log.debug('Requesting L1 to L2 messages from contract');
|
|
508
496
|
const l1ToL2Messages = await this.l1ToL2MessageSource.getL1ToL2Messages(newGlobalVariables.blockNumber.toBigInt());
|
|
509
497
|
this.log.verbose(
|
|
@@ -513,11 +501,15 @@ export class Sequencer {
|
|
|
513
501
|
const numRealTxs = validTxs.length;
|
|
514
502
|
const blockSize = Math.max(2, numRealTxs);
|
|
515
503
|
|
|
504
|
+
// Sync to the previous block at least
|
|
505
|
+
await this.worldState.syncImmediate(newGlobalVariables.blockNumber.toNumber() - 1);
|
|
506
|
+
this.log.verbose(`Synced to previous block ${newGlobalVariables.blockNumber.toNumber() - 1}`);
|
|
507
|
+
|
|
516
508
|
// NB: separating the dbs because both should update the state
|
|
517
509
|
const publicProcessorFork = await this.worldState.fork();
|
|
518
510
|
const orchestratorFork = await this.worldState.fork();
|
|
511
|
+
|
|
519
512
|
try {
|
|
520
|
-
// We create a fresh processor each time to reset any cached state (eg storage writes)
|
|
521
513
|
const processor = this.publicProcessorFactory.create(publicProcessorFork, historicalHeader, newGlobalVariables);
|
|
522
514
|
const blockBuildingTimer = new Timer();
|
|
523
515
|
const blockBuilder = this.blockBuilderFactory.create(orchestratorFork);
|
|
@@ -537,6 +529,62 @@ export class Sequencer {
|
|
|
537
529
|
await this.p2pClient.deleteTxs(Tx.getHashes(failedTxData));
|
|
538
530
|
}
|
|
539
531
|
|
|
532
|
+
await interrupt?.(processedTxs);
|
|
533
|
+
|
|
534
|
+
// All real transactions have been added, set the block as full and complete the proving.
|
|
535
|
+
const block = await blockBuilder.setBlockCompleted();
|
|
536
|
+
|
|
537
|
+
return { block, publicProcessorDuration, numProcessedTxs: processedTxs.length, blockBuildingTimer };
|
|
538
|
+
} finally {
|
|
539
|
+
// We create a fresh processor each time to reset any cached state (eg storage writes)
|
|
540
|
+
await publicProcessorFork.close();
|
|
541
|
+
await orchestratorFork.close();
|
|
542
|
+
}
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
/**
|
|
546
|
+
* @notice Build and propose a block to the chain
|
|
547
|
+
*
|
|
548
|
+
* @dev MUST throw instead of exiting early to ensure that world-state
|
|
549
|
+
* is being rolled back if the block is dropped.
|
|
550
|
+
*
|
|
551
|
+
* @param validTxs - The valid transactions to construct the block from
|
|
552
|
+
* @param proposalHeader - The partial header constructed for the proposal
|
|
553
|
+
* @param historicalHeader - The historical header of the parent
|
|
554
|
+
*/
|
|
555
|
+
@trackSpan('Sequencer.buildBlockAndAttemptToPublish', (_validTxs, proposalHeader, _historicalHeader) => ({
|
|
556
|
+
[Attributes.BLOCK_NUMBER]: proposalHeader.globalVariables.blockNumber.toNumber(),
|
|
557
|
+
}))
|
|
558
|
+
private async buildBlockAndAttemptToPublish(
|
|
559
|
+
validTxs: Tx[],
|
|
560
|
+
proposalHeader: Header,
|
|
561
|
+
historicalHeader: Header | undefined,
|
|
562
|
+
): Promise<void> {
|
|
563
|
+
await this.publisher.validateBlockForSubmission(proposalHeader);
|
|
564
|
+
|
|
565
|
+
const newGlobalVariables = proposalHeader.globalVariables;
|
|
566
|
+
|
|
567
|
+
this.metrics.recordNewBlock(newGlobalVariables.blockNumber.toNumber(), validTxs.length);
|
|
568
|
+
const workTimer = new Timer();
|
|
569
|
+
const secondsIntoSlot = getSecondsIntoSlot(
|
|
570
|
+
this.l1GenesisTime,
|
|
571
|
+
this.aztecSlotDuration,
|
|
572
|
+
newGlobalVariables.slotNumber.toNumber(),
|
|
573
|
+
);
|
|
574
|
+
this.setState(SequencerState.CREATING_BLOCK, secondsIntoSlot);
|
|
575
|
+
this.log.info(
|
|
576
|
+
`Building blockNumber=${newGlobalVariables.blockNumber.toNumber()} txCount=${
|
|
577
|
+
validTxs.length
|
|
578
|
+
} slotNumber=${newGlobalVariables.slotNumber.toNumber()}`,
|
|
579
|
+
);
|
|
580
|
+
|
|
581
|
+
/**
|
|
582
|
+
* BuildBlock is shared between the sequencer and the validator for re-execution
|
|
583
|
+
* We use the interrupt callback to validate the block for submission and check if we should propose the block
|
|
584
|
+
*
|
|
585
|
+
* If we fail, we throw an error in order to roll back
|
|
586
|
+
*/
|
|
587
|
+
const interrupt = async (processedTxs: ProcessedTx[]) => {
|
|
540
588
|
await this.publisher.validateBlockForSubmission(proposalHeader);
|
|
541
589
|
|
|
542
590
|
if (
|
|
@@ -548,9 +596,15 @@ export class Sequencer {
|
|
|
548
596
|
// TODO: Roll back changes to world state
|
|
549
597
|
throw new Error('Should not propose the block');
|
|
550
598
|
}
|
|
599
|
+
};
|
|
551
600
|
|
|
552
|
-
|
|
553
|
-
const block = await
|
|
601
|
+
try {
|
|
602
|
+
const { block, publicProcessorDuration, numProcessedTxs, blockBuildingTimer } = await this.buildBlock(
|
|
603
|
+
validTxs,
|
|
604
|
+
newGlobalVariables,
|
|
605
|
+
historicalHeader,
|
|
606
|
+
interrupt,
|
|
607
|
+
);
|
|
554
608
|
|
|
555
609
|
// TODO(@PhilWindle) We should probably periodically check for things like another
|
|
556
610
|
// block being published before ours instead of just waiting on our block
|
|
@@ -558,7 +612,7 @@ export class Sequencer {
|
|
|
558
612
|
await this.publisher.validateBlockForSubmission(block.header);
|
|
559
613
|
|
|
560
614
|
const workDuration = workTimer.ms();
|
|
561
|
-
this.log.
|
|
615
|
+
this.log.info(
|
|
562
616
|
`Assembled block ${block.number} (txEffectsHash: ${block.header.contentCommitment.txsEffectsHash.toString(
|
|
563
617
|
'hex',
|
|
564
618
|
)})`,
|
|
@@ -573,35 +627,32 @@ export class Sequencer {
|
|
|
573
627
|
);
|
|
574
628
|
|
|
575
629
|
if (this.isFlushing) {
|
|
576
|
-
this.log.
|
|
630
|
+
this.log.info(`Flushing completed`);
|
|
577
631
|
}
|
|
578
632
|
|
|
579
633
|
const txHashes = validTxs.map(tx => tx.getTxHash());
|
|
580
634
|
|
|
581
635
|
this.isFlushing = false;
|
|
582
636
|
this.log.verbose('Collecting attestations');
|
|
637
|
+
const stopCollectingAttestationsTimer = this.metrics.startCollectingAttestationsTimer();
|
|
583
638
|
const attestations = await this.collectAttestations(block, txHashes);
|
|
584
639
|
this.log.verbose('Attestations collected');
|
|
585
|
-
|
|
640
|
+
stopCollectingAttestationsTimer();
|
|
586
641
|
this.log.verbose('Collecting proof quotes');
|
|
642
|
+
|
|
587
643
|
const proofQuote = await this.createProofClaimForPreviousEpoch(newGlobalVariables.slotNumber.toBigInt());
|
|
588
|
-
this.log.
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
throw err;
|
|
601
|
-
}
|
|
602
|
-
} finally {
|
|
603
|
-
await publicProcessorFork.close();
|
|
604
|
-
await orchestratorFork.close();
|
|
644
|
+
this.log.info(proofQuote ? `Using proof quote ${inspect(proofQuote.payload)}` : 'No proof quote available');
|
|
645
|
+
|
|
646
|
+
await this.publishL2Block(block, attestations, txHashes, proofQuote);
|
|
647
|
+
this.metrics.recordPublishedBlock(workDuration);
|
|
648
|
+
this.log.info(
|
|
649
|
+
`Submitted rollup block ${block.number} with ${numProcessedTxs} transactions duration=${Math.ceil(
|
|
650
|
+
workDuration,
|
|
651
|
+
)}ms (Submitter: ${this.publisher.getSenderAddress()})`,
|
|
652
|
+
);
|
|
653
|
+
} catch (err) {
|
|
654
|
+
this.metrics.recordFailedBlock();
|
|
655
|
+
throw err;
|
|
605
656
|
}
|
|
606
657
|
}
|
|
607
658
|
|
|
@@ -621,7 +672,7 @@ export class Sequencer {
|
|
|
621
672
|
this.log.debug(`Attesting committee length ${committee.length}`);
|
|
622
673
|
|
|
623
674
|
if (committee.length === 0) {
|
|
624
|
-
this.log.
|
|
675
|
+
this.log.verbose(`Attesting committee length is 0, skipping`);
|
|
625
676
|
return undefined;
|
|
626
677
|
}
|
|
627
678
|
|
|
@@ -633,16 +684,28 @@ export class Sequencer {
|
|
|
633
684
|
|
|
634
685
|
const numberOfRequiredAttestations = Math.floor((committee.length * 2) / 3) + 1;
|
|
635
686
|
|
|
636
|
-
this.log.
|
|
687
|
+
this.log.info('Creating block proposal');
|
|
637
688
|
const proposal = await this.validatorClient.createBlockProposal(block.header, block.archive.root, txHashes);
|
|
638
689
|
|
|
639
|
-
|
|
640
|
-
|
|
690
|
+
let secondsIntoSlot = getSecondsIntoSlot(
|
|
691
|
+
this.l1GenesisTime,
|
|
692
|
+
this.aztecSlotDuration,
|
|
693
|
+
block.header.globalVariables.slotNumber.toNumber(),
|
|
694
|
+
);
|
|
695
|
+
|
|
696
|
+
this.setState(SequencerState.PUBLISHING_BLOCK_TO_PEERS, secondsIntoSlot);
|
|
697
|
+
this.log.info('Broadcasting block proposal to validators');
|
|
641
698
|
this.validatorClient.broadcastBlockProposal(proposal);
|
|
642
699
|
|
|
643
|
-
|
|
700
|
+
secondsIntoSlot = getSecondsIntoSlot(
|
|
701
|
+
this.l1GenesisTime,
|
|
702
|
+
this.aztecSlotDuration,
|
|
703
|
+
block.header.globalVariables.slotNumber.toNumber(),
|
|
704
|
+
);
|
|
705
|
+
|
|
706
|
+
this.setState(SequencerState.WAITING_FOR_ATTESTATIONS, secondsIntoSlot);
|
|
644
707
|
const attestations = await this.validatorClient.collectAttestations(proposal, numberOfRequiredAttestations);
|
|
645
|
-
this.log.
|
|
708
|
+
this.log.info(`Collected attestations from validators, number of attestations: ${attestations.length}`);
|
|
646
709
|
|
|
647
710
|
// note: the smart contract requires that the signatures are provided in the order of the committee
|
|
648
711
|
return orderAttestations(attestations, committee);
|
|
@@ -659,7 +722,7 @@ export class Sequencer {
|
|
|
659
722
|
|
|
660
723
|
// Get quotes for the epoch to be proven
|
|
661
724
|
const quotes = await this.p2pClient.getEpochProofQuotes(epochToProve);
|
|
662
|
-
this.log.
|
|
725
|
+
this.log.info(`Retrieved ${quotes.length} quotes, slot: ${slotNumber}, epoch to prove: ${epochToProve}`);
|
|
663
726
|
for (const quote of quotes) {
|
|
664
727
|
this.log.verbose(inspect(quote.payload));
|
|
665
728
|
}
|
|
@@ -670,7 +733,7 @@ export class Sequencer {
|
|
|
670
733
|
|
|
671
734
|
const validQuotes = (await validQuotesPromise).filter((q): q is EpochProofQuote => !!q);
|
|
672
735
|
if (!validQuotes.length) {
|
|
673
|
-
this.log.
|
|
736
|
+
this.log.warn(`Failed to find any valid proof quotes`);
|
|
674
737
|
return undefined;
|
|
675
738
|
}
|
|
676
739
|
// pick the quote with the lowest fee
|
|
@@ -697,8 +760,13 @@ export class Sequencer {
|
|
|
697
760
|
txHashes?: TxHash[],
|
|
698
761
|
proofQuote?: EpochProofQuote,
|
|
699
762
|
) {
|
|
763
|
+
const secondsIntoSlot = getSecondsIntoSlot(
|
|
764
|
+
this.l1GenesisTime,
|
|
765
|
+
this.aztecSlotDuration,
|
|
766
|
+
block.header.globalVariables.slotNumber.toNumber(),
|
|
767
|
+
);
|
|
700
768
|
// Publishes new block to the network and awaits the tx to be mined
|
|
701
|
-
this.setState(SequencerState.PUBLISHING_BLOCK);
|
|
769
|
+
this.setState(SequencerState.PUBLISHING_BLOCK, secondsIntoSlot);
|
|
702
770
|
|
|
703
771
|
const publishedL2Block = await this.publisher.proposeL2Block(block, attestations, txHashes, proofQuote);
|
|
704
772
|
if (!publishedL2Block) {
|
package/src/sequencer/utils.ts
CHANGED
|
@@ -73,6 +73,7 @@ export function orderAttestations(attestations: BlockAttestation[], orderAddress
|
|
|
73
73
|
return orderedAttestations;
|
|
74
74
|
}
|
|
75
75
|
|
|
76
|
-
export function getSecondsIntoSlot(l1GenesisTime: number, aztecSlotDuration: number): number {
|
|
77
|
-
|
|
76
|
+
export function getSecondsIntoSlot(l1GenesisTime: number, aztecSlotDuration: number, slotNumber: number): number {
|
|
77
|
+
const slotStartTimestamp = l1GenesisTime + slotNumber * aztecSlotDuration;
|
|
78
|
+
return Date.now() / 1000 - slotStartTimestamp;
|
|
78
79
|
}
|