@aztec/sequencer-client 0.56.0 → 0.58.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/index.d.ts +2 -2
- package/dest/block_builder/index.d.ts.map +1 -1
- package/dest/block_builder/light.d.ts +8 -12
- package/dest/block_builder/light.d.ts.map +1 -1
- package/dest/block_builder/light.js +11 -15
- package/dest/block_builder/orchestrator.d.ts +8 -11
- package/dest/block_builder/orchestrator.d.ts.map +1 -1
- package/dest/block_builder/orchestrator.js +3 -10
- package/dest/client/sequencer-client.d.ts +2 -2
- package/dest/client/sequencer-client.d.ts.map +1 -1
- package/dest/client/sequencer-client.js +3 -4
- package/dest/config.d.ts.map +1 -1
- package/dest/config.js +11 -9
- package/dest/global_variable_builder/global_builder.d.ts.map +1 -1
- package/dest/global_variable_builder/global_builder.js +2 -1
- package/dest/publisher/config.d.ts.map +1 -1
- package/dest/publisher/config.js +7 -2
- 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/l1-publisher-metrics.d.ts.map +1 -1
- package/dest/publisher/l1-publisher-metrics.js +2 -1
- package/dest/publisher/l1-publisher.d.ts +52 -36
- package/dest/publisher/l1-publisher.d.ts.map +1 -1
- package/dest/publisher/l1-publisher.js +305 -117
- package/dest/publisher/utils.d.ts +1 -1
- package/dest/publisher/utils.js +1 -1
- package/dest/sequencer/sequencer.d.ts +6 -6
- package/dest/sequencer/sequencer.d.ts.map +1 -1
- package/dest/sequencer/sequencer.js +142 -88
- package/dest/tx_validator/gas_validator.d.ts.map +1 -1
- package/dest/tx_validator/gas_validator.js +7 -5
- package/dest/tx_validator/phases_validator.d.ts +1 -1
- package/dest/tx_validator/phases_validator.d.ts.map +1 -1
- package/dest/tx_validator/phases_validator.js +1 -1
- package/dest/tx_validator/tx_validator_factory.d.ts +9 -7
- package/dest/tx_validator/tx_validator_factory.d.ts.map +1 -1
- package/dest/tx_validator/tx_validator_factory.js +20 -10
- package/package.json +22 -19
- package/src/block_builder/index.ts +2 -2
- package/src/block_builder/light.ts +16 -24
- package/src/block_builder/orchestrator.ts +10 -18
- package/src/client/sequencer-client.ts +3 -9
- package/src/config.ts +10 -8
- package/src/global_variable_builder/global_builder.ts +1 -0
- package/src/publisher/config.ts +6 -1
- package/src/publisher/index.ts +1 -1
- package/src/publisher/l1-publisher-metrics.ts +1 -0
- package/src/publisher/l1-publisher.ts +435 -144
- package/src/publisher/utils.ts +1 -1
- package/src/sequencer/sequencer.ts +173 -108
- package/src/tx_validator/gas_validator.ts +6 -7
- package/src/tx_validator/phases_validator.ts +1 -1
- package/src/tx_validator/tx_validator_factory.ts +39 -15
package/src/publisher/utils.ts
CHANGED
|
@@ -2,7 +2,7 @@ import { type Logger } from '@aztec/foundation/log';
|
|
|
2
2
|
|
|
3
3
|
import { BaseError, ContractFunctionRevertedError } from 'viem';
|
|
4
4
|
|
|
5
|
-
export function
|
|
5
|
+
export function prettyLogViemError(err: any, logger: Logger) {
|
|
6
6
|
if (err instanceof BaseError) {
|
|
7
7
|
const revertError = err.walk(err => err instanceof ContractFunctionRevertedError);
|
|
8
8
|
if (revertError instanceof ContractFunctionRevertedError) {
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
type BlockAttestation,
|
|
3
|
+
type EpochProofQuote,
|
|
3
4
|
type L1ToL2MessageSource,
|
|
4
5
|
type L2Block,
|
|
5
6
|
type L2BlockSource,
|
|
@@ -7,10 +8,13 @@ import {
|
|
|
7
8
|
Tx,
|
|
8
9
|
type TxHash,
|
|
9
10
|
type TxValidator,
|
|
10
|
-
type WorldStateStatus,
|
|
11
11
|
type WorldStateSynchronizer,
|
|
12
12
|
} from '@aztec/circuit-types';
|
|
13
|
-
import {
|
|
13
|
+
import {
|
|
14
|
+
type AllowedElement,
|
|
15
|
+
BlockProofError,
|
|
16
|
+
type WorldStateSynchronizerStatus,
|
|
17
|
+
} from '@aztec/circuit-types/interfaces';
|
|
14
18
|
import { type L2BlockBuiltStats } from '@aztec/circuit-types/stats';
|
|
15
19
|
import {
|
|
16
20
|
AppendOnlyTreeSnapshot,
|
|
@@ -31,10 +35,12 @@ import { type PublicProcessorFactory } from '@aztec/simulator';
|
|
|
31
35
|
import { Attributes, type TelemetryClient, type Tracer, trackSpan } from '@aztec/telemetry-client';
|
|
32
36
|
import { type ValidatorClient } from '@aztec/validator-client';
|
|
33
37
|
|
|
38
|
+
import { inspect } from 'util';
|
|
39
|
+
|
|
34
40
|
import { type BlockBuilderFactory } from '../block_builder/index.js';
|
|
35
41
|
import { type GlobalVariableBuilder } from '../global_variable_builder/global_builder.js';
|
|
36
42
|
import { type L1Publisher } from '../publisher/l1-publisher.js';
|
|
37
|
-
import {
|
|
43
|
+
import { prettyLogViemError } from '../publisher/utils.js';
|
|
38
44
|
import { type TxValidatorFactory } from '../tx_validator/tx_validator_factory.js';
|
|
39
45
|
import { type SequencerConfig } from './config.js';
|
|
40
46
|
import { SequencerMetrics } from './metrics.js';
|
|
@@ -64,7 +70,6 @@ export class Sequencer {
|
|
|
64
70
|
// TODO: zero values should not be allowed for the following 2 values in PROD
|
|
65
71
|
private _coinbase = EthAddress.ZERO;
|
|
66
72
|
private _feeRecipient = AztecAddress.ZERO;
|
|
67
|
-
private lastPublishedBlock = 0;
|
|
68
73
|
private state = SequencerState.STOPPED;
|
|
69
74
|
private allowedInSetup: AllowedElement[] = [];
|
|
70
75
|
private allowedInTeardown: AllowedElement[] = [];
|
|
@@ -139,13 +144,12 @@ export class Sequencer {
|
|
|
139
144
|
/**
|
|
140
145
|
* Starts the sequencer and moves to IDLE state. Blocks until the initial sync is complete.
|
|
141
146
|
*/
|
|
142
|
-
public
|
|
143
|
-
await this.initialSync();
|
|
144
|
-
|
|
147
|
+
public start() {
|
|
145
148
|
this.runningPromise = new RunningPromise(this.work.bind(this), this.pollingIntervalMs);
|
|
146
149
|
this.runningPromise.start();
|
|
147
150
|
this.state = SequencerState.IDLE;
|
|
148
151
|
this.log.info('Sequencer started');
|
|
152
|
+
return Promise.resolve();
|
|
149
153
|
}
|
|
150
154
|
|
|
151
155
|
/**
|
|
@@ -177,11 +181,6 @@ export class Sequencer {
|
|
|
177
181
|
return { state: this.state };
|
|
178
182
|
}
|
|
179
183
|
|
|
180
|
-
protected async initialSync() {
|
|
181
|
-
// TODO: Should we wait for world state to be ready, or is the caller expected to run await start?
|
|
182
|
-
this.lastPublishedBlock = await this.worldState.status().then((s: WorldStateStatus) => s.syncedToL2Block);
|
|
183
|
-
}
|
|
184
|
-
|
|
185
184
|
/**
|
|
186
185
|
* @notice Performs most of the sequencer duties:
|
|
187
186
|
* - Checks if we are up to date
|
|
@@ -276,15 +275,14 @@ export class Sequencer {
|
|
|
276
275
|
// @note It is very important that the following function will FAIL and not just return early
|
|
277
276
|
// if it have made any state changes. If not, we won't rollback the state, and you will
|
|
278
277
|
// be in for a world of pain.
|
|
279
|
-
await this.
|
|
278
|
+
await this.buildBlockAndAttemptToPublish(validTxs, proposalHeader, historicalHeader);
|
|
280
279
|
} catch (err) {
|
|
281
280
|
if (BlockProofError.isBlockProofError(err)) {
|
|
282
281
|
const txHashes = err.txHashes.filter(h => !h.isZero());
|
|
283
282
|
this.log.warn(`Proving block failed, removing ${txHashes.length} txs from pool`);
|
|
284
283
|
await this.p2pClient.deleteTxs(txHashes);
|
|
285
284
|
}
|
|
286
|
-
this.log.error(`
|
|
287
|
-
await this.worldState.getLatest().rollback();
|
|
285
|
+
this.log.error(`Error assembling block`, (err as any).stack);
|
|
288
286
|
}
|
|
289
287
|
}
|
|
290
288
|
|
|
@@ -311,7 +309,8 @@ export class Sequencer {
|
|
|
311
309
|
this.log.debug(`Can propose block ${proposalBlockNumber} at slot ${slot}`);
|
|
312
310
|
return slot;
|
|
313
311
|
} catch (err) {
|
|
314
|
-
|
|
312
|
+
this.log.verbose(`Rejected from being able to propose at next block with ${tipArchive}`);
|
|
313
|
+
prettyLogViemError(err, this.log);
|
|
315
314
|
throw err;
|
|
316
315
|
}
|
|
317
316
|
}
|
|
@@ -392,10 +391,10 @@ export class Sequencer {
|
|
|
392
391
|
* @param proposalHeader - The partial header constructed for the proposal
|
|
393
392
|
* @param historicalHeader - The historical header of the parent
|
|
394
393
|
*/
|
|
395
|
-
@trackSpan('Sequencer.
|
|
394
|
+
@trackSpan('Sequencer.buildBlockAndAttemptToPublish', (_validTxs, proposalHeader, _historicalHeader) => ({
|
|
396
395
|
[Attributes.BLOCK_NUMBER]: proposalHeader.globalVariables.blockNumber.toNumber(),
|
|
397
396
|
}))
|
|
398
|
-
private async
|
|
397
|
+
private async buildBlockAndAttemptToPublish(
|
|
399
398
|
validTxs: Tx[],
|
|
400
399
|
proposalHeader: Header,
|
|
401
400
|
historicalHeader: Header | undefined,
|
|
@@ -416,89 +415,90 @@ export class Sequencer {
|
|
|
416
415
|
`Retrieved ${l1ToL2Messages.length} L1 to L2 messages for block ${newGlobalVariables.blockNumber.toNumber()}`,
|
|
417
416
|
);
|
|
418
417
|
|
|
419
|
-
// We create a fresh processor each time to reset any cached state (eg storage writes)
|
|
420
|
-
const processor = this.publicProcessorFactory.create(historicalHeader, newGlobalVariables);
|
|
421
|
-
|
|
422
418
|
const numRealTxs = validTxs.length;
|
|
423
419
|
const blockSize = Math.max(2, numRealTxs);
|
|
424
420
|
|
|
425
|
-
const
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
const
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
!this.shouldProposeBlock(historicalHeader, {
|
|
442
|
-
validTxsCount: validTxs.length,
|
|
443
|
-
processedTxsCount: processedTxs.length,
|
|
444
|
-
})
|
|
445
|
-
) {
|
|
446
|
-
blockBuilder.cancel();
|
|
447
|
-
throw new Error('Should not propose the block');
|
|
448
|
-
}
|
|
421
|
+
const fork = await this.worldState.fork();
|
|
422
|
+
try {
|
|
423
|
+
// We create a fresh processor each time to reset any cached state (eg storage writes)
|
|
424
|
+
const processor = this.publicProcessorFactory.create(fork, historicalHeader, newGlobalVariables);
|
|
425
|
+
const blockBuildingTimer = new Timer();
|
|
426
|
+
const blockBuilder = this.blockBuilderFactory.create(fork);
|
|
427
|
+
await blockBuilder.startNewBlock(blockSize, newGlobalVariables, l1ToL2Messages);
|
|
428
|
+
|
|
429
|
+
const [publicProcessorDuration, [processedTxs, failedTxs]] = await elapsed(() =>
|
|
430
|
+
processor.process(validTxs, blockSize, blockBuilder, this.txValidatorFactory.validatorForProcessedTxs(fork)),
|
|
431
|
+
);
|
|
432
|
+
if (failedTxs.length > 0) {
|
|
433
|
+
const failedTxData = failedTxs.map(fail => fail.tx);
|
|
434
|
+
this.log.debug(`Dropping failed txs ${Tx.getHashes(failedTxData).join(', ')}`);
|
|
435
|
+
await this.p2pClient.deleteTxs(Tx.getHashes(failedTxData));
|
|
436
|
+
}
|
|
449
437
|
|
|
450
|
-
|
|
451
|
-
await blockBuilder.setBlockCompleted();
|
|
438
|
+
await this.publisher.validateBlockForSubmission(proposalHeader);
|
|
452
439
|
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
440
|
+
if (
|
|
441
|
+
!this.shouldProposeBlock(historicalHeader, {
|
|
442
|
+
validTxsCount: validTxs.length,
|
|
443
|
+
processedTxsCount: processedTxs.length,
|
|
444
|
+
})
|
|
445
|
+
) {
|
|
446
|
+
// TODO: Roll back changes to world state
|
|
447
|
+
throw new Error('Should not propose the block');
|
|
448
|
+
}
|
|
460
449
|
|
|
461
|
-
|
|
462
|
-
|
|
450
|
+
// All real transactions have been added, set the block as full and complete the proving.
|
|
451
|
+
const block = await blockBuilder.setBlockCompleted();
|
|
452
|
+
|
|
453
|
+
// TODO(@PhilWindle) We should probably periodically check for things like another
|
|
454
|
+
// block being published before ours instead of just waiting on our block
|
|
455
|
+
|
|
456
|
+
await this.publisher.validateBlockForSubmission(block.header);
|
|
457
|
+
|
|
458
|
+
const workDuration = workTimer.ms();
|
|
459
|
+
this.log.verbose(
|
|
460
|
+
`Assembled block ${block.number} (txEffectsHash: ${block.header.contentCommitment.txsEffectsHash.toString(
|
|
461
|
+
'hex',
|
|
462
|
+
)})`,
|
|
463
|
+
{
|
|
464
|
+
eventName: 'l2-block-built',
|
|
465
|
+
creator: this.publisher.getSenderAddress().toString(),
|
|
466
|
+
duration: workDuration,
|
|
467
|
+
publicProcessDuration: publicProcessorDuration,
|
|
468
|
+
rollupCircuitsDuration: blockBuildingTimer.ms(),
|
|
469
|
+
...block.getStats(),
|
|
470
|
+
} satisfies L2BlockBuiltStats,
|
|
471
|
+
);
|
|
463
472
|
|
|
464
|
-
|
|
473
|
+
if (this.isFlushing) {
|
|
474
|
+
this.log.verbose(`Flushing completed`);
|
|
475
|
+
}
|
|
465
476
|
|
|
466
|
-
|
|
467
|
-
this.log.verbose(
|
|
468
|
-
`Assembled block ${block.number} (txEffectsHash: ${block.header.contentCommitment.txsEffectsHash.toString(
|
|
469
|
-
'hex',
|
|
470
|
-
)})`,
|
|
471
|
-
{
|
|
472
|
-
eventName: 'l2-block-built',
|
|
473
|
-
duration: workDuration,
|
|
474
|
-
publicProcessDuration: publicProcessorDuration,
|
|
475
|
-
rollupCircuitsDuration: blockBuildingTimer.ms(),
|
|
476
|
-
...block.getStats(),
|
|
477
|
-
} satisfies L2BlockBuiltStats,
|
|
478
|
-
);
|
|
477
|
+
const txHashes = validTxs.map(tx => tx.getTxHash());
|
|
479
478
|
|
|
480
|
-
|
|
481
|
-
this.log.verbose(
|
|
482
|
-
|
|
479
|
+
this.isFlushing = false;
|
|
480
|
+
this.log.verbose('Collecting attestations');
|
|
481
|
+
const attestations = await this.collectAttestations(block, txHashes);
|
|
482
|
+
this.log.verbose('Attestations collected');
|
|
483
483
|
|
|
484
|
-
|
|
484
|
+
this.log.verbose('Collecting proof quotes');
|
|
485
|
+
const proofQuote = await this.createProofClaimForPreviousEpoch(newGlobalVariables.slotNumber.toBigInt());
|
|
486
|
+
this.log.verbose(proofQuote ? `Using proof quote ${inspect(proofQuote.payload)}` : 'No proof quote available');
|
|
485
487
|
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
this.metrics.recordFailedBlock();
|
|
501
|
-
throw err;
|
|
488
|
+
try {
|
|
489
|
+
await this.publishL2Block(block, attestations, txHashes, proofQuote);
|
|
490
|
+
this.metrics.recordPublishedBlock(workDuration);
|
|
491
|
+
this.log.info(
|
|
492
|
+
`Submitted rollup block ${block.number} with ${processedTxs.length} transactions duration=${Math.ceil(
|
|
493
|
+
workDuration,
|
|
494
|
+
)}ms (Submitter: ${this.publisher.getSenderAddress()})`,
|
|
495
|
+
);
|
|
496
|
+
} catch (err) {
|
|
497
|
+
this.metrics.recordFailedBlock();
|
|
498
|
+
throw err;
|
|
499
|
+
}
|
|
500
|
+
} finally {
|
|
501
|
+
await fork.close();
|
|
502
502
|
}
|
|
503
503
|
}
|
|
504
504
|
|
|
@@ -507,6 +507,11 @@ export class Sequencer {
|
|
|
507
507
|
this.isFlushing = true;
|
|
508
508
|
}
|
|
509
509
|
|
|
510
|
+
@trackSpan('Sequencer.collectAttestations', (block, txHashes) => ({
|
|
511
|
+
[Attributes.BLOCK_NUMBER]: block.number,
|
|
512
|
+
[Attributes.BLOCK_ARCHIVE]: block.archive.toString(),
|
|
513
|
+
[Attributes.BLOCK_TXS_COUNT]: txHashes.length,
|
|
514
|
+
}))
|
|
510
515
|
protected async collectAttestations(block: L2Block, txHashes: TxHash[]): Promise<Signature[] | undefined> {
|
|
511
516
|
// TODO(https://github.com/AztecProtocol/aztec-packages/issues/7962): inefficient to have a round trip in here - this should be cached
|
|
512
517
|
const committee = await this.publisher.getCurrentEpochCommittee();
|
|
@@ -540,6 +545,50 @@ export class Sequencer {
|
|
|
540
545
|
return orderAttestations(attestations, committee);
|
|
541
546
|
}
|
|
542
547
|
|
|
548
|
+
protected async createProofClaimForPreviousEpoch(slotNumber: bigint): Promise<EpochProofQuote | undefined> {
|
|
549
|
+
try {
|
|
550
|
+
// Find out which epoch we are currently in
|
|
551
|
+
const epochForBlock = await this.publisher.getEpochForSlotNumber(slotNumber);
|
|
552
|
+
if (epochForBlock < 1n) {
|
|
553
|
+
// It's the 0th epoch, nothing to be proven yet
|
|
554
|
+
this.log.verbose(`First epoch has no claim`);
|
|
555
|
+
return undefined;
|
|
556
|
+
}
|
|
557
|
+
const epochToProve = epochForBlock - 1n;
|
|
558
|
+
// Find out the next epoch that can be claimed
|
|
559
|
+
const canClaim = await this.publisher.nextEpochToClaim();
|
|
560
|
+
if (canClaim != epochToProve) {
|
|
561
|
+
// It's not the one we are looking to claim
|
|
562
|
+
this.log.verbose(`Unable to claim previous epoch (${canClaim} != ${epochToProve})`);
|
|
563
|
+
return undefined;
|
|
564
|
+
}
|
|
565
|
+
// Get quotes for the epoch to be proven
|
|
566
|
+
const quotes = await this.p2pClient.getEpochProofQuotes(epochToProve);
|
|
567
|
+
this.log.verbose(`Retrieved ${quotes.length} quotes, slot: ${slotNumber}, epoch to prove: ${epochToProve}`);
|
|
568
|
+
for (const quote of quotes) {
|
|
569
|
+
this.log.verbose(inspect(quote.payload));
|
|
570
|
+
}
|
|
571
|
+
// ensure these quotes are still valid for the slot and have the contract validate them
|
|
572
|
+
const validQuotesPromise = Promise.all(
|
|
573
|
+
quotes.filter(x => x.payload.validUntilSlot >= slotNumber).map(x => this.publisher.validateProofQuote(x)),
|
|
574
|
+
);
|
|
575
|
+
|
|
576
|
+
const validQuotes = (await validQuotesPromise).filter((q): q is EpochProofQuote => !!q);
|
|
577
|
+
if (!validQuotes.length) {
|
|
578
|
+
this.log.verbose(`Failed to find any valid proof quotes`);
|
|
579
|
+
return undefined;
|
|
580
|
+
}
|
|
581
|
+
// pick the quote with the lowest fee
|
|
582
|
+
const sortedQuotes = validQuotes.sort(
|
|
583
|
+
(a: EpochProofQuote, b: EpochProofQuote) => a.payload.basisPointFee - b.payload.basisPointFee,
|
|
584
|
+
);
|
|
585
|
+
return sortedQuotes[0];
|
|
586
|
+
} catch (err) {
|
|
587
|
+
this.log.error(`Failed to create proof claim for previous epoch: ${err}`);
|
|
588
|
+
return undefined;
|
|
589
|
+
}
|
|
590
|
+
}
|
|
591
|
+
|
|
543
592
|
/**
|
|
544
593
|
* Publishes the L2Block to the rollup contract.
|
|
545
594
|
* @param block - The L2Block to be published.
|
|
@@ -547,14 +596,17 @@ export class Sequencer {
|
|
|
547
596
|
@trackSpan('Sequencer.publishL2Block', block => ({
|
|
548
597
|
[Attributes.BLOCK_NUMBER]: block.number,
|
|
549
598
|
}))
|
|
550
|
-
protected async publishL2Block(
|
|
599
|
+
protected async publishL2Block(
|
|
600
|
+
block: L2Block,
|
|
601
|
+
attestations?: Signature[],
|
|
602
|
+
txHashes?: TxHash[],
|
|
603
|
+
proofQuote?: EpochProofQuote,
|
|
604
|
+
) {
|
|
551
605
|
// Publishes new block to the network and awaits the tx to be mined
|
|
552
606
|
this.state = SequencerState.PUBLISHING_BLOCK;
|
|
553
607
|
|
|
554
|
-
const publishedL2Block = await this.publisher.proposeL2Block(block, attestations, txHashes);
|
|
555
|
-
if (publishedL2Block) {
|
|
556
|
-
this.lastPublishedBlock = block.number;
|
|
557
|
-
} else {
|
|
608
|
+
const publishedL2Block = await this.publisher.proposeL2Block(block, attestations, txHashes, proofQuote);
|
|
609
|
+
if (!publishedL2Block) {
|
|
558
610
|
throw new Error(`Failed to publish block ${block.number}`);
|
|
559
611
|
}
|
|
560
612
|
}
|
|
@@ -590,24 +642,37 @@ export class Sequencer {
|
|
|
590
642
|
}
|
|
591
643
|
|
|
592
644
|
/**
|
|
593
|
-
* Returns whether
|
|
645
|
+
* Returns whether all dependencies have caught up.
|
|
646
|
+
* We don't check against the previous block submitted since it may have been reorg'd out.
|
|
594
647
|
* @returns Boolean indicating if our dependencies are synced to the latest block.
|
|
595
648
|
*/
|
|
596
649
|
protected async isBlockSynced() {
|
|
597
650
|
const syncedBlocks = await Promise.all([
|
|
598
|
-
this.worldState.status().then((s:
|
|
599
|
-
this.
|
|
600
|
-
this.
|
|
651
|
+
this.worldState.status().then((s: WorldStateSynchronizerStatus) => s.syncedToL2Block),
|
|
652
|
+
this.l2BlockSource.getL2Tips().then(t => t.latest),
|
|
653
|
+
this.p2pClient.getStatus().then(s => s.syncedToL2Block.number),
|
|
601
654
|
this.l1ToL2MessageSource.getBlockNumber(),
|
|
602
|
-
]);
|
|
603
|
-
const
|
|
604
|
-
const
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
655
|
+
] as const);
|
|
656
|
+
const [worldState, l2BlockSource, p2p, l1ToL2MessageSource] = syncedBlocks;
|
|
657
|
+
const result =
|
|
658
|
+
// check that world state has caught up with archiver
|
|
659
|
+
// note that the archiver reports undefined hash for the genesis block
|
|
660
|
+
// because it doesn't have access to world state to compute it (facepalm)
|
|
661
|
+
(l2BlockSource.hash === undefined || worldState.hash === l2BlockSource.hash) &&
|
|
662
|
+
// and p2p client and message source are at least at the same block
|
|
663
|
+
// this should change to hashes once p2p client handles reorgs
|
|
664
|
+
// and once we stop pretending that the l1tol2message source is not
|
|
665
|
+
// just the archiver under a different name
|
|
666
|
+
p2p >= l2BlockSource.number &&
|
|
667
|
+
l1ToL2MessageSource >= l2BlockSource.number;
|
|
668
|
+
|
|
669
|
+
this.log.verbose(`Sequencer sync check ${result ? 'succeeded' : 'failed'}`, {
|
|
670
|
+
worldStateNumber: worldState.number,
|
|
671
|
+
worldStateHash: worldState.hash,
|
|
672
|
+
l2BlockSourceNumber: l2BlockSource.number,
|
|
673
|
+
l2BlockSourceHash: l2BlockSource.hash,
|
|
674
|
+
p2pNumber: p2p,
|
|
675
|
+
l1ToL2MessageSourceNumber: l1ToL2MessageSource,
|
|
611
676
|
});
|
|
612
677
|
return result;
|
|
613
678
|
}
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { PublicKernelPhase, type Tx, type TxValidator } from '@aztec/circuit-types';
|
|
2
|
-
import { type AztecAddress, type Fr } from '@aztec/circuits.js';
|
|
2
|
+
import { type AztecAddress, type Fr, FunctionSelector } from '@aztec/circuits.js';
|
|
3
3
|
import { createDebugLogger } from '@aztec/foundation/log';
|
|
4
|
-
import { FeeJuiceArtifact } from '@aztec/protocol-contracts/fee-juice';
|
|
5
4
|
import { EnqueuedCallsProcessor, computeFeePayerBalanceStorageSlot } from '@aztec/simulator';
|
|
6
5
|
|
|
7
6
|
/** Provides a view into public contract state */
|
|
@@ -64,15 +63,15 @@ export class GasTxValidator implements TxValidator<Tx> {
|
|
|
64
63
|
fn =>
|
|
65
64
|
fn.contractAddress.equals(this.#feeJuiceAddress) &&
|
|
66
65
|
fn.callContext.msgSender.equals(this.#feeJuiceAddress) &&
|
|
67
|
-
fn.
|
|
68
|
-
|
|
69
|
-
) &&
|
|
70
|
-
fn.args[
|
|
66
|
+
fn.args.length > 2 &&
|
|
67
|
+
// Public functions get routed through the dispatch function, whose first argument is the target function selector.
|
|
68
|
+
fn.args[0].equals(FunctionSelector.fromSignature('_increase_public_balance((Field),Field)').toField()) &&
|
|
69
|
+
fn.args[1].equals(feePayer) &&
|
|
71
70
|
!fn.callContext.isStaticCall &&
|
|
72
71
|
!fn.callContext.isDelegateCall,
|
|
73
72
|
);
|
|
74
73
|
|
|
75
|
-
const balance = claimFunctionCall ? initialBalance.add(claimFunctionCall.args[
|
|
74
|
+
const balance = claimFunctionCall ? initialBalance.add(claimFunctionCall.args[2]) : initialBalance;
|
|
76
75
|
if (balance.lt(feeLimit)) {
|
|
77
76
|
this.#log.info(`Rejecting transaction due to not enough fee payer balance`, { feePayer, balance, feeLimit });
|
|
78
77
|
return false;
|
|
@@ -5,9 +5,9 @@ import {
|
|
|
5
5
|
Tx,
|
|
6
6
|
type TxValidator,
|
|
7
7
|
} from '@aztec/circuit-types';
|
|
8
|
+
import { type ContractDataSource } from '@aztec/circuits.js';
|
|
8
9
|
import { createDebugLogger } from '@aztec/foundation/log';
|
|
9
10
|
import { ContractsDataSourcePublicDB, EnqueuedCallsProcessor } from '@aztec/simulator';
|
|
10
|
-
import { type ContractDataSource } from '@aztec/types/contracts';
|
|
11
11
|
|
|
12
12
|
export class PhasesTxValidator implements TxValidator<Tx> {
|
|
13
13
|
#log = createDebugLogger('aztec:sequencer:tx_validator:tx_phases');
|
|
@@ -1,33 +1,57 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
1
|
+
import {
|
|
2
|
+
type AllowedElement,
|
|
3
|
+
MerkleTreeId,
|
|
4
|
+
type MerkleTreeReadOperations,
|
|
5
|
+
type ProcessedTx,
|
|
6
|
+
type Tx,
|
|
7
|
+
type TxValidator,
|
|
8
|
+
} from '@aztec/circuit-types';
|
|
9
|
+
import { type ContractDataSource, type GlobalVariables } from '@aztec/circuits.js';
|
|
10
|
+
import {
|
|
11
|
+
AggregateTxValidator,
|
|
12
|
+
DataTxValidator,
|
|
13
|
+
DoubleSpendTxValidator,
|
|
14
|
+
MetadataTxValidator,
|
|
15
|
+
type NullifierSource,
|
|
16
|
+
} from '@aztec/p2p';
|
|
17
|
+
import { ProtocolContractAddress } from '@aztec/protocol-contracts';
|
|
18
|
+
import { readPublicState } from '@aztec/simulator';
|
|
8
19
|
|
|
9
|
-
import { GasTxValidator } from './gas_validator.js';
|
|
20
|
+
import { GasTxValidator, type PublicStateSource } from './gas_validator.js';
|
|
10
21
|
import { PhasesTxValidator } from './phases_validator.js';
|
|
11
22
|
|
|
12
23
|
export class TxValidatorFactory {
|
|
24
|
+
nullifierSource: NullifierSource;
|
|
25
|
+
publicStateSource: PublicStateSource;
|
|
13
26
|
constructor(
|
|
14
|
-
private
|
|
27
|
+
private committedDb: MerkleTreeReadOperations,
|
|
15
28
|
private contractDataSource: ContractDataSource,
|
|
16
29
|
private enforceFees: boolean,
|
|
17
|
-
) {
|
|
30
|
+
) {
|
|
31
|
+
this.nullifierSource = {
|
|
32
|
+
getNullifierIndex: nullifier => this.committedDb.findLeafIndex(MerkleTreeId.NULLIFIER_TREE, nullifier.toBuffer()),
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
this.publicStateSource = {
|
|
36
|
+
storageRead: (contractAddress, slot) => {
|
|
37
|
+
return readPublicState(this.committedDb, contractAddress, slot);
|
|
38
|
+
},
|
|
39
|
+
};
|
|
40
|
+
}
|
|
18
41
|
|
|
19
42
|
validatorForNewTxs(globalVariables: GlobalVariables, setupAllowList: AllowedElement[]): TxValidator<Tx> {
|
|
20
|
-
const worldStateDB = new WorldStateDB(this.merkleTreeDb, this.contractDataSource);
|
|
21
43
|
return new AggregateTxValidator(
|
|
22
44
|
new DataTxValidator(),
|
|
23
45
|
new MetadataTxValidator(globalVariables.chainId, globalVariables.blockNumber),
|
|
24
|
-
new DoubleSpendTxValidator(
|
|
46
|
+
new DoubleSpendTxValidator(this.nullifierSource),
|
|
25
47
|
new PhasesTxValidator(this.contractDataSource, setupAllowList),
|
|
26
|
-
new GasTxValidator(
|
|
48
|
+
new GasTxValidator(this.publicStateSource, ProtocolContractAddress.FeeJuice, this.enforceFees),
|
|
27
49
|
);
|
|
28
50
|
}
|
|
29
51
|
|
|
30
|
-
validatorForProcessedTxs(): TxValidator<ProcessedTx> {
|
|
31
|
-
return new DoubleSpendTxValidator(
|
|
52
|
+
validatorForProcessedTxs(fork: MerkleTreeReadOperations): TxValidator<ProcessedTx> {
|
|
53
|
+
return new DoubleSpendTxValidator({
|
|
54
|
+
getNullifierIndex: nullifier => fork.findLeafIndex(MerkleTreeId.NULLIFIER_TREE, nullifier.toBuffer()),
|
|
55
|
+
});
|
|
32
56
|
}
|
|
33
57
|
}
|