@aztec/aztec-node 0.0.1-commit.8afd444 → 0.0.1-commit.934299a21
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 +7 -4
- package/dest/aztec-node/config.d.ts.map +1 -1
- package/dest/aztec-node/config.js +10 -2
- package/dest/aztec-node/server.d.ts +18 -4
- package/dest/aztec-node/server.d.ts.map +1 -1
- package/dest/aztec-node/server.js +169 -28
- package/dest/sentinel/sentinel.d.ts +2 -2
- package/dest/sentinel/sentinel.d.ts.map +1 -1
- package/dest/sentinel/sentinel.js +53 -27
- 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 +27 -25
- package/src/aztec-node/config.ts +24 -8
- package/src/aztec-node/server.ts +221 -33
- package/src/sentinel/sentinel.ts +56 -23
- 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
380
|
import { getPublicClient } from '@aztec/ethereum/client';
|
|
380
381
|
import { RegistryContract, RollupContract } from '@aztec/ethereum/contracts';
|
|
381
382
|
import { BlockNumber, SlotNumber } from '@aztec/foundation/branded-types';
|
|
382
|
-
import { compactArray, pick } from '@aztec/foundation/collection';
|
|
383
|
+
import { 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,13 +405,14 @@ 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
410
|
import { P2PClientType } from '@aztec/stdlib/p2p';
|
|
407
411
|
import { MerkleTreeId, NullifierMembershipWitness, PublicDataWitness } from '@aztec/stdlib/trees';
|
|
408
412
|
import { PublicSimulationOutput, TxReceipt, TxStatus } from '@aztec/stdlib/tx';
|
|
409
413
|
import { getPackageVersion } from '@aztec/stdlib/update-checker';
|
|
410
414
|
import { Attributes, getTelemetryClient, trackSpan } from '@aztec/telemetry-client';
|
|
411
|
-
import { FullNodeCheckpointsBuilder as CheckpointsBuilder, FullNodeCheckpointsBuilder, NodeKeystoreAdapter, ValidatorClient, createBlockProposalHandler, createValidatorClient
|
|
415
|
+
import { FullNodeCheckpointsBuilder as CheckpointsBuilder, FullNodeCheckpointsBuilder, NodeKeystoreAdapter, ValidatorClient, createBlockProposalHandler, createValidatorClient } from '@aztec/validator-client';
|
|
412
416
|
import { createWorldStateSynchronizer } from '@aztec/world-state';
|
|
413
417
|
import { createPublicClient, fallback, http } from 'viem';
|
|
414
418
|
import { createSentinel } from '../sentinel/factory.js';
|
|
@@ -428,6 +432,7 @@ _dec = trackSpan('AztecNodeService.simulatePublicCalls', (tx)=>({
|
|
|
428
432
|
l1ToL2MessageSource;
|
|
429
433
|
worldStateSynchronizer;
|
|
430
434
|
sequencer;
|
|
435
|
+
proverNode;
|
|
431
436
|
slasherClient;
|
|
432
437
|
validatorsSentinel;
|
|
433
438
|
epochPruneWatcher;
|
|
@@ -440,6 +445,9 @@ _dec = trackSpan('AztecNodeService.simulatePublicCalls', (tx)=>({
|
|
|
440
445
|
telemetry;
|
|
441
446
|
log;
|
|
442
447
|
blobClient;
|
|
448
|
+
validatorClient;
|
|
449
|
+
keyStoreManager;
|
|
450
|
+
debugLogStore;
|
|
443
451
|
static{
|
|
444
452
|
({ e: [_initProto] } = _apply_decs_2203_r(this, [
|
|
445
453
|
[
|
|
@@ -454,7 +462,7 @@ _dec = trackSpan('AztecNodeService.simulatePublicCalls', (tx)=>({
|
|
|
454
462
|
// Prevent two snapshot operations to happen simultaneously
|
|
455
463
|
isUploadingSnapshot;
|
|
456
464
|
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){
|
|
465
|
+
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
466
|
this.config = config;
|
|
459
467
|
this.p2pClient = p2pClient;
|
|
460
468
|
this.blockSource = blockSource;
|
|
@@ -463,6 +471,7 @@ _dec = trackSpan('AztecNodeService.simulatePublicCalls', (tx)=>({
|
|
|
463
471
|
this.l1ToL2MessageSource = l1ToL2MessageSource;
|
|
464
472
|
this.worldStateSynchronizer = worldStateSynchronizer;
|
|
465
473
|
this.sequencer = sequencer;
|
|
474
|
+
this.proverNode = proverNode;
|
|
466
475
|
this.slasherClient = slasherClient;
|
|
467
476
|
this.validatorsSentinel = validatorsSentinel;
|
|
468
477
|
this.epochPruneWatcher = epochPruneWatcher;
|
|
@@ -475,12 +484,21 @@ _dec = trackSpan('AztecNodeService.simulatePublicCalls', (tx)=>({
|
|
|
475
484
|
this.telemetry = telemetry;
|
|
476
485
|
this.log = log;
|
|
477
486
|
this.blobClient = blobClient;
|
|
487
|
+
this.validatorClient = validatorClient;
|
|
488
|
+
this.keyStoreManager = keyStoreManager;
|
|
489
|
+
this.debugLogStore = debugLogStore;
|
|
478
490
|
this.initialHeaderHashPromise = (_initProto(this), undefined);
|
|
479
491
|
this.isUploadingSnapshot = false;
|
|
480
492
|
this.metrics = new NodeMetrics(telemetry, 'AztecNodeService');
|
|
481
493
|
this.tracer = telemetry.getTracer('AztecNodeService');
|
|
482
494
|
this.log.info(`Aztec Node version: ${this.packageVersion}`);
|
|
483
495
|
this.log.info(`Aztec Node started on chain 0x${l1ChainId.toString(16)}`, config.l1Contracts);
|
|
496
|
+
// A defensive check that protects us against introducing a bug in the complex `createAndSync` function. We must
|
|
497
|
+
// never have debugLogStore enabled when not in test mode because then we would be accumulating debug logs in
|
|
498
|
+
// memory which could be a DoS vector on the sequencer (since no fees are paid for debug logs).
|
|
499
|
+
if (debugLogStore.isEnabled && config.realProofs) {
|
|
500
|
+
throw new Error('debugLogStore should never be enabled when realProofs are set');
|
|
501
|
+
}
|
|
484
502
|
}
|
|
485
503
|
async getWorldStateSyncStatus() {
|
|
486
504
|
const status = await this.worldStateSynchronizer.status();
|
|
@@ -502,16 +520,27 @@ _dec = trackSpan('AztecNodeService.simulatePublicCalls', (tx)=>({
|
|
|
502
520
|
const telemetry = deps.telemetry ?? getTelemetryClient();
|
|
503
521
|
const dateProvider = deps.dateProvider ?? new DateProvider();
|
|
504
522
|
const ethereumChain = createEthereumChain(config.l1RpcUrls, config.l1ChainId);
|
|
505
|
-
// Build a key store from file if given or from environment otherwise
|
|
523
|
+
// Build a key store from file if given or from environment otherwise.
|
|
524
|
+
// We keep the raw KeyStore available so we can merge with prover keys if enableProverNode is set.
|
|
506
525
|
let keyStoreManager;
|
|
507
526
|
const keyStoreProvided = config.keyStoreDirectory !== undefined && config.keyStoreDirectory.length > 0;
|
|
508
527
|
if (keyStoreProvided) {
|
|
509
528
|
const keyStores = loadKeystores(config.keyStoreDirectory);
|
|
510
529
|
keyStoreManager = new KeystoreManager(mergeKeystores(keyStores));
|
|
511
530
|
} else {
|
|
512
|
-
const
|
|
513
|
-
|
|
514
|
-
|
|
531
|
+
const rawKeyStores = [];
|
|
532
|
+
const validatorKeyStore = createKeyStoreForValidator(config);
|
|
533
|
+
if (validatorKeyStore) {
|
|
534
|
+
rawKeyStores.push(validatorKeyStore);
|
|
535
|
+
}
|
|
536
|
+
if (config.enableProverNode) {
|
|
537
|
+
const proverKeyStore = createKeyStoreForProver(config);
|
|
538
|
+
if (proverKeyStore) {
|
|
539
|
+
rawKeyStores.push(proverKeyStore);
|
|
540
|
+
}
|
|
541
|
+
}
|
|
542
|
+
if (rawKeyStores.length > 0) {
|
|
543
|
+
keyStoreManager = new KeystoreManager(rawKeyStores.length === 1 ? rawKeyStores[0] : mergeKeystores(rawKeyStores));
|
|
515
544
|
}
|
|
516
545
|
}
|
|
517
546
|
await keyStoreManager?.validateSigners();
|
|
@@ -520,8 +549,8 @@ _dec = trackSpan('AztecNodeService.simulatePublicCalls', (tx)=>({
|
|
|
520
549
|
if (keyStoreManager === undefined) {
|
|
521
550
|
throw new Error('Failed to create key store, a requirement for running a validator');
|
|
522
551
|
}
|
|
523
|
-
if (!keyStoreProvided) {
|
|
524
|
-
log.warn(
|
|
552
|
+
if (!keyStoreProvided && process.env.NODE_ENV !== 'test') {
|
|
553
|
+
log.warn("Keystore created from env: it's recommended to use a file-based key store for production");
|
|
525
554
|
}
|
|
526
555
|
ValidatorClient.validateKeyStoreConfiguration(keyStoreManager, log);
|
|
527
556
|
}
|
|
@@ -552,7 +581,7 @@ _dec = trackSpan('AztecNodeService.simulatePublicCalls', (tx)=>({
|
|
|
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,8 +598,13 @@ _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);
|
|
576
610
|
// create the tx pool and the p2p client, which will need the l2 block source
|
|
@@ -658,27 +692,29 @@ _dec = trackSpan('AztecNodeService.simulatePublicCalls', (tx)=>({
|
|
|
658
692
|
const validatorAddresses = keyStoreManager ? NodeKeystoreAdapter.fromKeyStoreManager(keyStoreManager).getAddresses() : [];
|
|
659
693
|
slasherClient = await createSlasher(config, config.l1Contracts, getPublicClient(config), watchers, dateProvider, epochCache, validatorAddresses, undefined);
|
|
660
694
|
await slasherClient.start();
|
|
661
|
-
const l1TxUtils = config.
|
|
695
|
+
const l1TxUtils = config.sequencerPublisherForwarderAddress ? await createForwarderL1TxUtilsFromSigners(publicClient, keyStoreManager.createAllValidatorPublisherSigners(), config.sequencerPublisherForwarderAddress, {
|
|
662
696
|
...config,
|
|
663
697
|
scope: 'sequencer'
|
|
664
698
|
}, {
|
|
665
699
|
telemetry,
|
|
666
700
|
logger: log.createChild('l1-tx-utils'),
|
|
667
|
-
dateProvider
|
|
668
|
-
|
|
701
|
+
dateProvider,
|
|
702
|
+
kzg: Blob.getViemKzgInstance()
|
|
703
|
+
}) : await createL1TxUtilsFromSigners(publicClient, keyStoreManager.createAllValidatorPublisherSigners(), {
|
|
669
704
|
...config,
|
|
670
705
|
scope: 'sequencer'
|
|
671
706
|
}, {
|
|
672
707
|
telemetry,
|
|
673
708
|
logger: log.createChild('l1-tx-utils'),
|
|
674
|
-
dateProvider
|
|
709
|
+
dateProvider,
|
|
710
|
+
kzg: Blob.getViemKzgInstance()
|
|
675
711
|
});
|
|
676
712
|
// Create and start the sequencer client
|
|
677
713
|
const checkpointsBuilder = new CheckpointsBuilder({
|
|
678
714
|
...config,
|
|
679
715
|
l1GenesisTime,
|
|
680
716
|
slotDuration: Number(slotDuration)
|
|
681
|
-
}, worldStateSynchronizer, archiver, dateProvider, telemetry);
|
|
717
|
+
}, worldStateSynchronizer, archiver, dateProvider, telemetry, debugLogStore);
|
|
682
718
|
sequencer = await SequencerClient.new(config, {
|
|
683
719
|
...deps,
|
|
684
720
|
epochCache,
|
|
@@ -702,13 +738,35 @@ _dec = trackSpan('AztecNodeService.simulatePublicCalls', (tx)=>({
|
|
|
702
738
|
} else if (sequencer) {
|
|
703
739
|
log.warn(`Sequencer created but not started`);
|
|
704
740
|
}
|
|
741
|
+
// Create prover node subsystem if enabled
|
|
742
|
+
let proverNode;
|
|
743
|
+
if (config.enableProverNode) {
|
|
744
|
+
proverNode = await createProverNode(config, {
|
|
745
|
+
...deps.proverNodeDeps,
|
|
746
|
+
telemetry,
|
|
747
|
+
dateProvider,
|
|
748
|
+
archiver,
|
|
749
|
+
worldStateSynchronizer,
|
|
750
|
+
p2pClient,
|
|
751
|
+
epochCache,
|
|
752
|
+
blobClient,
|
|
753
|
+
keyStoreManager
|
|
754
|
+
});
|
|
755
|
+
if (!options.dontStartProverNode) {
|
|
756
|
+
await proverNode.start();
|
|
757
|
+
log.info(`Prover node subsystem started`);
|
|
758
|
+
} else {
|
|
759
|
+
log.info(`Prover node subsystem created but not started`);
|
|
760
|
+
}
|
|
761
|
+
}
|
|
705
762
|
const globalVariableBuilder = new GlobalVariableBuilder({
|
|
706
763
|
...config,
|
|
707
764
|
rollupVersion: BigInt(config.rollupVersion),
|
|
708
765
|
l1GenesisTime,
|
|
709
766
|
slotDuration: Number(slotDuration)
|
|
710
767
|
});
|
|
711
|
-
|
|
768
|
+
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);
|
|
769
|
+
return node;
|
|
712
770
|
}
|
|
713
771
|
/**
|
|
714
772
|
* Returns the sequencer client instance.
|
|
@@ -716,6 +774,9 @@ _dec = trackSpan('AztecNodeService.simulatePublicCalls', (tx)=>({
|
|
|
716
774
|
*/ getSequencer() {
|
|
717
775
|
return this.sequencer;
|
|
718
776
|
}
|
|
777
|
+
/** Returns the prover node subsystem, if enabled. */ getProverNode() {
|
|
778
|
+
return this.proverNode;
|
|
779
|
+
}
|
|
719
780
|
getBlockSource() {
|
|
720
781
|
return this.blockSource;
|
|
721
782
|
}
|
|
@@ -758,7 +819,8 @@ _dec = trackSpan('AztecNodeService.simulatePublicCalls', (tx)=>({
|
|
|
758
819
|
rollupVersion,
|
|
759
820
|
enr,
|
|
760
821
|
l1ContractAddresses: contractAddresses,
|
|
761
|
-
protocolContractAddresses: protocolContractAddresses
|
|
822
|
+
protocolContractAddresses: protocolContractAddresses,
|
|
823
|
+
realProofs: !!this.config.realProofs
|
|
762
824
|
};
|
|
763
825
|
return nodeInfo;
|
|
764
826
|
}
|
|
@@ -932,18 +994,20 @@ _dec = trackSpan('AztecNodeService.simulatePublicCalls', (tx)=>({
|
|
|
932
994
|
const isKnownToPool = txPoolStatus === 'pending' || txPoolStatus === 'mined';
|
|
933
995
|
// Then get the actual tx from the archiver, which tracks every tx in a mined block.
|
|
934
996
|
const settledTxReceipt = await this.blockSource.getSettledTxReceipt(txHash);
|
|
997
|
+
let receipt;
|
|
935
998
|
if (settledTxReceipt) {
|
|
936
|
-
|
|
937
|
-
return settledTxReceipt;
|
|
999
|
+
receipt = settledTxReceipt;
|
|
938
1000
|
} else if (isKnownToPool) {
|
|
939
1001
|
// If the tx is in the pool but not in the archiver, it's pending.
|
|
940
1002
|
// This handles race conditions between archiver and p2p, where the archiver
|
|
941
1003
|
// has pruned the block in which a tx was mined, but p2p has not caught up yet.
|
|
942
|
-
|
|
1004
|
+
receipt = new TxReceipt(txHash, TxStatus.PENDING, undefined, undefined);
|
|
943
1005
|
} else {
|
|
944
1006
|
// Otherwise, if we don't know the tx, we consider it dropped.
|
|
945
|
-
|
|
1007
|
+
receipt = new TxReceipt(txHash, TxStatus.DROPPED, undefined, 'Tx dropped by P2P node');
|
|
946
1008
|
}
|
|
1009
|
+
this.debugLogStore.decorateReceiptWithLogs(txHash.toString(), receipt);
|
|
1010
|
+
return receipt;
|
|
947
1011
|
}
|
|
948
1012
|
getTxEffect(txHash) {
|
|
949
1013
|
return this.blockSource.getTxEffect(txHash);
|
|
@@ -957,6 +1021,7 @@ _dec = trackSpan('AztecNodeService.simulatePublicCalls', (tx)=>({
|
|
|
957
1021
|
await tryStop(this.slasherClient);
|
|
958
1022
|
await tryStop(this.proofVerifier);
|
|
959
1023
|
await tryStop(this.sequencer);
|
|
1024
|
+
await tryStop(this.proverNode);
|
|
960
1025
|
await tryStop(this.p2pClient);
|
|
961
1026
|
await tryStop(this.worldStateSynchronizer);
|
|
962
1027
|
await tryStop(this.blockSource);
|
|
@@ -1194,6 +1259,12 @@ _dec = trackSpan('AztecNodeService.simulatePublicCalls', (tx)=>({
|
|
|
1194
1259
|
*/ async getBlockHeaderByArchive(archive) {
|
|
1195
1260
|
return await this.blockSource.getBlockHeaderByArchive(archive);
|
|
1196
1261
|
}
|
|
1262
|
+
getBlockData(number) {
|
|
1263
|
+
return this.blockSource.getBlockData(number);
|
|
1264
|
+
}
|
|
1265
|
+
getBlockDataByArchive(archive) {
|
|
1266
|
+
return this.blockSource.getBlockDataByArchive(archive);
|
|
1267
|
+
}
|
|
1197
1268
|
/**
|
|
1198
1269
|
* Simulates the public part of a transaction with the current state.
|
|
1199
1270
|
* @param tx - The transaction to simulate.
|
|
@@ -1206,7 +1277,8 @@ _dec = trackSpan('AztecNodeService.simulatePublicCalls', (tx)=>({
|
|
|
1206
1277
|
throw new BadRequestError(`Transaction total gas limit ${txGasLimit + teardownGasLimit} (${txGasLimit} + ${teardownGasLimit}) exceeds maximum gas limit ${this.config.rpcSimulatePublicMaxGasLimit} for simulation`);
|
|
1207
1278
|
}
|
|
1208
1279
|
const txHash = tx.getTxHash();
|
|
1209
|
-
const
|
|
1280
|
+
const latestBlockNumber = await this.blockSource.getBlockNumber();
|
|
1281
|
+
const blockNumber = BlockNumber.add(latestBlockNumber, 1);
|
|
1210
1282
|
// If sequencer is not initialized, we just set these values to zero for simulation.
|
|
1211
1283
|
const coinbase = EthAddress.ZERO;
|
|
1212
1284
|
const feeRecipient = AztecAddress.ZERO;
|
|
@@ -1217,6 +1289,8 @@ _dec = trackSpan('AztecNodeService.simulatePublicCalls', (tx)=>({
|
|
|
1217
1289
|
txHash,
|
|
1218
1290
|
blockNumber
|
|
1219
1291
|
});
|
|
1292
|
+
// Ensure world-state has caught up with the latest block we loaded from the archiver
|
|
1293
|
+
await this.worldStateSynchronizer.syncImmediate(latestBlockNumber);
|
|
1220
1294
|
const merkleTreeFork = await this.worldStateSynchronizer.fork();
|
|
1221
1295
|
try {
|
|
1222
1296
|
const config = PublicSimulatorConfig.from({
|
|
@@ -1231,7 +1305,7 @@ _dec = trackSpan('AztecNodeService.simulatePublicCalls', (tx)=>({
|
|
|
1231
1305
|
});
|
|
1232
1306
|
const processor = publicProcessorFactory.create(merkleTreeFork, newGlobalVariables, config);
|
|
1233
1307
|
// REFACTOR: Consider merging ProcessReturnValues into ProcessedTx
|
|
1234
|
-
const [processedTxs, failedTxs, _usedTxs, returns] = await processor.process([
|
|
1308
|
+
const [processedTxs, failedTxs, _usedTxs, returns, _blobFields, debugLogs] = await processor.process([
|
|
1235
1309
|
tx
|
|
1236
1310
|
]);
|
|
1237
1311
|
// REFACTOR: Consider returning the error rather than throwing
|
|
@@ -1242,7 +1316,7 @@ _dec = trackSpan('AztecNodeService.simulatePublicCalls', (tx)=>({
|
|
|
1242
1316
|
throw failedTxs[0].error;
|
|
1243
1317
|
}
|
|
1244
1318
|
const [processedTx] = processedTxs;
|
|
1245
|
-
return new PublicSimulationOutput(processedTx.revertReason, processedTx.globalVariables, processedTx.txEffect, returns, processedTx.gasUsed);
|
|
1319
|
+
return new PublicSimulationOutput(processedTx.revertReason, processedTx.globalVariables, processedTx.txEffect, returns, processedTx.gasUsed, debugLogs);
|
|
1246
1320
|
} finally{
|
|
1247
1321
|
await merkleTreeFork.close();
|
|
1248
1322
|
}
|
|
@@ -1250,10 +1324,10 @@ _dec = trackSpan('AztecNodeService.simulatePublicCalls', (tx)=>({
|
|
|
1250
1324
|
async isValidTx(tx, { isSimulation, skipFeeEnforcement } = {}) {
|
|
1251
1325
|
const db = this.worldStateSynchronizer.getCommitted();
|
|
1252
1326
|
const verifier = isSimulation ? undefined : this.proofVerifier;
|
|
1253
|
-
// We accept transactions if they are not expired by the next slot (checked based on the
|
|
1327
|
+
// We accept transactions if they are not expired by the next slot (checked based on the ExpirationTimestamp field)
|
|
1254
1328
|
const { ts: nextSlotTimestamp } = this.epochCache.getEpochAndSlotInNextL1Slot();
|
|
1255
1329
|
const blockNumber = BlockNumber(await this.blockSource.getBlockNumber() + 1);
|
|
1256
|
-
const validator =
|
|
1330
|
+
const validator = createTxValidatorForAcceptingTxsOverRPC(db, this.contractDataSource, verifier, {
|
|
1257
1331
|
timestamp: nextSlotTimestamp,
|
|
1258
1332
|
blockNumber,
|
|
1259
1333
|
l1ChainId: this.l1ChainId,
|
|
@@ -1404,6 +1478,73 @@ _dec = trackSpan('AztecNodeService.simulatePublicCalls', (tx)=>({
|
|
|
1404
1478
|
return this.slasherClient.gatherOffensesForRound(round === 'current' ? undefined : BigInt(round));
|
|
1405
1479
|
}
|
|
1406
1480
|
}
|
|
1481
|
+
async reloadKeystore() {
|
|
1482
|
+
if (!this.config.keyStoreDirectory?.length) {
|
|
1483
|
+
throw new BadRequestError('Cannot reload keystore: node is not using a file-based keystore. ' + 'Set KEY_STORE_DIRECTORY to use file-based keystores.');
|
|
1484
|
+
}
|
|
1485
|
+
if (!this.validatorClient) {
|
|
1486
|
+
throw new BadRequestError('Cannot reload keystore: validator is not enabled.');
|
|
1487
|
+
}
|
|
1488
|
+
this.log.info('Reloading keystore from disk');
|
|
1489
|
+
// Re-read and validate keystore files
|
|
1490
|
+
const keyStores = loadKeystores(this.config.keyStoreDirectory);
|
|
1491
|
+
const newManager = new KeystoreManager(mergeKeystores(keyStores));
|
|
1492
|
+
await newManager.validateSigners();
|
|
1493
|
+
ValidatorClient.validateKeyStoreConfiguration(newManager, this.log);
|
|
1494
|
+
// Validate that every validator's publisher keys overlap with the L1 signers
|
|
1495
|
+
// that were initialized at startup. Publishers cannot be hot-reloaded, so a
|
|
1496
|
+
// validator with a publisher key that doesn't match any existing L1 signer
|
|
1497
|
+
// would silently fail on every proposer slot.
|
|
1498
|
+
if (this.keyStoreManager && this.sequencer) {
|
|
1499
|
+
const oldAdapter = NodeKeystoreAdapter.fromKeyStoreManager(this.keyStoreManager);
|
|
1500
|
+
const availablePublishers = new Set(oldAdapter.getAttesterAddresses().flatMap((a)=>oldAdapter.getPublisherAddresses(a).map((p)=>p.toString().toLowerCase())));
|
|
1501
|
+
const newAdapter = NodeKeystoreAdapter.fromKeyStoreManager(newManager);
|
|
1502
|
+
for (const attester of newAdapter.getAttesterAddresses()){
|
|
1503
|
+
const pubs = newAdapter.getPublisherAddresses(attester);
|
|
1504
|
+
if (pubs.length > 0 && !pubs.some((p)=>availablePublishers.has(p.toString().toLowerCase()))) {
|
|
1505
|
+
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 ` + `[${[
|
|
1506
|
+
...availablePublishers
|
|
1507
|
+
].join(', ')}]. Publishers cannot be hot-reloaded — ` + `use an existing publisher key or restart the node.`);
|
|
1508
|
+
}
|
|
1509
|
+
}
|
|
1510
|
+
}
|
|
1511
|
+
// Build adapters for old and new keystores to compute diff
|
|
1512
|
+
const newAdapter = NodeKeystoreAdapter.fromKeyStoreManager(newManager);
|
|
1513
|
+
const newAddresses = newAdapter.getAttesterAddresses();
|
|
1514
|
+
const oldAddresses = this.keyStoreManager ? NodeKeystoreAdapter.fromKeyStoreManager(this.keyStoreManager).getAttesterAddresses() : [];
|
|
1515
|
+
const oldSet = new Set(oldAddresses.map((a)=>a.toString()));
|
|
1516
|
+
const newSet = new Set(newAddresses.map((a)=>a.toString()));
|
|
1517
|
+
const added = newAddresses.filter((a)=>!oldSet.has(a.toString()));
|
|
1518
|
+
const removed = oldAddresses.filter((a)=>!newSet.has(a.toString()));
|
|
1519
|
+
if (added.length > 0) {
|
|
1520
|
+
this.log.info(`Keystore reload: adding attester keys: ${added.map((a)=>a.toString()).join(', ')}`);
|
|
1521
|
+
}
|
|
1522
|
+
if (removed.length > 0) {
|
|
1523
|
+
this.log.info(`Keystore reload: removing attester keys: ${removed.map((a)=>a.toString()).join(', ')}`);
|
|
1524
|
+
}
|
|
1525
|
+
if (added.length === 0 && removed.length === 0) {
|
|
1526
|
+
this.log.info('Keystore reload: attester keys unchanged');
|
|
1527
|
+
}
|
|
1528
|
+
// Update the validator client (coinbase, feeRecipient, attester keys)
|
|
1529
|
+
this.validatorClient.reloadKeystore(newManager);
|
|
1530
|
+
// Update the publisher factory's keystore so newly-added validators
|
|
1531
|
+
// can be matched to existing publisher keys when proposing blocks.
|
|
1532
|
+
if (this.sequencer) {
|
|
1533
|
+
this.sequencer.updatePublisherNodeKeyStore(newAdapter);
|
|
1534
|
+
}
|
|
1535
|
+
// Update slasher's "don't-slash-self" list with new validator addresses
|
|
1536
|
+
if (this.slasherClient && !this.config.slashSelfAllowed) {
|
|
1537
|
+
const slashValidatorsNever = unique([
|
|
1538
|
+
...this.config.slashValidatorsNever ?? [],
|
|
1539
|
+
...newAddresses
|
|
1540
|
+
].map((a)=>a.toString())).map(EthAddress.fromString);
|
|
1541
|
+
this.slasherClient.updateConfig({
|
|
1542
|
+
slashValidatorsNever
|
|
1543
|
+
});
|
|
1544
|
+
}
|
|
1545
|
+
this.keyStoreManager = newManager;
|
|
1546
|
+
this.log.info('Keystore reloaded: coinbase, feeRecipient, and attester keys updated');
|
|
1547
|
+
}
|
|
1407
1548
|
#getInitialHeaderHash() {
|
|
1408
1549
|
if (!this.initialHeaderHashPromise) {
|
|
1409
1550
|
this.initialHeaderHashPromise = this.worldStateSynchronizer.getCommitted().getInitialHeader().hash();
|
|
@@ -77,7 +77,7 @@ export declare class Sentinel extends Sentinel_base implements L2BlockStreamEven
|
|
|
77
77
|
/** Computes stats for a single validator. */
|
|
78
78
|
getValidatorStats(validatorAddress: EthAddress, fromSlot?: SlotNumber, toSlot?: SlotNumber): Promise<SingleValidatorStats | undefined>;
|
|
79
79
|
protected computeStatsForValidator(address: `0x${string}`, allHistory: ValidatorStatusHistory, fromSlot?: SlotNumber, toSlot?: SlotNumber): ValidatorStats;
|
|
80
|
-
protected computeMissed(history: ValidatorStatusHistory,
|
|
80
|
+
protected computeMissed(history: ValidatorStatusHistory, computeOverCategory: ValidatorStatusType | undefined, filter: ValidatorStatusInSlot[]): {
|
|
81
81
|
currentStreak: number;
|
|
82
82
|
rate: number | undefined;
|
|
83
83
|
count: number;
|
|
@@ -90,4 +90,4 @@ export declare class Sentinel extends Sentinel_base implements L2BlockStreamEven
|
|
|
90
90
|
} | undefined;
|
|
91
91
|
}
|
|
92
92
|
export {};
|
|
93
|
-
//# sourceMappingURL=data:application/json;base64,
|
|
93
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoic2VudGluZWwuZC50cyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uL3NyYy9zZW50aW5lbC9zZW50aW5lbC50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxPQUFPLEtBQUssRUFBRSxVQUFVLEVBQUUsTUFBTSxvQkFBb0IsQ0FBQztBQUNyRCxPQUFPLEVBQWUsZ0JBQWdCLEVBQUUsV0FBVyxFQUFFLFVBQVUsRUFBRSxNQUFNLGlDQUFpQyxDQUFDO0FBRXpHLE9BQU8sRUFBRSxVQUFVLEVBQUUsTUFBTSwrQkFBK0IsQ0FBQztBQUUzRCxPQUFPLEVBQUUsY0FBYyxFQUFFLE1BQU0sbUNBQW1DLENBQUM7QUFDbkUsT0FBTyxFQUFxQixLQUFLLFdBQVcsRUFBRSxNQUFNLHdCQUF3QixDQUFDO0FBQzdFLE9BQU8sS0FBSyxFQUFFLFNBQVMsRUFBRSxNQUFNLFlBQVksQ0FBQztBQUM1QyxPQUFPLEVBSUwsS0FBSyxPQUFPLEVBQ1osS0FBSyxjQUFjLEVBQ3BCLE1BQU0sZ0JBQWdCLENBQUM7QUFDeEIsT0FBTyxLQUFLLEVBQUUsYUFBYSxFQUFFLE1BQU0sdUJBQXVCLENBQUM7QUFDM0QsT0FBTyxFQUNMLEtBQUssYUFBYSxFQUNsQixhQUFhLEVBQ2IsS0FBSyxrQkFBa0IsRUFDdkIsS0FBSyx5QkFBeUIsRUFFL0IsTUFBTSxxQkFBcUIsQ0FBQztBQUU3QixPQUFPLEtBQUssRUFDVixvQkFBb0IsRUFDcEIsY0FBYyxFQUNkLHNCQUFzQixFQUN0QixxQkFBcUIsRUFDckIsbUJBQW1CLEVBQ25CLDBCQUEwQixFQUMxQixlQUFlLEVBQ2hCLE1BQU0sMEJBQTBCLENBQUM7QUFJbEMsT0FBTyxFQUFFLGFBQWEsRUFBRSxNQUFNLFlBQVksQ0FBQzs7QUFhM0MscUJBQWEsUUFBUyxTQUFRLGFBQTJDLFlBQVcseUJBQXlCLEVBQUUsT0FBTztJQWNsSCxTQUFTLENBQUMsVUFBVSxFQUFFLFVBQVU7SUFDaEMsU0FBUyxDQUFDLFFBQVEsRUFBRSxhQUFhO0lB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
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sentinel.d.ts","sourceRoot":"","sources":["../../src/sentinel/sentinel.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAC;AACrD,OAAO,EAAe,gBAAgB,EAAE,WAAW,EAAE,UAAU,EAAE,MAAM,iCAAiC,CAAC;AAEzG,OAAO,EAAE,UAAU,EAAE,MAAM,+BAA+B,CAAC;AAE3D,OAAO,EAAE,cAAc,EAAE,MAAM,mCAAmC,CAAC;AACnE,OAAO,EAAqB,KAAK,WAAW,EAAE,MAAM,wBAAwB,CAAC;AAC7E,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAC5C,OAAO,EAIL,KAAK,OAAO,EACZ,KAAK,cAAc,EACpB,MAAM,gBAAgB,CAAC;AACxB,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AAC3D,OAAO,EACL,KAAK,aAAa,EAClB,aAAa,EACb,KAAK,kBAAkB,EACvB,KAAK,yBAAyB,EAE/B,MAAM,qBAAqB,CAAC;AAE7B,OAAO,KAAK,EACV,oBAAoB,EACpB,cAAc,EACd,sBAAsB,EACtB,qBAAqB,EACrB,mBAAmB,EACnB,0BAA0B,EAC1B,eAAe,EAChB,MAAM,0BAA0B,CAAC;AAIlC,OAAO,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;;
|
|
1
|
+
{"version":3,"file":"sentinel.d.ts","sourceRoot":"","sources":["../../src/sentinel/sentinel.ts"],"names":[],"mappings":"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"}
|
|
@@ -8,6 +8,15 @@ import { OffenseType, WANT_TO_SLASH_EVENT } from '@aztec/slasher';
|
|
|
8
8
|
import { L2BlockStream, getAttestationInfoFromPublishedCheckpoint } from '@aztec/stdlib/block';
|
|
9
9
|
import { getEpochAtSlot, getSlotRangeForEpoch, getTimestampForSlot } from '@aztec/stdlib/epoch-helpers';
|
|
10
10
|
import EventEmitter from 'node:events';
|
|
11
|
+
/** Maps a validator status to its category: proposer or attestation. */ function statusToCategory(status) {
|
|
12
|
+
switch(status){
|
|
13
|
+
case 'attestation-sent':
|
|
14
|
+
case 'attestation-missed':
|
|
15
|
+
return 'attestation';
|
|
16
|
+
default:
|
|
17
|
+
return 'proposer';
|
|
18
|
+
}
|
|
19
|
+
}
|
|
11
20
|
export class Sentinel extends EventEmitter {
|
|
12
21
|
epochCache;
|
|
13
22
|
archiver;
|
|
@@ -82,16 +91,14 @@ export class Sentinel extends EventEmitter {
|
|
|
82
91
|
return;
|
|
83
92
|
}
|
|
84
93
|
const blockNumber = event.block.number;
|
|
85
|
-
const
|
|
86
|
-
if (!
|
|
87
|
-
this.logger.error(`Failed to get block ${blockNumber}
|
|
88
|
-
block
|
|
89
|
-
});
|
|
94
|
+
const header = await this.archiver.getBlockHeader(blockNumber);
|
|
95
|
+
if (!header) {
|
|
96
|
+
this.logger.error(`Failed to get block header ${blockNumber}`);
|
|
90
97
|
return;
|
|
91
98
|
}
|
|
92
99
|
// TODO(palla/slash): We should only be computing proven performance if this is
|
|
93
100
|
// a full proof epoch and not a partial one, otherwise we'll end up with skewed stats.
|
|
94
|
-
const epoch = getEpochAtSlot(
|
|
101
|
+
const epoch = getEpochAtSlot(header.getSlot(), this.epochCache.getL1Constants());
|
|
95
102
|
this.logger.debug(`Computing proven performance for epoch ${epoch}`);
|
|
96
103
|
const performance = await this.computeProvenPerformance(epoch);
|
|
97
104
|
this.logger.info(`Computed proven performance for epoch ${epoch}`, performance);
|
|
@@ -100,7 +107,11 @@ export class Sentinel extends EventEmitter {
|
|
|
100
107
|
}
|
|
101
108
|
async computeProvenPerformance(epoch) {
|
|
102
109
|
const [fromSlot, toSlot] = getSlotRangeForEpoch(epoch, this.epochCache.getL1Constants());
|
|
103
|
-
const { committee } = await this.epochCache.getCommittee(fromSlot);
|
|
110
|
+
const { committee, isEscapeHatchOpen } = await this.epochCache.getCommittee(fromSlot);
|
|
111
|
+
if (isEscapeHatchOpen) {
|
|
112
|
+
this.logger.info(`Skipping proven performance for epoch ${epoch} - escape hatch is open`);
|
|
113
|
+
return {};
|
|
114
|
+
}
|
|
104
115
|
if (!committee) {
|
|
105
116
|
this.logger.trace(`No committee found for slot ${fromSlot}`);
|
|
106
117
|
return {};
|
|
@@ -242,7 +253,12 @@ export class Sentinel extends EventEmitter {
|
|
|
242
253
|
* Gathers committee and proposer data for a given slot, computes slot stats,
|
|
243
254
|
* and updates overall stats.
|
|
244
255
|
*/ async processSlot(slot) {
|
|
245
|
-
const { epoch, seed, committee } = await this.epochCache.getCommittee(slot);
|
|
256
|
+
const { epoch, seed, committee, isEscapeHatchOpen } = await this.epochCache.getCommittee(slot);
|
|
257
|
+
if (isEscapeHatchOpen) {
|
|
258
|
+
this.logger.info(`Skipping slot ${slot} at epoch ${epoch} - escape hatch is open`);
|
|
259
|
+
this.lastProcessedSlot = slot;
|
|
260
|
+
return;
|
|
261
|
+
}
|
|
246
262
|
if (!committee || committee.length === 0) {
|
|
247
263
|
this.logger.trace(`No committee found for slot ${slot} at epoch ${epoch}`);
|
|
248
264
|
this.lastProcessedSlot = slot;
|
|
@@ -263,17 +279,17 @@ export class Sentinel extends EventEmitter {
|
|
|
263
279
|
committee
|
|
264
280
|
});
|
|
265
281
|
// Check if there is an L2 block in L1 for this L2 slot
|
|
266
|
-
// Here we get all attestations for the
|
|
267
|
-
// or all attestations for all proposals in the slot if no
|
|
282
|
+
// Here we get all checkpoint attestations for the checkpoint at the given slot,
|
|
283
|
+
// or all checkpoint attestations for all proposals in the slot if no checkpoint was mined.
|
|
268
284
|
// We gather from both p2p (contains the ones seen on the p2p layer) and archiver
|
|
269
|
-
// (contains the ones synced from mined
|
|
270
|
-
const
|
|
271
|
-
const p2pAttested = await this.p2p.getCheckpointAttestationsForSlot(slot,
|
|
285
|
+
// (contains the ones synced from mined checkpoints, which we may have missed from p2p).
|
|
286
|
+
const checkpoint = this.slotNumberToCheckpoint.get(slot);
|
|
287
|
+
const p2pAttested = await this.p2p.getCheckpointAttestationsForSlot(slot, checkpoint?.archive);
|
|
272
288
|
// Filter out attestations with invalid signatures
|
|
273
289
|
const p2pAttestors = p2pAttested.map((a)=>a.getSender()).filter((s)=>s !== undefined);
|
|
274
290
|
const attestors = new Set([
|
|
275
291
|
...p2pAttestors.map((a)=>a.toString()),
|
|
276
|
-
...
|
|
292
|
+
...checkpoint?.attestors.map((a)=>a.toString()) ?? []
|
|
277
293
|
].filter((addr)=>proposer.toString() !== addr));
|
|
278
294
|
// We assume that there was a block proposal if at least one of the validators (other than the proposer) attested to it.
|
|
279
295
|
// It could be the case that every single validator failed, and we could differentiate it by having
|
|
@@ -281,17 +297,26 @@ export class Sentinel extends EventEmitter {
|
|
|
281
297
|
// But we'll leave that corner case out to reduce pressure on the node.
|
|
282
298
|
// TODO(palla/slash): This breaks if a given node has more than one validator in the current committee,
|
|
283
299
|
// since they will attest to their own proposal it even if it's not re-executable.
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
300
|
+
let status;
|
|
301
|
+
if (checkpoint) {
|
|
302
|
+
status = 'checkpoint-mined';
|
|
303
|
+
} else if (attestors.size > 0) {
|
|
304
|
+
status = 'checkpoint-proposed';
|
|
305
|
+
} else {
|
|
306
|
+
// No checkpoint on L1 and no checkpoint attestations seen. Check if block proposals were sent for this slot.
|
|
307
|
+
const hasBlockProposals = await this.p2p.hasBlockProposalsForSlot(slot);
|
|
308
|
+
status = hasBlockProposals ? 'checkpoint-missed' : 'blocks-missed';
|
|
309
|
+
}
|
|
310
|
+
this.logger.debug(`Checkpoint status for slot ${slot}: ${status}`, {
|
|
311
|
+
...checkpoint,
|
|
287
312
|
slot
|
|
288
313
|
});
|
|
289
|
-
// Get attestors that failed their
|
|
290
|
-
const missedAttestors = new Set(
|
|
314
|
+
// Get attestors that failed their checkpoint attestation duties, but only if there was a checkpoint proposed or mined
|
|
315
|
+
const missedAttestors = new Set(status === 'blocks-missed' || status === 'checkpoint-missed' ? [] : committee.filter((v)=>!attestors.has(v.toString()) && !proposer.equals(v)).map((v)=>v.toString()));
|
|
291
316
|
this.logger.debug(`Retrieved ${attestors.size} attestors out of ${committee.length} for slot ${slot}`, {
|
|
292
|
-
|
|
317
|
+
status,
|
|
293
318
|
proposer: proposer.toString(),
|
|
294
|
-
...
|
|
319
|
+
...checkpoint,
|
|
295
320
|
slot,
|
|
296
321
|
attestors: [
|
|
297
322
|
...attestors
|
|
@@ -304,7 +329,7 @@ export class Sentinel extends EventEmitter {
|
|
|
304
329
|
// Compute the status for each validator in the committee
|
|
305
330
|
const statusFor = (who)=>{
|
|
306
331
|
if (who === proposer.toString()) {
|
|
307
|
-
return
|
|
332
|
+
return status;
|
|
308
333
|
} else if (attestors.has(who)) {
|
|
309
334
|
return 'attestation-sent';
|
|
310
335
|
} else if (missedAttestors.has(who)) {
|
|
@@ -361,15 +386,16 @@ export class Sentinel extends EventEmitter {
|
|
|
361
386
|
computeStatsForValidator(address, allHistory, fromSlot, toSlot) {
|
|
362
387
|
let history = fromSlot ? allHistory.filter((h)=>BigInt(h.slot) >= fromSlot) : allHistory;
|
|
363
388
|
history = toSlot ? history.filter((h)=>BigInt(h.slot) <= toSlot) : history;
|
|
364
|
-
const lastProposal = history.filter((h)=>h.status === '
|
|
389
|
+
const lastProposal = history.filter((h)=>h.status === 'checkpoint-proposed' || h.status === 'checkpoint-mined').at(-1);
|
|
365
390
|
const lastAttestation = history.filter((h)=>h.status === 'attestation-sent').at(-1);
|
|
366
391
|
return {
|
|
367
392
|
address: EthAddress.fromString(address),
|
|
368
393
|
lastProposal: this.computeFromSlot(lastProposal?.slot),
|
|
369
394
|
lastAttestation: this.computeFromSlot(lastAttestation?.slot),
|
|
370
395
|
totalSlots: history.length,
|
|
371
|
-
missedProposals: this.computeMissed(history, '
|
|
372
|
-
'
|
|
396
|
+
missedProposals: this.computeMissed(history, 'proposer', [
|
|
397
|
+
'checkpoint-missed',
|
|
398
|
+
'blocks-missed'
|
|
373
399
|
]),
|
|
374
400
|
missedAttestations: this.computeMissed(history, 'attestation', [
|
|
375
401
|
'attestation-missed'
|
|
@@ -377,8 +403,8 @@ export class Sentinel extends EventEmitter {
|
|
|
377
403
|
history
|
|
378
404
|
};
|
|
379
405
|
}
|
|
380
|
-
computeMissed(history,
|
|
381
|
-
const relevantHistory = history.filter((h)=>!
|
|
406
|
+
computeMissed(history, computeOverCategory, filter) {
|
|
407
|
+
const relevantHistory = history.filter((h)=>!computeOverCategory || statusToCategory(h.status) === computeOverCategory);
|
|
382
408
|
const filteredHistory = relevantHistory.filter((h)=>filter.includes(h.status));
|
|
383
409
|
return {
|
|
384
410
|
currentStreak: countWhile([
|