@aztec/aztec-node 0.0.1-commit.6d63667d → 0.0.1-commit.7035c9bd6
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/aztec-node/config.d.ts +8 -5
- package/dest/aztec-node/config.d.ts.map +1 -1
- package/dest/aztec-node/config.js +11 -3
- package/dest/aztec-node/server.d.ts +32 -22
- package/dest/aztec-node/server.d.ts.map +1 -1
- package/dest/aztec-node/server.js +350 -163
- package/dest/sentinel/factory.d.ts +2 -2
- package/dest/sentinel/factory.d.ts.map +1 -1
- package/dest/sentinel/sentinel.d.ts +2 -2
- package/dest/sentinel/sentinel.d.ts.map +1 -1
- package/dest/sentinel/sentinel.js +60 -34
- package/dest/sentinel/store.d.ts +2 -2
- package/dest/sentinel/store.d.ts.map +1 -1
- package/dest/sentinel/store.js +11 -7
- package/package.json +26 -25
- package/src/aztec-node/config.ts +25 -9
- package/src/aztec-node/server.ts +422 -174
- package/src/sentinel/factory.ts +1 -1
- package/src/sentinel/sentinel.ts +63 -30
- package/src/sentinel/store.ts +12 -12
|
@@ -374,12 +374,13 @@ var _dec, _initProto;
|
|
|
374
374
|
import { createArchiver } from '@aztec/archiver';
|
|
375
375
|
import { BBCircuitVerifier, QueuedIVCVerifier, TestCircuitVerifier } from '@aztec/bb-prover';
|
|
376
376
|
import { createBlobClientWithFileStores } from '@aztec/blob-client/client';
|
|
377
|
+
import { Blob } from '@aztec/blob-lib';
|
|
377
378
|
import { EpochCache } from '@aztec/epoch-cache';
|
|
378
379
|
import { createEthereumChain } from '@aztec/ethereum/chain';
|
|
379
|
-
import { getPublicClient } from '@aztec/ethereum/client';
|
|
380
|
+
import { getPublicClient, makeL1HttpTransport } from '@aztec/ethereum/client';
|
|
380
381
|
import { RegistryContract, RollupContract } from '@aztec/ethereum/contracts';
|
|
381
|
-
import { BlockNumber
|
|
382
|
-
import { compactArray, pick } from '@aztec/foundation/collection';
|
|
382
|
+
import { BlockNumber } from '@aztec/foundation/branded-types';
|
|
383
|
+
import { chunkBy, compactArray, pick, unique } from '@aztec/foundation/collection';
|
|
383
384
|
import { Fr } from '@aztec/foundation/curves/bn254';
|
|
384
385
|
import { EthAddress } from '@aztec/foundation/eth-address';
|
|
385
386
|
import { BadRequestError } from '@aztec/foundation/json-rpc';
|
|
@@ -389,9 +390,11 @@ import { DateProvider, Timer } from '@aztec/foundation/timer';
|
|
|
389
390
|
import { MembershipWitness } from '@aztec/foundation/trees';
|
|
390
391
|
import { KeystoreManager, loadKeystores, mergeKeystores } from '@aztec/node-keystore';
|
|
391
392
|
import { trySnapshotSync, uploadSnapshot } from '@aztec/node-lib/actions';
|
|
392
|
-
import {
|
|
393
|
-
import { createP2PClient, getDefaultAllowedSetupFunctions } from '@aztec/p2p';
|
|
393
|
+
import { createForwarderL1TxUtilsFromSigners, createL1TxUtilsFromSigners } from '@aztec/node-lib/factories';
|
|
394
|
+
import { createP2PClient, createTxValidatorForAcceptingTxsOverRPC, getDefaultAllowedSetupFunctions } from '@aztec/p2p';
|
|
394
395
|
import { ProtocolContractAddress } from '@aztec/protocol-contracts';
|
|
396
|
+
import { createProverNode } from '@aztec/prover-node';
|
|
397
|
+
import { createKeyStoreForProver } from '@aztec/prover-node/config';
|
|
395
398
|
import { GlobalVariableBuilder, SequencerClient } from '@aztec/sequencer-client';
|
|
396
399
|
import { PublicProcessorFactory } from '@aztec/simulator/server';
|
|
397
400
|
import { AttestationsBlockWatcher, EpochPruneWatcher, createSlasher } from '@aztec/slasher';
|
|
@@ -402,15 +405,15 @@ import { GasFees } from '@aztec/stdlib/gas';
|
|
|
402
405
|
import { computePublicDataTreeLeafSlot } from '@aztec/stdlib/hash';
|
|
403
406
|
import { AztecNodeAdminConfigSchema } from '@aztec/stdlib/interfaces/client';
|
|
404
407
|
import { tryStop } from '@aztec/stdlib/interfaces/server';
|
|
408
|
+
import { InMemoryDebugLogStore, NullDebugLogStore } from '@aztec/stdlib/logs';
|
|
405
409
|
import { InboxLeaf } from '@aztec/stdlib/messaging';
|
|
406
|
-
import { P2PClientType } from '@aztec/stdlib/p2p';
|
|
407
410
|
import { MerkleTreeId, NullifierMembershipWitness, PublicDataWitness } from '@aztec/stdlib/trees';
|
|
408
411
|
import { PublicSimulationOutput, TxReceipt, TxStatus } from '@aztec/stdlib/tx';
|
|
409
412
|
import { getPackageVersion } from '@aztec/stdlib/update-checker';
|
|
410
413
|
import { Attributes, getTelemetryClient, trackSpan } from '@aztec/telemetry-client';
|
|
411
|
-
import { FullNodeCheckpointsBuilder as CheckpointsBuilder, FullNodeCheckpointsBuilder, NodeKeystoreAdapter, ValidatorClient, createBlockProposalHandler, createValidatorClient
|
|
414
|
+
import { FullNodeCheckpointsBuilder as CheckpointsBuilder, FullNodeCheckpointsBuilder, NodeKeystoreAdapter, ValidatorClient, createBlockProposalHandler, createValidatorClient } from '@aztec/validator-client';
|
|
412
415
|
import { createWorldStateSynchronizer } from '@aztec/world-state';
|
|
413
|
-
import { createPublicClient
|
|
416
|
+
import { createPublicClient } from 'viem';
|
|
414
417
|
import { createSentinel } from '../sentinel/factory.js';
|
|
415
418
|
import { createKeyStoreForValidator } from './config.js';
|
|
416
419
|
import { NodeMetrics } from './node_metrics.js';
|
|
@@ -428,6 +431,7 @@ _dec = trackSpan('AztecNodeService.simulatePublicCalls', (tx)=>({
|
|
|
428
431
|
l1ToL2MessageSource;
|
|
429
432
|
worldStateSynchronizer;
|
|
430
433
|
sequencer;
|
|
434
|
+
proverNode;
|
|
431
435
|
slasherClient;
|
|
432
436
|
validatorsSentinel;
|
|
433
437
|
epochPruneWatcher;
|
|
@@ -440,6 +444,9 @@ _dec = trackSpan('AztecNodeService.simulatePublicCalls', (tx)=>({
|
|
|
440
444
|
telemetry;
|
|
441
445
|
log;
|
|
442
446
|
blobClient;
|
|
447
|
+
validatorClient;
|
|
448
|
+
keyStoreManager;
|
|
449
|
+
debugLogStore;
|
|
443
450
|
static{
|
|
444
451
|
({ e: [_initProto] } = _apply_decs_2203_r(this, [
|
|
445
452
|
[
|
|
@@ -454,7 +461,7 @@ _dec = trackSpan('AztecNodeService.simulatePublicCalls', (tx)=>({
|
|
|
454
461
|
// Prevent two snapshot operations to happen simultaneously
|
|
455
462
|
isUploadingSnapshot;
|
|
456
463
|
tracer;
|
|
457
|
-
constructor(config, p2pClient, blockSource, logsSource, contractDataSource, l1ToL2MessageSource, worldStateSynchronizer, sequencer, slasherClient, validatorsSentinel, epochPruneWatcher, l1ChainId, version, globalVariableBuilder, epochCache, packageVersion, proofVerifier, telemetry = getTelemetryClient(), log = createLogger('node'), blobClient){
|
|
464
|
+
constructor(config, p2pClient, blockSource, logsSource, contractDataSource, l1ToL2MessageSource, worldStateSynchronizer, sequencer, proverNode, slasherClient, validatorsSentinel, epochPruneWatcher, l1ChainId, version, globalVariableBuilder, epochCache, packageVersion, proofVerifier, telemetry = getTelemetryClient(), log = createLogger('node'), blobClient, validatorClient, keyStoreManager, debugLogStore = new NullDebugLogStore()){
|
|
458
465
|
this.config = config;
|
|
459
466
|
this.p2pClient = p2pClient;
|
|
460
467
|
this.blockSource = blockSource;
|
|
@@ -463,6 +470,7 @@ _dec = trackSpan('AztecNodeService.simulatePublicCalls', (tx)=>({
|
|
|
463
470
|
this.l1ToL2MessageSource = l1ToL2MessageSource;
|
|
464
471
|
this.worldStateSynchronizer = worldStateSynchronizer;
|
|
465
472
|
this.sequencer = sequencer;
|
|
473
|
+
this.proverNode = proverNode;
|
|
466
474
|
this.slasherClient = slasherClient;
|
|
467
475
|
this.validatorsSentinel = validatorsSentinel;
|
|
468
476
|
this.epochPruneWatcher = epochPruneWatcher;
|
|
@@ -475,12 +483,21 @@ _dec = trackSpan('AztecNodeService.simulatePublicCalls', (tx)=>({
|
|
|
475
483
|
this.telemetry = telemetry;
|
|
476
484
|
this.log = log;
|
|
477
485
|
this.blobClient = blobClient;
|
|
486
|
+
this.validatorClient = validatorClient;
|
|
487
|
+
this.keyStoreManager = keyStoreManager;
|
|
488
|
+
this.debugLogStore = debugLogStore;
|
|
478
489
|
this.initialHeaderHashPromise = (_initProto(this), undefined);
|
|
479
490
|
this.isUploadingSnapshot = false;
|
|
480
491
|
this.metrics = new NodeMetrics(telemetry, 'AztecNodeService');
|
|
481
492
|
this.tracer = telemetry.getTracer('AztecNodeService');
|
|
482
493
|
this.log.info(`Aztec Node version: ${this.packageVersion}`);
|
|
483
494
|
this.log.info(`Aztec Node started on chain 0x${l1ChainId.toString(16)}`, config.l1Contracts);
|
|
495
|
+
// A defensive check that protects us against introducing a bug in the complex `createAndSync` function. We must
|
|
496
|
+
// never have debugLogStore enabled when not in test mode because then we would be accumulating debug logs in
|
|
497
|
+
// memory which could be a DoS vector on the sequencer (since no fees are paid for debug logs).
|
|
498
|
+
if (debugLogStore.isEnabled && config.realProofs) {
|
|
499
|
+
throw new Error('debugLogStore should never be enabled when realProofs are set');
|
|
500
|
+
}
|
|
484
501
|
}
|
|
485
502
|
async getWorldStateSyncStatus() {
|
|
486
503
|
const status = await this.worldStateSynchronizer.status();
|
|
@@ -502,16 +519,27 @@ _dec = trackSpan('AztecNodeService.simulatePublicCalls', (tx)=>({
|
|
|
502
519
|
const telemetry = deps.telemetry ?? getTelemetryClient();
|
|
503
520
|
const dateProvider = deps.dateProvider ?? new DateProvider();
|
|
504
521
|
const ethereumChain = createEthereumChain(config.l1RpcUrls, config.l1ChainId);
|
|
505
|
-
// Build a key store from file if given or from environment otherwise
|
|
522
|
+
// Build a key store from file if given or from environment otherwise.
|
|
523
|
+
// We keep the raw KeyStore available so we can merge with prover keys if enableProverNode is set.
|
|
506
524
|
let keyStoreManager;
|
|
507
525
|
const keyStoreProvided = config.keyStoreDirectory !== undefined && config.keyStoreDirectory.length > 0;
|
|
508
526
|
if (keyStoreProvided) {
|
|
509
527
|
const keyStores = loadKeystores(config.keyStoreDirectory);
|
|
510
528
|
keyStoreManager = new KeystoreManager(mergeKeystores(keyStores));
|
|
511
529
|
} else {
|
|
512
|
-
const
|
|
513
|
-
|
|
514
|
-
|
|
530
|
+
const rawKeyStores = [];
|
|
531
|
+
const validatorKeyStore = createKeyStoreForValidator(config);
|
|
532
|
+
if (validatorKeyStore) {
|
|
533
|
+
rawKeyStores.push(validatorKeyStore);
|
|
534
|
+
}
|
|
535
|
+
if (config.enableProverNode) {
|
|
536
|
+
const proverKeyStore = createKeyStoreForProver(config);
|
|
537
|
+
if (proverKeyStore) {
|
|
538
|
+
rawKeyStores.push(proverKeyStore);
|
|
539
|
+
}
|
|
540
|
+
}
|
|
541
|
+
if (rawKeyStores.length > 0) {
|
|
542
|
+
keyStoreManager = new KeystoreManager(rawKeyStores.length === 1 ? rawKeyStores[0] : mergeKeystores(rawKeyStores));
|
|
515
543
|
}
|
|
516
544
|
}
|
|
517
545
|
await keyStoreManager?.validateSigners();
|
|
@@ -520,8 +548,8 @@ _dec = trackSpan('AztecNodeService.simulatePublicCalls', (tx)=>({
|
|
|
520
548
|
if (keyStoreManager === undefined) {
|
|
521
549
|
throw new Error('Failed to create key store, a requirement for running a validator');
|
|
522
550
|
}
|
|
523
|
-
if (!keyStoreProvided) {
|
|
524
|
-
log.warn(
|
|
551
|
+
if (!keyStoreProvided && process.env.NODE_ENV !== 'test') {
|
|
552
|
+
log.warn("Keystore created from env: it's recommended to use a file-based key store for production");
|
|
525
553
|
}
|
|
526
554
|
ValidatorClient.validateKeyStoreConfiguration(keyStoreManager, log);
|
|
527
555
|
}
|
|
@@ -531,9 +559,9 @@ _dec = trackSpan('AztecNodeService.simulatePublicCalls', (tx)=>({
|
|
|
531
559
|
}
|
|
532
560
|
const publicClient = createPublicClient({
|
|
533
561
|
chain: ethereumChain.chainInfo,
|
|
534
|
-
transport:
|
|
535
|
-
|
|
536
|
-
|
|
562
|
+
transport: makeL1HttpTransport(config.l1RpcUrls, {
|
|
563
|
+
timeout: config.l1HttpTimeoutMS
|
|
564
|
+
}),
|
|
537
565
|
pollingInterval: config.viemPollingIntervalMS
|
|
538
566
|
});
|
|
539
567
|
const l1ContractsAddresses = await RegistryContract.collectAddresses(publicClient, config.l1Contracts.registryAddress, config.rollupVersion ?? 'canonical');
|
|
@@ -543,16 +571,17 @@ _dec = trackSpan('AztecNodeService.simulatePublicCalls', (tx)=>({
|
|
|
543
571
|
...l1ContractsAddresses
|
|
544
572
|
};
|
|
545
573
|
const rollupContract = new RollupContract(publicClient, config.l1Contracts.rollupAddress.toString());
|
|
546
|
-
const [l1GenesisTime, slotDuration, rollupVersionFromRollup] = await Promise.all([
|
|
574
|
+
const [l1GenesisTime, slotDuration, rollupVersionFromRollup, rollupManaLimit] = await Promise.all([
|
|
547
575
|
rollupContract.getL1GenesisTime(),
|
|
548
576
|
rollupContract.getSlotDuration(),
|
|
549
|
-
rollupContract.getVersion()
|
|
577
|
+
rollupContract.getVersion(),
|
|
578
|
+
rollupContract.getManaLimit().then(Number)
|
|
550
579
|
]);
|
|
551
580
|
config.rollupVersion ??= Number(rollupVersionFromRollup);
|
|
552
581
|
if (config.rollupVersion !== Number(rollupVersionFromRollup)) {
|
|
553
582
|
log.warn(`Registry looked up and returned a rollup with version (${config.rollupVersion}), but this does not match with version detected from the rollup directly: (${rollupVersionFromRollup}).`);
|
|
554
583
|
}
|
|
555
|
-
const blobClient = await createBlobClientWithFileStores(config,
|
|
584
|
+
const blobClient = await createBlobClientWithFileStores(config, log.createChild('blob-client'));
|
|
556
585
|
// attempt snapshot sync if possible
|
|
557
586
|
await trySnapshotSync(config, log);
|
|
558
587
|
const epochCache = await EpochCache.create(config.l1Contracts.rollupAddress, config, {
|
|
@@ -569,48 +598,64 @@ _dec = trackSpan('AztecNodeService.simulatePublicCalls', (tx)=>({
|
|
|
569
598
|
// now create the merkle trees and the world state synchronizer
|
|
570
599
|
const worldStateSynchronizer = await createWorldStateSynchronizer(config, archiver, options.prefilledPublicData, telemetry);
|
|
571
600
|
const circuitVerifier = config.realProofs || config.debugForceTxProofVerification ? await BBCircuitVerifier.new(config) : new TestCircuitVerifier(config.proverTestVerificationDelayMs);
|
|
601
|
+
let debugLogStore;
|
|
572
602
|
if (!config.realProofs) {
|
|
573
603
|
log.warn(`Aztec node is accepting fake proofs`);
|
|
604
|
+
debugLogStore = new InMemoryDebugLogStore();
|
|
605
|
+
log.info('Aztec node started in test mode (realProofs set to false) hence debug logs from public functions will be collected and served');
|
|
606
|
+
} else {
|
|
607
|
+
debugLogStore = new NullDebugLogStore();
|
|
574
608
|
}
|
|
575
609
|
const proofVerifier = new QueuedIVCVerifier(config, circuitVerifier);
|
|
610
|
+
const proverOnly = config.enableProverNode && config.disableValidator;
|
|
611
|
+
if (proverOnly) {
|
|
612
|
+
log.info('Starting in prover-only mode: skipping validator, sequencer, sentinel, and slasher subsystems');
|
|
613
|
+
}
|
|
576
614
|
// create the tx pool and the p2p client, which will need the l2 block source
|
|
577
|
-
const p2pClient = await createP2PClient(
|
|
578
|
-
// We
|
|
579
|
-
|
|
580
|
-
// Create FullNodeCheckpointsBuilder for
|
|
615
|
+
const p2pClient = await createP2PClient(config, archiver, proofVerifier, worldStateSynchronizer, epochCache, packageVersion, dateProvider, telemetry, deps.p2pClientDeps);
|
|
616
|
+
// We'll accumulate sentinel watchers here
|
|
617
|
+
const watchers = [];
|
|
618
|
+
// Create FullNodeCheckpointsBuilder for block proposal handling and tx validation.
|
|
619
|
+
// Override maxTxsPerCheckpoint with the validator-specific limit if set.
|
|
581
620
|
const validatorCheckpointsBuilder = new FullNodeCheckpointsBuilder({
|
|
582
621
|
...config,
|
|
583
622
|
l1GenesisTime,
|
|
584
|
-
slotDuration: Number(slotDuration)
|
|
623
|
+
slotDuration: Number(slotDuration),
|
|
624
|
+
rollupManaLimit,
|
|
625
|
+
maxTxsPerCheckpoint: config.validateMaxTxsPerCheckpoint
|
|
585
626
|
}, worldStateSynchronizer, archiver, dateProvider, telemetry);
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
627
|
+
let validatorClient;
|
|
628
|
+
if (!proverOnly) {
|
|
629
|
+
// Create validator client if required
|
|
630
|
+
validatorClient = await createValidatorClient(config, {
|
|
631
|
+
checkpointsBuilder: validatorCheckpointsBuilder,
|
|
632
|
+
worldState: worldStateSynchronizer,
|
|
633
|
+
p2pClient,
|
|
634
|
+
telemetry,
|
|
635
|
+
dateProvider,
|
|
636
|
+
epochCache,
|
|
637
|
+
blockSource: archiver,
|
|
638
|
+
l1ToL2MessageSource: archiver,
|
|
639
|
+
keyStoreManager,
|
|
640
|
+
blobClient,
|
|
641
|
+
slashingProtectionDb: deps.slashingProtectionDb
|
|
642
|
+
});
|
|
643
|
+
// If we have a validator client, register it as a source of offenses for the slasher,
|
|
644
|
+
// and have it register callbacks on the p2p client *before* we start it, otherwise messages
|
|
645
|
+
// like attestations or auths will fail.
|
|
646
|
+
if (validatorClient) {
|
|
647
|
+
watchers.push(validatorClient);
|
|
648
|
+
if (!options.dontStartSequencer) {
|
|
649
|
+
await validatorClient.registerHandlers();
|
|
650
|
+
}
|
|
608
651
|
}
|
|
609
652
|
}
|
|
610
|
-
// If there's no validator client
|
|
611
|
-
//
|
|
612
|
-
|
|
613
|
-
|
|
653
|
+
// If there's no validator client, create a BlockProposalHandler to handle block proposals
|
|
654
|
+
// for monitoring or reexecution. Reexecution (default) allows us to follow the pending chain,
|
|
655
|
+
// while non-reexecution is used for validating the proposals and collecting their txs.
|
|
656
|
+
if (!validatorClient) {
|
|
657
|
+
const reexecute = !!config.alwaysReexecuteBlockProposals;
|
|
658
|
+
log.info(`Setting up block proposal handler` + (reexecute ? ' with reexecution of proposals' : ''));
|
|
614
659
|
createBlockProposalHandler(config, {
|
|
615
660
|
checkpointsBuilder: validatorCheckpointsBuilder,
|
|
616
661
|
worldState: worldStateSynchronizer,
|
|
@@ -620,26 +665,29 @@ _dec = trackSpan('AztecNodeService.simulatePublicCalls', (tx)=>({
|
|
|
620
665
|
p2pClient,
|
|
621
666
|
dateProvider,
|
|
622
667
|
telemetry
|
|
623
|
-
}).
|
|
668
|
+
}).register(p2pClient, reexecute);
|
|
624
669
|
}
|
|
625
670
|
// Start world state and wait for it to sync to the archiver.
|
|
626
671
|
await worldStateSynchronizer.start();
|
|
627
672
|
// Start p2p. Note that it depends on world state to be running.
|
|
628
673
|
await p2pClient.start();
|
|
629
|
-
|
|
630
|
-
if (validatorsSentinel && config.slashInactivityPenalty > 0n) {
|
|
631
|
-
watchers.push(validatorsSentinel);
|
|
632
|
-
}
|
|
674
|
+
let validatorsSentinel;
|
|
633
675
|
let epochPruneWatcher;
|
|
634
|
-
if (config.slashPrunePenalty > 0n || config.slashDataWithholdingPenalty > 0n) {
|
|
635
|
-
epochPruneWatcher = new EpochPruneWatcher(archiver, archiver, epochCache, p2pClient.getTxProvider(), validatorCheckpointsBuilder, config);
|
|
636
|
-
watchers.push(epochPruneWatcher);
|
|
637
|
-
}
|
|
638
|
-
// We assume we want to slash for invalid attestations unless all max penalties are set to 0
|
|
639
676
|
let attestationsBlockWatcher;
|
|
640
|
-
if (
|
|
641
|
-
|
|
642
|
-
|
|
677
|
+
if (!proverOnly) {
|
|
678
|
+
validatorsSentinel = await createSentinel(epochCache, archiver, p2pClient, config);
|
|
679
|
+
if (validatorsSentinel && config.slashInactivityPenalty > 0n) {
|
|
680
|
+
watchers.push(validatorsSentinel);
|
|
681
|
+
}
|
|
682
|
+
if (config.slashPrunePenalty > 0n || config.slashDataWithholdingPenalty > 0n) {
|
|
683
|
+
epochPruneWatcher = new EpochPruneWatcher(archiver, archiver, epochCache, p2pClient.getTxProvider(), validatorCheckpointsBuilder, config);
|
|
684
|
+
watchers.push(epochPruneWatcher);
|
|
685
|
+
}
|
|
686
|
+
// We assume we want to slash for invalid attestations unless all max penalties are set to 0
|
|
687
|
+
if (config.slashProposeInvalidAttestationsPenalty > 0n || config.slashAttestDescendantOfInvalidPenalty > 0n) {
|
|
688
|
+
attestationsBlockWatcher = new AttestationsBlockWatcher(archiver, epochCache, config);
|
|
689
|
+
watchers.push(attestationsBlockWatcher);
|
|
690
|
+
}
|
|
643
691
|
}
|
|
644
692
|
// Start p2p-related services once the archiver has completed sync
|
|
645
693
|
void archiver.waitForInitialSync().then(async ()=>{
|
|
@@ -658,27 +706,30 @@ _dec = trackSpan('AztecNodeService.simulatePublicCalls', (tx)=>({
|
|
|
658
706
|
const validatorAddresses = keyStoreManager ? NodeKeystoreAdapter.fromKeyStoreManager(keyStoreManager).getAddresses() : [];
|
|
659
707
|
slasherClient = await createSlasher(config, config.l1Contracts, getPublicClient(config), watchers, dateProvider, epochCache, validatorAddresses, undefined);
|
|
660
708
|
await slasherClient.start();
|
|
661
|
-
const l1TxUtils = config.
|
|
709
|
+
const l1TxUtils = config.sequencerPublisherForwarderAddress ? await createForwarderL1TxUtilsFromSigners(publicClient, keyStoreManager.createAllValidatorPublisherSigners(), config.sequencerPublisherForwarderAddress, {
|
|
662
710
|
...config,
|
|
663
711
|
scope: 'sequencer'
|
|
664
712
|
}, {
|
|
665
713
|
telemetry,
|
|
666
714
|
logger: log.createChild('l1-tx-utils'),
|
|
667
|
-
dateProvider
|
|
668
|
-
|
|
715
|
+
dateProvider,
|
|
716
|
+
kzg: Blob.getViemKzgInstance()
|
|
717
|
+
}) : await createL1TxUtilsFromSigners(publicClient, keyStoreManager.createAllValidatorPublisherSigners(), {
|
|
669
718
|
...config,
|
|
670
719
|
scope: 'sequencer'
|
|
671
720
|
}, {
|
|
672
721
|
telemetry,
|
|
673
722
|
logger: log.createChild('l1-tx-utils'),
|
|
674
|
-
dateProvider
|
|
723
|
+
dateProvider,
|
|
724
|
+
kzg: Blob.getViemKzgInstance()
|
|
675
725
|
});
|
|
676
726
|
// Create and start the sequencer client
|
|
677
727
|
const checkpointsBuilder = new CheckpointsBuilder({
|
|
678
728
|
...config,
|
|
679
729
|
l1GenesisTime,
|
|
680
|
-
slotDuration: Number(slotDuration)
|
|
681
|
-
|
|
730
|
+
slotDuration: Number(slotDuration),
|
|
731
|
+
rollupManaLimit
|
|
732
|
+
}, worldStateSynchronizer, archiver, dateProvider, telemetry, debugLogStore);
|
|
682
733
|
sequencer = await SequencerClient.new(config, {
|
|
683
734
|
...deps,
|
|
684
735
|
epochCache,
|
|
@@ -702,13 +753,35 @@ _dec = trackSpan('AztecNodeService.simulatePublicCalls', (tx)=>({
|
|
|
702
753
|
} else if (sequencer) {
|
|
703
754
|
log.warn(`Sequencer created but not started`);
|
|
704
755
|
}
|
|
756
|
+
// Create prover node subsystem if enabled
|
|
757
|
+
let proverNode;
|
|
758
|
+
if (config.enableProverNode) {
|
|
759
|
+
proverNode = await createProverNode(config, {
|
|
760
|
+
...deps.proverNodeDeps,
|
|
761
|
+
telemetry,
|
|
762
|
+
dateProvider,
|
|
763
|
+
archiver,
|
|
764
|
+
worldStateSynchronizer,
|
|
765
|
+
p2pClient,
|
|
766
|
+
epochCache,
|
|
767
|
+
blobClient,
|
|
768
|
+
keyStoreManager
|
|
769
|
+
});
|
|
770
|
+
if (!options.dontStartProverNode) {
|
|
771
|
+
await proverNode.start();
|
|
772
|
+
log.info(`Prover node subsystem started`);
|
|
773
|
+
} else {
|
|
774
|
+
log.info(`Prover node subsystem created but not started`);
|
|
775
|
+
}
|
|
776
|
+
}
|
|
705
777
|
const globalVariableBuilder = new GlobalVariableBuilder({
|
|
706
778
|
...config,
|
|
707
779
|
rollupVersion: BigInt(config.rollupVersion),
|
|
708
780
|
l1GenesisTime,
|
|
709
781
|
slotDuration: Number(slotDuration)
|
|
710
782
|
});
|
|
711
|
-
|
|
783
|
+
const node = new AztecNodeService(config, p2pClient, archiver, archiver, archiver, archiver, worldStateSynchronizer, sequencer, proverNode, slasherClient, validatorsSentinel, epochPruneWatcher, ethereumChain.chainInfo.id, config.rollupVersion, globalVariableBuilder, epochCache, packageVersion, proofVerifier, telemetry, log, blobClient, validatorClient, keyStoreManager, debugLogStore);
|
|
784
|
+
return node;
|
|
712
785
|
}
|
|
713
786
|
/**
|
|
714
787
|
* Returns the sequencer client instance.
|
|
@@ -716,6 +789,9 @@ _dec = trackSpan('AztecNodeService.simulatePublicCalls', (tx)=>({
|
|
|
716
789
|
*/ getSequencer() {
|
|
717
790
|
return this.sequencer;
|
|
718
791
|
}
|
|
792
|
+
/** Returns the prover node subsystem, if enabled. */ getProverNode() {
|
|
793
|
+
return this.proverNode;
|
|
794
|
+
}
|
|
719
795
|
getBlockSource() {
|
|
720
796
|
return this.blockSource;
|
|
721
797
|
}
|
|
@@ -735,7 +811,10 @@ _dec = trackSpan('AztecNodeService.simulatePublicCalls', (tx)=>({
|
|
|
735
811
|
return Promise.resolve(this.p2pClient.getEnr()?.encodeTxt());
|
|
736
812
|
}
|
|
737
813
|
async getAllowedPublicSetup() {
|
|
738
|
-
return
|
|
814
|
+
return [
|
|
815
|
+
...await getDefaultAllowedSetupFunctions(),
|
|
816
|
+
...this.config.txPublicSetupAllowListExtend ?? []
|
|
817
|
+
];
|
|
739
818
|
}
|
|
740
819
|
/**
|
|
741
820
|
* Method to determine if the node is ready to accept transactions.
|
|
@@ -758,7 +837,8 @@ _dec = trackSpan('AztecNodeService.simulatePublicCalls', (tx)=>({
|
|
|
758
837
|
rollupVersion,
|
|
759
838
|
enr,
|
|
760
839
|
l1ContractAddresses: contractAddresses,
|
|
761
|
-
protocolContractAddresses: protocolContractAddresses
|
|
840
|
+
protocolContractAddresses: protocolContractAddresses,
|
|
841
|
+
realProofs: !!this.config.realProofs
|
|
762
842
|
};
|
|
763
843
|
return nodeInfo;
|
|
764
844
|
}
|
|
@@ -812,6 +892,9 @@ _dec = trackSpan('AztecNodeService.simulatePublicCalls', (tx)=>({
|
|
|
812
892
|
async getCheckpointedBlocks(from, limit) {
|
|
813
893
|
return await this.blockSource.getCheckpointedBlocks(from, limit) ?? [];
|
|
814
894
|
}
|
|
895
|
+
getCheckpointsDataForEpoch(epochNumber) {
|
|
896
|
+
return this.blockSource.getCheckpointsDataForEpoch(epochNumber);
|
|
897
|
+
}
|
|
815
898
|
/**
|
|
816
899
|
* Method to fetch the current min L2 fees.
|
|
817
900
|
* @returns The current min L2 fees.
|
|
@@ -839,6 +922,9 @@ _dec = trackSpan('AztecNodeService.simulatePublicCalls', (tx)=>({
|
|
|
839
922
|
async getCheckpointedBlockNumber() {
|
|
840
923
|
return await this.blockSource.getCheckpointedL2BlockNumber();
|
|
841
924
|
}
|
|
925
|
+
getCheckpointNumber() {
|
|
926
|
+
return this.blockSource.getCheckpointNumber();
|
|
927
|
+
}
|
|
842
928
|
/**
|
|
843
929
|
* Method to fetch the version of the package.
|
|
844
930
|
* @returns The node package version
|
|
@@ -864,28 +950,36 @@ _dec = trackSpan('AztecNodeService.simulatePublicCalls', (tx)=>({
|
|
|
864
950
|
return this.contractDataSource.getContract(address);
|
|
865
951
|
}
|
|
866
952
|
async getPrivateLogsByTags(tags, page, referenceBlock) {
|
|
953
|
+
let upToBlockNumber;
|
|
867
954
|
if (referenceBlock) {
|
|
868
955
|
const initialBlockHash = await this.#getInitialHeaderHash();
|
|
869
|
-
if (
|
|
956
|
+
if (referenceBlock.equals(initialBlockHash)) {
|
|
957
|
+
upToBlockNumber = BlockNumber(0);
|
|
958
|
+
} else {
|
|
870
959
|
const header = await this.blockSource.getBlockHeaderByHash(referenceBlock);
|
|
871
960
|
if (!header) {
|
|
872
961
|
throw new Error(`Block ${referenceBlock.toString()} not found in the node. This might indicate a reorg has occurred.`);
|
|
873
962
|
}
|
|
963
|
+
upToBlockNumber = header.globalVariables.blockNumber;
|
|
874
964
|
}
|
|
875
965
|
}
|
|
876
|
-
return this.logsSource.getPrivateLogsByTags(tags, page);
|
|
966
|
+
return this.logsSource.getPrivateLogsByTags(tags, page, upToBlockNumber);
|
|
877
967
|
}
|
|
878
968
|
async getPublicLogsByTagsFromContract(contractAddress, tags, page, referenceBlock) {
|
|
969
|
+
let upToBlockNumber;
|
|
879
970
|
if (referenceBlock) {
|
|
880
971
|
const initialBlockHash = await this.#getInitialHeaderHash();
|
|
881
|
-
if (
|
|
972
|
+
if (referenceBlock.equals(initialBlockHash)) {
|
|
973
|
+
upToBlockNumber = BlockNumber(0);
|
|
974
|
+
} else {
|
|
882
975
|
const header = await this.blockSource.getBlockHeaderByHash(referenceBlock);
|
|
883
976
|
if (!header) {
|
|
884
977
|
throw new Error(`Block ${referenceBlock.toString()} not found in the node. This might indicate a reorg has occurred.`);
|
|
885
978
|
}
|
|
979
|
+
upToBlockNumber = header.globalVariables.blockNumber;
|
|
886
980
|
}
|
|
887
981
|
}
|
|
888
|
-
return this.logsSource.getPublicLogsByTagsFromContract(contractAddress, tags, page);
|
|
982
|
+
return this.logsSource.getPublicLogsByTagsFromContract(contractAddress, tags, page, upToBlockNumber);
|
|
889
983
|
}
|
|
890
984
|
/**
|
|
891
985
|
* Gets public logs based on the provided filter.
|
|
@@ -920,8 +1014,9 @@ _dec = trackSpan('AztecNodeService.simulatePublicCalls', (tx)=>({
|
|
|
920
1014
|
throw new Error(`Invalid tx: ${reason}`);
|
|
921
1015
|
}
|
|
922
1016
|
await this.p2pClient.sendTx(tx);
|
|
923
|
-
|
|
924
|
-
this.
|
|
1017
|
+
const duration = timer.ms();
|
|
1018
|
+
this.metrics.receivedTx(duration, true);
|
|
1019
|
+
this.log.info(`Received tx ${txHash} in ${duration}ms`, {
|
|
925
1020
|
txHash
|
|
926
1021
|
});
|
|
927
1022
|
}
|
|
@@ -932,18 +1027,20 @@ _dec = trackSpan('AztecNodeService.simulatePublicCalls', (tx)=>({
|
|
|
932
1027
|
const isKnownToPool = txPoolStatus === 'pending' || txPoolStatus === 'mined';
|
|
933
1028
|
// Then get the actual tx from the archiver, which tracks every tx in a mined block.
|
|
934
1029
|
const settledTxReceipt = await this.blockSource.getSettledTxReceipt(txHash);
|
|
1030
|
+
let receipt;
|
|
935
1031
|
if (settledTxReceipt) {
|
|
936
|
-
|
|
937
|
-
return settledTxReceipt;
|
|
1032
|
+
receipt = settledTxReceipt;
|
|
938
1033
|
} else if (isKnownToPool) {
|
|
939
1034
|
// If the tx is in the pool but not in the archiver, it's pending.
|
|
940
1035
|
// This handles race conditions between archiver and p2p, where the archiver
|
|
941
1036
|
// has pruned the block in which a tx was mined, but p2p has not caught up yet.
|
|
942
|
-
|
|
1037
|
+
receipt = new TxReceipt(txHash, TxStatus.PENDING, undefined, undefined);
|
|
943
1038
|
} else {
|
|
944
1039
|
// Otherwise, if we don't know the tx, we consider it dropped.
|
|
945
|
-
|
|
1040
|
+
receipt = new TxReceipt(txHash, TxStatus.DROPPED, undefined, 'Tx dropped by P2P node');
|
|
946
1041
|
}
|
|
1042
|
+
this.debugLogStore.decorateReceiptWithLogs(txHash.toString(), receipt);
|
|
1043
|
+
return receipt;
|
|
947
1044
|
}
|
|
948
1045
|
getTxEffect(txHash) {
|
|
949
1046
|
return this.blockSource.getTxEffect(txHash);
|
|
@@ -957,6 +1054,7 @@ _dec = trackSpan('AztecNodeService.simulatePublicCalls', (tx)=>({
|
|
|
957
1054
|
await tryStop(this.slasherClient);
|
|
958
1055
|
await tryStop(this.proofVerifier);
|
|
959
1056
|
await tryStop(this.sequencer);
|
|
1057
|
+
await tryStop(this.proverNode);
|
|
960
1058
|
await tryStop(this.p2pClient);
|
|
961
1059
|
await tryStop(this.worldStateSynchronizer);
|
|
962
1060
|
await tryStop(this.blockSource);
|
|
@@ -996,70 +1094,78 @@ _dec = trackSpan('AztecNodeService.simulatePublicCalls', (tx)=>({
|
|
|
996
1094
|
return compactArray(await Promise.all(txHashes.map((txHash)=>this.getTxByHash(txHash))));
|
|
997
1095
|
}
|
|
998
1096
|
async findLeavesIndexes(referenceBlock, treeId, leafValues) {
|
|
999
|
-
const committedDb = await this
|
|
1097
|
+
const committedDb = await this.getWorldState(referenceBlock);
|
|
1000
1098
|
const maybeIndices = await committedDb.findLeafIndices(treeId, leafValues.map((x)=>x.toBuffer()));
|
|
1001
|
-
//
|
|
1002
|
-
const
|
|
1003
|
-
// Now we find the block numbers for the indices
|
|
1004
|
-
const blockNumbers = await committedDb.getBlockNumbersForLeafIndices(treeId,
|
|
1005
|
-
//
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1099
|
+
// Filter out undefined values to query block numbers only for found leaves
|
|
1100
|
+
const definedIndices = maybeIndices.filter((x)=>x !== undefined);
|
|
1101
|
+
// Now we find the block numbers for the defined indices
|
|
1102
|
+
const blockNumbers = await committedDb.getBlockNumbersForLeafIndices(treeId, definedIndices);
|
|
1103
|
+
// Build a map from leaf index to block number
|
|
1104
|
+
const indexToBlockNumber = new Map();
|
|
1105
|
+
for(let i = 0; i < definedIndices.length; i++){
|
|
1106
|
+
const blockNumber = blockNumbers[i];
|
|
1107
|
+
if (blockNumber === undefined) {
|
|
1108
|
+
throw new Error(`Block number is undefined for leaf index ${definedIndices[i]} in tree ${MerkleTreeId[treeId]}`);
|
|
1009
1109
|
}
|
|
1110
|
+
indexToBlockNumber.set(definedIndices[i], blockNumber);
|
|
1010
1111
|
}
|
|
1011
1112
|
// Get unique block numbers in order to optimize num calls to getLeafValue function.
|
|
1012
1113
|
const uniqueBlockNumbers = [
|
|
1013
|
-
...new Set(
|
|
1114
|
+
...new Set(indexToBlockNumber.values())
|
|
1014
1115
|
];
|
|
1015
|
-
// Now we obtain the block hashes from the archive tree
|
|
1016
|
-
// (note that block number corresponds to the leaf index in the archive tree).
|
|
1116
|
+
// Now we obtain the block hashes from the archive tree (block number = leaf index in archive tree).
|
|
1017
1117
|
const blockHashes = await Promise.all(uniqueBlockNumbers.map((blockNumber)=>{
|
|
1018
1118
|
return committedDb.getLeafValue(MerkleTreeId.ARCHIVE, BigInt(blockNumber));
|
|
1019
1119
|
}));
|
|
1020
|
-
//
|
|
1120
|
+
// Build a map from block number to block hash
|
|
1121
|
+
const blockNumberToHash = new Map();
|
|
1021
1122
|
for(let i = 0; i < uniqueBlockNumbers.length; i++){
|
|
1022
|
-
|
|
1123
|
+
const blockHash = blockHashes[i];
|
|
1124
|
+
if (blockHash === undefined) {
|
|
1023
1125
|
throw new Error(`Block hash is undefined for block number ${uniqueBlockNumbers[i]}`);
|
|
1024
1126
|
}
|
|
1127
|
+
blockNumberToHash.set(uniqueBlockNumbers[i], blockHash);
|
|
1025
1128
|
}
|
|
1026
1129
|
// Create DataInBlock objects by combining indices, blockNumbers and blockHashes and return them.
|
|
1027
|
-
return maybeIndices.map((index
|
|
1130
|
+
return maybeIndices.map((index)=>{
|
|
1028
1131
|
if (index === undefined) {
|
|
1029
1132
|
return undefined;
|
|
1030
1133
|
}
|
|
1031
|
-
const blockNumber =
|
|
1134
|
+
const blockNumber = indexToBlockNumber.get(index);
|
|
1032
1135
|
if (blockNumber === undefined) {
|
|
1033
|
-
|
|
1136
|
+
throw new Error(`Block number not found for leaf index ${index} in tree ${MerkleTreeId[treeId]}`);
|
|
1034
1137
|
}
|
|
1035
|
-
const
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
return undefined;
|
|
1138
|
+
const blockHash = blockNumberToHash.get(blockNumber);
|
|
1139
|
+
if (blockHash === undefined) {
|
|
1140
|
+
throw new Error(`Block hash not found for block number ${blockNumber}`);
|
|
1039
1141
|
}
|
|
1040
1142
|
return {
|
|
1041
|
-
l2BlockNumber:
|
|
1143
|
+
l2BlockNumber: blockNumber,
|
|
1042
1144
|
l2BlockHash: new BlockHash(blockHash),
|
|
1043
1145
|
data: index
|
|
1044
1146
|
};
|
|
1045
1147
|
});
|
|
1046
1148
|
}
|
|
1047
1149
|
async getBlockHashMembershipWitness(referenceBlock, blockHash) {
|
|
1048
|
-
|
|
1150
|
+
// The Noir circuit checks the archive membership proof against `anchor_block_header.last_archive.root`,
|
|
1151
|
+
// which is the archive tree root BEFORE the anchor block was added (i.e. the state after block N-1).
|
|
1152
|
+
// So we need the world state at block N-1, not block N, to produce a sibling path matching that root.
|
|
1153
|
+
const referenceBlockNumber = await this.resolveBlockNumber(referenceBlock);
|
|
1154
|
+
const committedDb = await this.getWorldState(BlockNumber(referenceBlockNumber - 1));
|
|
1049
1155
|
const [pathAndIndex] = await committedDb.findSiblingPaths(MerkleTreeId.ARCHIVE, [
|
|
1050
1156
|
blockHash
|
|
1051
1157
|
]);
|
|
1052
1158
|
return pathAndIndex === undefined ? undefined : MembershipWitness.fromSiblingPath(pathAndIndex.index, pathAndIndex.path);
|
|
1053
1159
|
}
|
|
1054
1160
|
async getNoteHashMembershipWitness(referenceBlock, noteHash) {
|
|
1055
|
-
const committedDb = await this
|
|
1161
|
+
const committedDb = await this.getWorldState(referenceBlock);
|
|
1056
1162
|
const [pathAndIndex] = await committedDb.findSiblingPaths(MerkleTreeId.NOTE_HASH_TREE, [
|
|
1057
1163
|
noteHash
|
|
1058
1164
|
]);
|
|
1059
1165
|
return pathAndIndex === undefined ? undefined : MembershipWitness.fromSiblingPath(pathAndIndex.index, pathAndIndex.path);
|
|
1060
1166
|
}
|
|
1061
1167
|
async getL1ToL2MessageMembershipWitness(referenceBlock, l1ToL2Message) {
|
|
1062
|
-
const db = await this
|
|
1168
|
+
const db = await this.getWorldState(referenceBlock);
|
|
1063
1169
|
const [witness] = await db.findSiblingPaths(MerkleTreeId.L1_TO_L2_MESSAGE_TREE, [
|
|
1064
1170
|
l1ToL2Message
|
|
1065
1171
|
]);
|
|
@@ -1072,9 +1178,9 @@ _dec = trackSpan('AztecNodeService.simulatePublicCalls', (tx)=>({
|
|
|
1072
1178
|
witness.path
|
|
1073
1179
|
];
|
|
1074
1180
|
}
|
|
1075
|
-
async
|
|
1181
|
+
async getL1ToL2MessageCheckpoint(l1ToL2Message) {
|
|
1076
1182
|
const messageIndex = await this.l1ToL2MessageSource.getL1ToL2MessageIndex(l1ToL2Message);
|
|
1077
|
-
return messageIndex ?
|
|
1183
|
+
return messageIndex ? InboxLeaf.checkpointNumberFromIndex(messageIndex) : undefined;
|
|
1078
1184
|
}
|
|
1079
1185
|
/**
|
|
1080
1186
|
* Returns whether an L1 to L2 message is synced by archiver and if it's ready to be included in a block.
|
|
@@ -1091,23 +1197,11 @@ _dec = trackSpan('AztecNodeService.simulatePublicCalls', (tx)=>({
|
|
|
1091
1197
|
*/ async getL2ToL1Messages(epoch) {
|
|
1092
1198
|
// Assumes `getCheckpointedBlocksForEpoch` returns blocks in ascending order of block number.
|
|
1093
1199
|
const checkpointedBlocks = await this.blockSource.getCheckpointedBlocksForEpoch(epoch);
|
|
1094
|
-
const blocksInCheckpoints =
|
|
1095
|
-
let previousSlotNumber = SlotNumber.ZERO;
|
|
1096
|
-
let checkpointIndex = -1;
|
|
1097
|
-
for (const checkpointedBlock of checkpointedBlocks){
|
|
1098
|
-
const block = checkpointedBlock.block;
|
|
1099
|
-
const slotNumber = block.header.globalVariables.slotNumber;
|
|
1100
|
-
if (slotNumber !== previousSlotNumber) {
|
|
1101
|
-
checkpointIndex++;
|
|
1102
|
-
blocksInCheckpoints.push([]);
|
|
1103
|
-
previousSlotNumber = slotNumber;
|
|
1104
|
-
}
|
|
1105
|
-
blocksInCheckpoints[checkpointIndex].push(block);
|
|
1106
|
-
}
|
|
1200
|
+
const blocksInCheckpoints = chunkBy(checkpointedBlocks, (cb)=>cb.block.header.globalVariables.slotNumber).map((group)=>group.map((cb)=>cb.block));
|
|
1107
1201
|
return blocksInCheckpoints.map((blocks)=>blocks.map((block)=>block.body.txEffects.map((txEffect)=>txEffect.l2ToL1Msgs)));
|
|
1108
1202
|
}
|
|
1109
1203
|
async getNullifierMembershipWitness(referenceBlock, nullifier) {
|
|
1110
|
-
const db = await this
|
|
1204
|
+
const db = await this.getWorldState(referenceBlock);
|
|
1111
1205
|
const [witness] = await db.findSiblingPaths(MerkleTreeId.NULLIFIER_TREE, [
|
|
1112
1206
|
nullifier.toBuffer()
|
|
1113
1207
|
]);
|
|
@@ -1121,36 +1215,22 @@ _dec = trackSpan('AztecNodeService.simulatePublicCalls', (tx)=>({
|
|
|
1121
1215
|
}
|
|
1122
1216
|
return new NullifierMembershipWitness(index, leafPreimage, path);
|
|
1123
1217
|
}
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
* @param referenceBlock - The block parameter (block number, block hash, or 'latest') at which to get the data
|
|
1127
|
-
* (which contains the root of the nullifier tree in which we are searching for the nullifier).
|
|
1128
|
-
* @param nullifier - Nullifier we try to find the low nullifier witness for.
|
|
1129
|
-
* @returns The low nullifier membership witness (if found).
|
|
1130
|
-
* @remarks Low nullifier witness can be used to perform a nullifier non-inclusion proof by leveraging the "linked
|
|
1131
|
-
* list structure" of leaves and proving that a lower nullifier is pointing to a bigger next value than the nullifier
|
|
1132
|
-
* we are trying to prove non-inclusion for.
|
|
1133
|
-
*
|
|
1134
|
-
* Note: This function returns the membership witness of the nullifier itself and not the low nullifier when
|
|
1135
|
-
* the nullifier already exists in the tree. This is because the `getPreviousValueIndex` function returns the
|
|
1136
|
-
* index of the nullifier itself when it already exists in the tree.
|
|
1137
|
-
* TODO: This is a confusing behavior and we should eventually address that.
|
|
1138
|
-
*/ async getLowNullifierMembershipWitness(referenceBlock, nullifier) {
|
|
1139
|
-
const committedDb = await this.#getWorldState(referenceBlock);
|
|
1218
|
+
async getLowNullifierMembershipWitness(referenceBlock, nullifier) {
|
|
1219
|
+
const committedDb = await this.getWorldState(referenceBlock);
|
|
1140
1220
|
const findResult = await committedDb.getPreviousValueIndex(MerkleTreeId.NULLIFIER_TREE, nullifier.toBigInt());
|
|
1141
1221
|
if (!findResult) {
|
|
1142
1222
|
return undefined;
|
|
1143
1223
|
}
|
|
1144
1224
|
const { index, alreadyPresent } = findResult;
|
|
1145
1225
|
if (alreadyPresent) {
|
|
1146
|
-
|
|
1226
|
+
throw new Error(`Cannot prove nullifier non-inclusion: nullifier ${nullifier.toBigInt()} already exists in the tree`);
|
|
1147
1227
|
}
|
|
1148
1228
|
const preimageData = await committedDb.getLeafPreimage(MerkleTreeId.NULLIFIER_TREE, index);
|
|
1149
1229
|
const siblingPath = await committedDb.getSiblingPath(MerkleTreeId.NULLIFIER_TREE, BigInt(index));
|
|
1150
1230
|
return new NullifierMembershipWitness(BigInt(index), preimageData, siblingPath);
|
|
1151
1231
|
}
|
|
1152
1232
|
async getPublicDataWitness(referenceBlock, leafSlot) {
|
|
1153
|
-
const committedDb = await this
|
|
1233
|
+
const committedDb = await this.getWorldState(referenceBlock);
|
|
1154
1234
|
const lowLeafResult = await committedDb.getPreviousValueIndex(MerkleTreeId.PUBLIC_DATA_TREE, leafSlot.toBigInt());
|
|
1155
1235
|
if (!lowLeafResult) {
|
|
1156
1236
|
return undefined;
|
|
@@ -1161,7 +1241,7 @@ _dec = trackSpan('AztecNodeService.simulatePublicCalls', (tx)=>({
|
|
|
1161
1241
|
}
|
|
1162
1242
|
}
|
|
1163
1243
|
async getPublicStorageAt(referenceBlock, contract, slot) {
|
|
1164
|
-
const committedDb = await this
|
|
1244
|
+
const committedDb = await this.getWorldState(referenceBlock);
|
|
1165
1245
|
const leafSlot = await computePublicDataTreeLeafSlot(contract, slot);
|
|
1166
1246
|
const lowLeafResult = await committedDb.getPreviousValueIndex(MerkleTreeId.PUBLIC_DATA_TREE, leafSlot.toBigInt());
|
|
1167
1247
|
if (!lowLeafResult || !lowLeafResult.alreadyPresent) {
|
|
@@ -1194,6 +1274,12 @@ _dec = trackSpan('AztecNodeService.simulatePublicCalls', (tx)=>({
|
|
|
1194
1274
|
*/ async getBlockHeaderByArchive(archive) {
|
|
1195
1275
|
return await this.blockSource.getBlockHeaderByArchive(archive);
|
|
1196
1276
|
}
|
|
1277
|
+
getBlockData(number) {
|
|
1278
|
+
return this.blockSource.getBlockData(number);
|
|
1279
|
+
}
|
|
1280
|
+
getBlockDataByArchive(archive) {
|
|
1281
|
+
return this.blockSource.getBlockDataByArchive(archive);
|
|
1282
|
+
}
|
|
1197
1283
|
/**
|
|
1198
1284
|
* Simulates the public part of a transaction with the current state.
|
|
1199
1285
|
* @param tx - The transaction to simulate.
|
|
@@ -1206,7 +1292,8 @@ _dec = trackSpan('AztecNodeService.simulatePublicCalls', (tx)=>({
|
|
|
1206
1292
|
throw new BadRequestError(`Transaction total gas limit ${txGasLimit + teardownGasLimit} (${txGasLimit} + ${teardownGasLimit}) exceeds maximum gas limit ${this.config.rpcSimulatePublicMaxGasLimit} for simulation`);
|
|
1207
1293
|
}
|
|
1208
1294
|
const txHash = tx.getTxHash();
|
|
1209
|
-
const
|
|
1295
|
+
const latestBlockNumber = await this.blockSource.getBlockNumber();
|
|
1296
|
+
const blockNumber = BlockNumber.add(latestBlockNumber, 1);
|
|
1210
1297
|
// If sequencer is not initialized, we just set these values to zero for simulation.
|
|
1211
1298
|
const coinbase = EthAddress.ZERO;
|
|
1212
1299
|
const feeRecipient = AztecAddress.ZERO;
|
|
@@ -1217,6 +1304,8 @@ _dec = trackSpan('AztecNodeService.simulatePublicCalls', (tx)=>({
|
|
|
1217
1304
|
txHash,
|
|
1218
1305
|
blockNumber
|
|
1219
1306
|
});
|
|
1307
|
+
// Ensure world-state has caught up with the latest block we loaded from the archiver
|
|
1308
|
+
await this.worldStateSynchronizer.syncImmediate(latestBlockNumber);
|
|
1220
1309
|
const merkleTreeFork = await this.worldStateSynchronizer.fork();
|
|
1221
1310
|
try {
|
|
1222
1311
|
const config = PublicSimulatorConfig.from({
|
|
@@ -1231,7 +1320,7 @@ _dec = trackSpan('AztecNodeService.simulatePublicCalls', (tx)=>({
|
|
|
1231
1320
|
});
|
|
1232
1321
|
const processor = publicProcessorFactory.create(merkleTreeFork, newGlobalVariables, config);
|
|
1233
1322
|
// REFACTOR: Consider merging ProcessReturnValues into ProcessedTx
|
|
1234
|
-
const [processedTxs, failedTxs, _usedTxs, returns] = await processor.process([
|
|
1323
|
+
const [processedTxs, failedTxs, _usedTxs, returns, debugLogs] = await processor.process([
|
|
1235
1324
|
tx
|
|
1236
1325
|
]);
|
|
1237
1326
|
// REFACTOR: Consider returning the error rather than throwing
|
|
@@ -1242,7 +1331,7 @@ _dec = trackSpan('AztecNodeService.simulatePublicCalls', (tx)=>({
|
|
|
1242
1331
|
throw failedTxs[0].error;
|
|
1243
1332
|
}
|
|
1244
1333
|
const [processedTx] = processedTxs;
|
|
1245
|
-
return new PublicSimulationOutput(processedTx.revertReason, processedTx.globalVariables, processedTx.txEffect, returns, processedTx.gasUsed);
|
|
1334
|
+
return new PublicSimulationOutput(processedTx.revertReason, processedTx.globalVariables, processedTx.txEffect, returns, processedTx.gasUsed, debugLogs);
|
|
1246
1335
|
} finally{
|
|
1247
1336
|
await merkleTreeFork.close();
|
|
1248
1337
|
}
|
|
@@ -1250,18 +1339,25 @@ _dec = trackSpan('AztecNodeService.simulatePublicCalls', (tx)=>({
|
|
|
1250
1339
|
async isValidTx(tx, { isSimulation, skipFeeEnforcement } = {}) {
|
|
1251
1340
|
const db = this.worldStateSynchronizer.getCommitted();
|
|
1252
1341
|
const verifier = isSimulation ? undefined : this.proofVerifier;
|
|
1253
|
-
// We accept transactions if they are not expired by the next slot (checked based on the
|
|
1342
|
+
// We accept transactions if they are not expired by the next slot (checked based on the ExpirationTimestamp field)
|
|
1254
1343
|
const { ts: nextSlotTimestamp } = this.epochCache.getEpochAndSlotInNextL1Slot();
|
|
1255
1344
|
const blockNumber = BlockNumber(await this.blockSource.getBlockNumber() + 1);
|
|
1256
|
-
const
|
|
1345
|
+
const l1Constants = await this.blockSource.getL1Constants();
|
|
1346
|
+
const validator = createTxValidatorForAcceptingTxsOverRPC(db, this.contractDataSource, verifier, {
|
|
1257
1347
|
timestamp: nextSlotTimestamp,
|
|
1258
1348
|
blockNumber,
|
|
1259
1349
|
l1ChainId: this.l1ChainId,
|
|
1260
1350
|
rollupVersion: this.version,
|
|
1261
|
-
setupAllowList:
|
|
1351
|
+
setupAllowList: [
|
|
1352
|
+
...await getDefaultAllowedSetupFunctions(),
|
|
1353
|
+
...this.config.txPublicSetupAllowListExtend ?? []
|
|
1354
|
+
],
|
|
1262
1355
|
gasFees: await this.getCurrentMinFees(),
|
|
1263
1356
|
skipFeeEnforcement,
|
|
1264
|
-
txsPermitted: !this.config.disableTransactions
|
|
1357
|
+
txsPermitted: !this.config.disableTransactions,
|
|
1358
|
+
rollupManaLimit: l1Constants.rollupManaLimit,
|
|
1359
|
+
maxBlockL2Gas: this.config.validateMaxL2BlockGas,
|
|
1360
|
+
maxBlockDAGas: this.config.validateMaxDABlockGas
|
|
1265
1361
|
}, this.log.getBindings());
|
|
1266
1362
|
return await validator.validateTx(tx);
|
|
1267
1363
|
}
|
|
@@ -1404,6 +1500,73 @@ _dec = trackSpan('AztecNodeService.simulatePublicCalls', (tx)=>({
|
|
|
1404
1500
|
return this.slasherClient.gatherOffensesForRound(round === 'current' ? undefined : BigInt(round));
|
|
1405
1501
|
}
|
|
1406
1502
|
}
|
|
1503
|
+
async reloadKeystore() {
|
|
1504
|
+
if (!this.config.keyStoreDirectory?.length) {
|
|
1505
|
+
throw new BadRequestError('Cannot reload keystore: node is not using a file-based keystore. ' + 'Set KEY_STORE_DIRECTORY to use file-based keystores.');
|
|
1506
|
+
}
|
|
1507
|
+
if (!this.validatorClient) {
|
|
1508
|
+
throw new BadRequestError('Cannot reload keystore: validator is not enabled.');
|
|
1509
|
+
}
|
|
1510
|
+
this.log.info('Reloading keystore from disk');
|
|
1511
|
+
// Re-read and validate keystore files
|
|
1512
|
+
const keyStores = loadKeystores(this.config.keyStoreDirectory);
|
|
1513
|
+
const newManager = new KeystoreManager(mergeKeystores(keyStores));
|
|
1514
|
+
await newManager.validateSigners();
|
|
1515
|
+
ValidatorClient.validateKeyStoreConfiguration(newManager, this.log);
|
|
1516
|
+
// Validate that every validator's publisher keys overlap with the L1 signers
|
|
1517
|
+
// that were initialized at startup. Publishers cannot be hot-reloaded, so a
|
|
1518
|
+
// validator with a publisher key that doesn't match any existing L1 signer
|
|
1519
|
+
// would silently fail on every proposer slot.
|
|
1520
|
+
if (this.keyStoreManager && this.sequencer) {
|
|
1521
|
+
const oldAdapter = NodeKeystoreAdapter.fromKeyStoreManager(this.keyStoreManager);
|
|
1522
|
+
const availablePublishers = new Set(oldAdapter.getAttesterAddresses().flatMap((a)=>oldAdapter.getPublisherAddresses(a).map((p)=>p.toString().toLowerCase())));
|
|
1523
|
+
const newAdapter = NodeKeystoreAdapter.fromKeyStoreManager(newManager);
|
|
1524
|
+
for (const attester of newAdapter.getAttesterAddresses()){
|
|
1525
|
+
const pubs = newAdapter.getPublisherAddresses(attester);
|
|
1526
|
+
if (pubs.length > 0 && !pubs.some((p)=>availablePublishers.has(p.toString().toLowerCase()))) {
|
|
1527
|
+
throw new BadRequestError(`Cannot reload keystore: validator ${attester} has publisher keys ` + `[${pubs.map((p)=>p.toString()).join(', ')}] but none match the L1 signers initialized at startup ` + `[${[
|
|
1528
|
+
...availablePublishers
|
|
1529
|
+
].join(', ')}]. Publishers cannot be hot-reloaded — ` + `use an existing publisher key or restart the node.`);
|
|
1530
|
+
}
|
|
1531
|
+
}
|
|
1532
|
+
}
|
|
1533
|
+
// Build adapters for old and new keystores to compute diff
|
|
1534
|
+
const newAdapter = NodeKeystoreAdapter.fromKeyStoreManager(newManager);
|
|
1535
|
+
const newAddresses = newAdapter.getAttesterAddresses();
|
|
1536
|
+
const oldAddresses = this.keyStoreManager ? NodeKeystoreAdapter.fromKeyStoreManager(this.keyStoreManager).getAttesterAddresses() : [];
|
|
1537
|
+
const oldSet = new Set(oldAddresses.map((a)=>a.toString()));
|
|
1538
|
+
const newSet = new Set(newAddresses.map((a)=>a.toString()));
|
|
1539
|
+
const added = newAddresses.filter((a)=>!oldSet.has(a.toString()));
|
|
1540
|
+
const removed = oldAddresses.filter((a)=>!newSet.has(a.toString()));
|
|
1541
|
+
if (added.length > 0) {
|
|
1542
|
+
this.log.info(`Keystore reload: adding attester keys: ${added.map((a)=>a.toString()).join(', ')}`);
|
|
1543
|
+
}
|
|
1544
|
+
if (removed.length > 0) {
|
|
1545
|
+
this.log.info(`Keystore reload: removing attester keys: ${removed.map((a)=>a.toString()).join(', ')}`);
|
|
1546
|
+
}
|
|
1547
|
+
if (added.length === 0 && removed.length === 0) {
|
|
1548
|
+
this.log.info('Keystore reload: attester keys unchanged');
|
|
1549
|
+
}
|
|
1550
|
+
// Update the validator client (coinbase, feeRecipient, attester keys)
|
|
1551
|
+
this.validatorClient.reloadKeystore(newManager);
|
|
1552
|
+
// Update the publisher factory's keystore so newly-added validators
|
|
1553
|
+
// can be matched to existing publisher keys when proposing blocks.
|
|
1554
|
+
if (this.sequencer) {
|
|
1555
|
+
this.sequencer.updatePublisherNodeKeyStore(newAdapter);
|
|
1556
|
+
}
|
|
1557
|
+
// Update slasher's "don't-slash-self" list with new validator addresses
|
|
1558
|
+
if (this.slasherClient && !this.config.slashSelfAllowed) {
|
|
1559
|
+
const slashValidatorsNever = unique([
|
|
1560
|
+
...this.config.slashValidatorsNever ?? [],
|
|
1561
|
+
...newAddresses
|
|
1562
|
+
].map((a)=>a.toString())).map(EthAddress.fromString);
|
|
1563
|
+
this.slasherClient.updateConfig({
|
|
1564
|
+
slashValidatorsNever
|
|
1565
|
+
});
|
|
1566
|
+
}
|
|
1567
|
+
this.keyStoreManager = newManager;
|
|
1568
|
+
this.log.info('Keystore reloaded: coinbase, feeRecipient, and attester keys updated');
|
|
1569
|
+
}
|
|
1407
1570
|
#getInitialHeaderHash() {
|
|
1408
1571
|
if (!this.initialHeaderHashPromise) {
|
|
1409
1572
|
this.initialHeaderHashPromise = this.worldStateSynchronizer.getCommitted().getInitialHeader().hash();
|
|
@@ -1414,7 +1577,7 @@ _dec = trackSpan('AztecNodeService.simulatePublicCalls', (tx)=>({
|
|
|
1414
1577
|
* Returns an instance of MerkleTreeOperations having first ensured the world state is fully synched
|
|
1415
1578
|
* @param block - The block parameter (block number, block hash, or 'latest') at which to get the data.
|
|
1416
1579
|
* @returns An instance of a committed MerkleTreeOperations
|
|
1417
|
-
*/ async
|
|
1580
|
+
*/ async getWorldState(block) {
|
|
1418
1581
|
let blockSyncedTo = BlockNumber.ZERO;
|
|
1419
1582
|
try {
|
|
1420
1583
|
// Attempt to sync the world state if necessary
|
|
@@ -1426,6 +1589,8 @@ _dec = trackSpan('AztecNodeService.simulatePublicCalls', (tx)=>({
|
|
|
1426
1589
|
this.log.debug(`Using committed db for block 'latest', world state synced upto ${blockSyncedTo}`);
|
|
1427
1590
|
return this.worldStateSynchronizer.getCommitted();
|
|
1428
1591
|
}
|
|
1592
|
+
// Get the block number, either directly from the parameter or by quering the archiver with the block hash
|
|
1593
|
+
let blockNumber;
|
|
1429
1594
|
if (BlockHash.isBlockHash(block)) {
|
|
1430
1595
|
const initialBlockHash = await this.#getInitialHeaderHash();
|
|
1431
1596
|
if (block.equals(initialBlockHash)) {
|
|
@@ -1436,19 +1601,41 @@ _dec = trackSpan('AztecNodeService.simulatePublicCalls', (tx)=>({
|
|
|
1436
1601
|
if (!header) {
|
|
1437
1602
|
throw new Error(`Block hash ${block.toString()} not found when querying world state. If the node API has been queried with anchor block hash possibly a reorg has occurred.`);
|
|
1438
1603
|
}
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
}
|
|
1443
|
-
//
|
|
1444
|
-
{
|
|
1445
|
-
|
|
1446
|
-
|
|
1447
|
-
|
|
1604
|
+
blockNumber = header.getBlockNumber();
|
|
1605
|
+
} else {
|
|
1606
|
+
blockNumber = block;
|
|
1607
|
+
}
|
|
1608
|
+
// Check it's within world state sync range
|
|
1609
|
+
if (blockNumber > blockSyncedTo) {
|
|
1610
|
+
throw new Error(`Queried block ${block} not yet synced by the node (node is synced upto ${blockSyncedTo}).`);
|
|
1611
|
+
}
|
|
1612
|
+
this.log.debug(`Using snapshot for block ${blockNumber}, world state synced upto ${blockSyncedTo}`);
|
|
1613
|
+
const snapshot = this.worldStateSynchronizer.getSnapshot(blockNumber);
|
|
1614
|
+
// Double-check world-state synced to the same block hash as was requested
|
|
1615
|
+
if (BlockHash.isBlockHash(block)) {
|
|
1616
|
+
const blockHash = await snapshot.getLeafValue(MerkleTreeId.ARCHIVE, BigInt(blockNumber));
|
|
1617
|
+
if (!blockHash || !new BlockHash(blockHash).equals(block)) {
|
|
1618
|
+
throw new Error(`Block hash ${block.toString()} not found in world state at block number ${blockNumber}. If the node API has been queried with anchor block hash possibly a reorg has occurred.`);
|
|
1619
|
+
}
|
|
1620
|
+
}
|
|
1621
|
+
return snapshot;
|
|
1622
|
+
}
|
|
1623
|
+
/** Resolves a block parameter to a block number. */ async resolveBlockNumber(block) {
|
|
1624
|
+
if (block === 'latest') {
|
|
1625
|
+
return BlockNumber(await this.blockSource.getBlockNumber());
|
|
1626
|
+
}
|
|
1627
|
+
if (BlockHash.isBlockHash(block)) {
|
|
1628
|
+
const initialBlockHash = await this.#getInitialHeaderHash();
|
|
1629
|
+
if (block.equals(initialBlockHash)) {
|
|
1630
|
+
return BlockNumber.ZERO;
|
|
1631
|
+
}
|
|
1632
|
+
const header = await this.blockSource.getBlockHeaderByHash(block);
|
|
1633
|
+
if (!header) {
|
|
1634
|
+
throw new Error(`Block hash ${block.toString()} not found.`);
|
|
1448
1635
|
}
|
|
1449
|
-
|
|
1450
|
-
return this.worldStateSynchronizer.getSnapshot(blockNumber);
|
|
1636
|
+
return header.getBlockNumber();
|
|
1451
1637
|
}
|
|
1638
|
+
return block;
|
|
1452
1639
|
}
|
|
1453
1640
|
/**
|
|
1454
1641
|
* Ensure we fully sync the world state
|