@aztec/validator-client 0.0.1-commit.e2b2873ed → 0.0.1-commit.e304674f1

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.
Files changed (52) hide show
  1. package/README.md +41 -2
  2. package/dest/checkpoint_builder.d.ts +21 -8
  3. package/dest/checkpoint_builder.d.ts.map +1 -1
  4. package/dest/checkpoint_builder.js +124 -46
  5. package/dest/config.d.ts +1 -1
  6. package/dest/config.d.ts.map +1 -1
  7. package/dest/config.js +22 -6
  8. package/dest/duties/validation_service.d.ts +3 -4
  9. package/dest/duties/validation_service.d.ts.map +1 -1
  10. package/dest/duties/validation_service.js +11 -22
  11. package/dest/factory.d.ts +7 -4
  12. package/dest/factory.d.ts.map +1 -1
  13. package/dest/factory.js +6 -5
  14. package/dest/index.d.ts +2 -3
  15. package/dest/index.d.ts.map +1 -1
  16. package/dest/index.js +1 -2
  17. package/dest/key_store/ha_key_store.js +1 -1
  18. package/dest/metrics.d.ts +10 -2
  19. package/dest/metrics.d.ts.map +1 -1
  20. package/dest/metrics.js +12 -0
  21. package/dest/proposal_handler.d.ts +107 -0
  22. package/dest/proposal_handler.d.ts.map +1 -0
  23. package/dest/proposal_handler.js +966 -0
  24. package/dest/validator.d.ts +18 -15
  25. package/dest/validator.d.ts.map +1 -1
  26. package/dest/validator.js +80 -191
  27. package/package.json +19 -19
  28. package/src/checkpoint_builder.ts +142 -39
  29. package/src/config.ts +22 -6
  30. package/src/duties/validation_service.ts +18 -24
  31. package/src/factory.ts +9 -3
  32. package/src/index.ts +1 -2
  33. package/src/key_store/ha_key_store.ts +1 -1
  34. package/src/metrics.ts +19 -1
  35. package/src/proposal_handler.ts +1033 -0
  36. package/src/validator.ts +103 -210
  37. package/dest/block_proposal_handler.d.ts +0 -63
  38. package/dest/block_proposal_handler.d.ts.map +0 -1
  39. package/dest/block_proposal_handler.js +0 -546
  40. package/dest/tx_validator/index.d.ts +0 -3
  41. package/dest/tx_validator/index.d.ts.map +0 -1
  42. package/dest/tx_validator/index.js +0 -2
  43. package/dest/tx_validator/nullifier_cache.d.ts +0 -14
  44. package/dest/tx_validator/nullifier_cache.d.ts.map +0 -1
  45. package/dest/tx_validator/nullifier_cache.js +0 -24
  46. package/dest/tx_validator/tx_validator_factory.d.ts +0 -19
  47. package/dest/tx_validator/tx_validator_factory.d.ts.map +0 -1
  48. package/dest/tx_validator/tx_validator_factory.js +0 -54
  49. package/src/block_proposal_handler.ts +0 -555
  50. package/src/tx_validator/index.ts +0 -2
  51. package/src/tx_validator/nullifier_cache.ts +0 -30
  52. package/src/tx_validator/tx_validator_factory.ts +0 -154
@@ -4,24 +4,25 @@ import { BlockNumber, CheckpointNumber, IndexWithinCheckpoint, SlotNumber } from
4
4
  import { Fr } from '@aztec/foundation/curves/bn254';
5
5
  import type { EthAddress } from '@aztec/foundation/eth-address';
6
6
  import type { Signature } from '@aztec/foundation/eth-signature';
7
- import { type Logger } from '@aztec/foundation/log';
7
+ import { type LogData, type Logger } from '@aztec/foundation/log';
8
8
  import { DateProvider } from '@aztec/foundation/timer';
9
9
  import type { KeystoreManager } from '@aztec/node-keystore';
10
10
  import type { P2P, PeerId } from '@aztec/p2p';
11
11
  import { type Watcher, type WatcherEmitter } from '@aztec/slasher';
12
12
  import type { AztecAddress } from '@aztec/stdlib/aztec-address';
13
13
  import type { CommitteeAttestationsAndSigners, L2BlockSink, L2BlockSource } from '@aztec/stdlib/block';
14
- import type { CreateCheckpointProposalLastBlockData, ITxProvider, Validator, ValidatorClientFullConfig, WorldStateSynchronizer } from '@aztec/stdlib/interfaces/server';
15
- import { type L1ToL2MessageSource } from '@aztec/stdlib/messaging';
14
+ import type { ITxProvider, Validator, ValidatorClientFullConfig, WorldStateSynchronizer } from '@aztec/stdlib/interfaces/server';
15
+ import type { L1ToL2MessageSource } from '@aztec/stdlib/messaging';
16
16
  import { type BlockProposal, type BlockProposalOptions, type CheckpointAttestation, CheckpointProposal, type CheckpointProposalCore, type CheckpointProposalOptions } from '@aztec/stdlib/p2p';
17
17
  import type { CheckpointHeader } from '@aztec/stdlib/rollup';
18
18
  import type { BlockHeader, Tx } from '@aztec/stdlib/tx';
19
19
  import { type TelemetryClient, type Tracer } from '@aztec/telemetry-client';
20
- import { type SigningContext } from '@aztec/validator-ha-signer/types';
20
+ import { type SigningContext, type SlashingProtectionDatabase } from '@aztec/validator-ha-signer/types';
21
+ import type { ValidatorHASigner } from '@aztec/validator-ha-signer/validator-ha-signer';
21
22
  import type { TypedDataDefinition } from 'viem';
22
- import { BlockProposalHandler } from './block_proposal_handler.js';
23
23
  import type { FullNodeCheckpointsBuilder } from './checkpoint_builder.js';
24
24
  import type { ExtendedValidatorKeyStore } from './key_store/interface.js';
25
+ import { ProposalHandler } from './proposal_handler.js';
25
26
  declare const ValidatorClient_base: new () => WatcherEmitter;
26
27
  /**
27
28
  * Validator Client
@@ -30,13 +31,14 @@ export declare class ValidatorClient extends ValidatorClient_base implements Val
30
31
  private keyStore;
31
32
  private epochCache;
32
33
  private p2pClient;
33
- private blockProposalHandler;
34
+ private proposalHandler;
34
35
  private blockSource;
35
36
  private checkpointsBuilder;
36
37
  private worldState;
37
38
  private l1ToL2MessageSource;
38
39
  private config;
39
40
  private blobClient;
41
+ private slashingProtectionSigner;
40
42
  private dateProvider;
41
43
  readonly tracer: Tracer;
42
44
  private validationService;
@@ -49,20 +51,23 @@ export declare class ValidatorClient extends ValidatorClient_base implements Val
49
51
  private lastProposedCheckpoint?;
50
52
  private lastEpochForCommitteeUpdateLoop;
51
53
  private epochCacheUpdateLoop;
54
+ /** Tracks the last epoch in which each attester successfully submitted at least one attestation. */
55
+ private lastAttestedEpochByAttester;
52
56
  private proposersOfInvalidBlocks;
53
57
  /** Tracks the last checkpoint proposal we attested to, to prevent equivocation. */
54
58
  private lastAttestedProposal?;
55
- protected constructor(keyStore: ExtendedValidatorKeyStore, epochCache: EpochCache, p2pClient: P2P, blockProposalHandler: BlockProposalHandler, blockSource: L2BlockSource, checkpointsBuilder: FullNodeCheckpointsBuilder, worldState: WorldStateSynchronizer, l1ToL2MessageSource: L1ToL2MessageSource, config: ValidatorClientFullConfig, blobClient: BlobClientInterface, dateProvider?: DateProvider, telemetry?: TelemetryClient, log?: Logger);
59
+ protected constructor(keyStore: ExtendedValidatorKeyStore, epochCache: EpochCache, p2pClient: P2P, proposalHandler: ProposalHandler, blockSource: L2BlockSource, checkpointsBuilder: FullNodeCheckpointsBuilder, worldState: WorldStateSynchronizer, l1ToL2MessageSource: L1ToL2MessageSource, config: ValidatorClientFullConfig, blobClient: BlobClientInterface, slashingProtectionSigner: ValidatorHASigner, dateProvider?: DateProvider, telemetry?: TelemetryClient, log?: Logger);
56
60
  static validateKeyStoreConfiguration(keyStoreManager: KeystoreManager, logger?: Logger): void;
57
61
  private handleEpochCommitteeUpdate;
58
- static new(config: ValidatorClientFullConfig, checkpointsBuilder: FullNodeCheckpointsBuilder, worldState: WorldStateSynchronizer, epochCache: EpochCache, p2pClient: P2P, blockSource: L2BlockSource & L2BlockSink, l1ToL2MessageSource: L1ToL2MessageSource, txProvider: ITxProvider, keyStoreManager: KeystoreManager, blobClient: BlobClientInterface, dateProvider?: DateProvider, telemetry?: TelemetryClient): Promise<ValidatorClient>;
62
+ static new(config: ValidatorClientFullConfig, checkpointsBuilder: FullNodeCheckpointsBuilder, worldState: WorldStateSynchronizer, epochCache: EpochCache, p2pClient: P2P, blockSource: L2BlockSource & L2BlockSink, l1ToL2MessageSource: L1ToL2MessageSource, txProvider: ITxProvider, keyStoreManager: KeystoreManager, blobClient: BlobClientInterface, dateProvider?: DateProvider, telemetry?: TelemetryClient, slashingProtectionDb?: SlashingProtectionDatabase): Promise<ValidatorClient>;
59
63
  getValidatorAddresses(): EthAddress[];
60
- getBlockProposalHandler(): BlockProposalHandler;
64
+ getProposalHandler(): ProposalHandler;
61
65
  signWithAddress(addr: EthAddress, msg: TypedDataDefinition, context: SigningContext): Promise<Signature>;
62
66
  getCoinbaseForAttestor(attestor: EthAddress): EthAddress;
63
67
  getFeeRecipientForAttestor(attestor: EthAddress): AztecAddress;
64
68
  getConfig(): ValidatorClientFullConfig;
65
69
  updateConfig(config: Partial<ValidatorClientFullConfig>): void;
70
+ reloadKeystore(newManager: KeystoreManager): void;
66
71
  start(): Promise<void>;
67
72
  stop(): Promise<void>;
68
73
  /** Register handlers on the p2p client */
@@ -86,12 +91,10 @@ export declare class ValidatorClient extends ValidatorClient_base implements Val
86
91
  */
87
92
  private shouldAttestToSlot;
88
93
  private createCheckpointAttestationsFromProposal;
89
- private validateCheckpointProposal;
90
94
  /**
91
- * Extract checkpoint global variables from a block.
95
+ * Uploads blobs for a checkpoint to the filestore (fire and forget).
92
96
  */
93
- private extractCheckpointConstants;
94
- private uploadBlobsForCheckpoint;
97
+ protected uploadBlobsForCheckpoint(proposal: CheckpointProposalCore, proposalInfo: LogData): Promise<void>;
95
98
  private slashInvalidBlock;
96
99
  /**
97
100
  * Handle detection of a duplicate proposal (equivocation).
@@ -104,7 +107,7 @@ export declare class ValidatorClient extends ValidatorClient_base implements Val
104
107
  */
105
108
  private handleDuplicateAttestation;
106
109
  createBlockProposal(blockHeader: BlockHeader, indexWithinCheckpoint: IndexWithinCheckpoint, inHash: Fr, archive: Fr, txs: Tx[], proposerAddress: EthAddress | undefined, options?: BlockProposalOptions): Promise<BlockProposal>;
107
- createCheckpointProposal(checkpointHeader: CheckpointHeader, archive: Fr, lastBlockInfo: CreateCheckpointProposalLastBlockData | undefined, proposerAddress: EthAddress | undefined, options?: CheckpointProposalOptions): Promise<CheckpointProposal>;
110
+ createCheckpointProposal(checkpointHeader: CheckpointHeader, archive: Fr, feeAssetPriceModifier: bigint, lastBlockProposal: BlockProposal | undefined, proposerAddress: EthAddress | undefined, options?: CheckpointProposalOptions): Promise<CheckpointProposal>;
108
111
  broadcastBlockProposal(proposal: BlockProposal): Promise<void>;
109
112
  signAttestationsAndSigners(attestationsAndSigners: CommitteeAttestationsAndSigners, proposer: EthAddress, slot: SlotNumber, blockNumber: BlockNumber | CheckpointNumber): Promise<Signature>;
110
113
  collectOwnAttestations(proposal: CheckpointProposal): Promise<CheckpointAttestation[]>;
@@ -112,4 +115,4 @@ export declare class ValidatorClient extends ValidatorClient_base implements Val
112
115
  private handleAuthRequest;
113
116
  }
114
117
  export {};
115
- //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoidmFsaWRhdG9yLmQudHMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi9zcmMvdmFsaWRhdG9yLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBLE9BQU8sS0FBSyxFQUFFLG1CQUFtQixFQUFFLE1BQU0sMkJBQTJCLENBQUM7QUFFckUsT0FBTyxLQUFLLEVBQUUsVUFBVSxFQUFFLE1BQU0sb0JBQW9CLENBQUM7QUFDckQsT0FBTyxFQUNMLFdBQVcsRUFDWCxnQkFBZ0IsRUFFaEIscUJBQXFCLEVBQ3JCLFVBQVUsRUFDWCxNQUFNLGlDQUFpQyxDQUFDO0FBQ3pDLE9BQU8sRUFBRSxFQUFFLEVBQUUsTUFBTSxnQ0FBZ0MsQ0FBQztBQUVwRCxPQUFPLEtBQUssRUFBRSxVQUFVLEVBQUUsTUFBTSwrQkFBK0IsQ0FBQztBQUNoRSxPQUFPLEtBQUssRUFBRSxTQUFTLEVBQUUsTUFBTSxpQ0FBaUMsQ0FBQztBQUNqRSxPQUFPLEVBQWdCLEtBQUssTUFBTSxFQUFnQixNQUFNLHVCQUF1QixDQUFDO0FBSWhGLE9BQU8sRUFBRSxZQUFZLEVBQUUsTUFBTSx5QkFBeUIsQ0FBQztBQUN2RCxPQUFPLEtBQUssRUFBRSxlQUFlLEVBQUUsTUFBTSxzQkFBc0IsQ0FBQztBQUM1RCxPQUFPLEtBQUssRUFBbUQsR0FBRyxFQUFFLE1BQU0sRUFBRSxNQUFNLFlBQVksQ0FBQztBQUUvRixPQUFPLEVBQW9DLEtBQUssT0FBTyxFQUFFLEtBQUssY0FBYyxFQUFFLE1BQU0sZ0JBQWdCLENBQUM7QUFDckcsT0FBTyxLQUFLLEVBQUUsWUFBWSxFQUFFLE1BQU0sNkJBQTZCLENBQUM7QUFDaEUsT0FBTyxLQUFLLEVBQUUsK0JBQStCLEVBQVcsV0FBVyxFQUFFLGFBQWEsRUFBRSxNQUFNLHFCQUFxQixDQUFDO0FBRWhILE9BQU8sS0FBSyxFQUNWLHFDQUFxQyxFQUNyQyxXQUFXLEVBQ1gsU0FBUyxFQUNULHlCQUF5QixFQUN6QixzQkFBc0IsRUFDdkIsTUFBTSxpQ0FBaUMsQ0FB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
118
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoidmFsaWRhdG9yLmQudHMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi9zcmMvdmFsaWRhdG9yLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBLE9BQU8sS0FBSyxFQUFFLG1CQUFtQixFQUFFLE1BQU0sMkJBQTJCLENBQUM7QUFFckUsT0FBTyxLQUFLLEVBQUUsVUFBVSxFQUFFLE1BQU0sb0JBQW9CLENBQUM7QUFDckQsT0FBTyxFQUNMLFdBQVcsRUFDWCxnQkFBZ0IsRUFFaEIscUJBQXFCLEVBQ3JCLFVBQVUsRUFDWCxNQUFNLGlDQUFpQyxDQUFDO0FBQ3pDLE9BQU8sRUFBRSxFQUFFLEVBQUUsTUFBTSxnQ0FBZ0MsQ0FBQztBQUNwRCxPQUFPLEtBQUssRUFBRSxVQUFVLEVBQUUsTUFBTSwrQkFBK0IsQ0FBQztBQUNoRSxPQUFPLEtBQUssRUFBRSxTQUFTLEVBQUUsTUFBTSxpQ0FBaUMsQ0FBQztBQUNqRSxPQUFPLEVBQUUsS0FBSyxPQUFPLEVBQUUsS0FBSyxNQUFNLEVBQWdCLE1BQU0sdUJBQXVCLENBQUM7QUFHaEYsT0FBTyxFQUFFLFlBQVksRUFBRSxNQUFNLHlCQUF5QixDQUFDO0FBQ3ZELE9BQU8sS0FBSyxFQUFFLGVBQWUsRUFBRSxNQUFNLHNCQUFzQixDQUFDO0FBQzVELE9BQU8sS0FBSyxFQUFtRCxHQUFHLEVBQUUsTUFBTSxFQUFFLE1BQU0sWUFBWSxDQUFDO0FBRS9GLE9BQU8sRUFBb0MsS0FBSyxPQUFPLEVBQUUsS0FBSyxjQUFjLEVBQUUsTUFBTSxnQkFBZ0IsQ0FBQztBQUNyRyxPQUFPLEtBQUssRUFBRSxZQUFZLEVBQUUsTUFBTSw2QkFBNkIsQ0FBQztBQUNoRSxPQUFPLEtBQUssRUFBRSwrQkFBK0IsRUFBRSxXQUFXLEVBQUUsYUFBYSxFQUFFLE1BQU0scUJBQXFCLENBQUM7QUFFdkcsT0FBTyxLQUFLLEVBQ1YsV0FBVyxFQUNYLFNBQVMsRUFDVCx5QkFBeUIsRUFDekIsc0JBQXNCLEVBQ3ZCLE1BQU0saUNBQWlDLENB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
@@ -1 +1 @@
1
- {"version":3,"file":"validator.d.ts","sourceRoot":"","sources":["../src/validator.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,2BAA2B,CAAC;AAErE,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAC;AACrD,OAAO,EACL,WAAW,EACX,gBAAgB,EAEhB,qBAAqB,EACrB,UAAU,EACX,MAAM,iCAAiC,CAAC;AACzC,OAAO,EAAE,EAAE,EAAE,MAAM,gCAAgC,CAAC;AAEpD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,+BAA+B,CAAC;AAChE,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,iCAAiC,CAAC;AACjE,OAAO,EAAgB,KAAK,MAAM,EAAgB,MAAM,uBAAuB,CAAC;AAIhF,OAAO,EAAE,YAAY,EAAE,MAAM,yBAAyB,CAAC;AACvD,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAC;AAC5D,OAAO,KAAK,EAAmD,GAAG,EAAE,MAAM,EAAE,MAAM,YAAY,CAAC;AAE/F,OAAO,EAAoC,KAAK,OAAO,EAAE,KAAK,cAAc,EAAE,MAAM,gBAAgB,CAAC;AACrG,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,6BAA6B,CAAC;AAChE,OAAO,KAAK,EAAE,+BAA+B,EAAW,WAAW,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAEhH,OAAO,KAAK,EACV,qCAAqC,EACrC,WAAW,EACX,SAAS,EACT,yBAAyB,EACzB,sBAAsB,EACvB,MAAM,iCAAiC,CAAC;AACzC,OAAO,EAAE,KAAK,mBAAmB,EAAiC,MAAM,yBAAyB,CAAC;AAClG,OAAO,EACL,KAAK,aAAa,EAClB,KAAK,oBAAoB,EACzB,KAAK,qBAAqB,EAC1B,kBAAkB,EAClB,KAAK,sBAAsB,EAC3B,KAAK,yBAAyB,EAC/B,MAAM,mBAAmB,CAAC;AAC3B,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,sBAAsB,CAAC;AAC7D,OAAO,KAAK,EAAE,WAAW,EAA6B,EAAE,EAAE,MAAM,kBAAkB,CAAC;AAEnF,OAAO,EAAE,KAAK,eAAe,EAAE,KAAK,MAAM,EAAsB,MAAM,yBAAyB,CAAC;AAEhG,OAAO,EAAY,KAAK,cAAc,EAAE,MAAM,kCAAkC,CAAC;AAGjF,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,MAAM,CAAC;AAEhD,OAAO,EAAE,oBAAoB,EAA6C,MAAM,6BAA6B,CAAC;AAC9G,OAAO,KAAK,EAAE,0BAA0B,EAAE,MAAM,yBAAyB,CAAC;AAG1E,OAAO,KAAK,EAAE,yBAAyB,EAAE,MAAM,0BAA0B,CAAC;;AAc1E;;GAEG;AACH,qBAAa,eAAgB,SAAQ,oBAA2C,YAAW,SAAS,EAAE,OAAO;IAwBzG,OAAO,CAAC,QAAQ;IAChB,OAAO,CAAC,UAAU;IAClB,OAAO,CAAC,SAAS;IACjB,OAAO,CAAC,oBAAoB;IAC5B,OAAO,CAAC,WAAW;IACnB,OAAO,CAAC,kBAAkB;IAC1B,OAAO,CAAC,UAAU;IAClB,OAAO,CAAC,mBAAmB;IAC3B,OAAO,CAAC,MAAM;IACd,OAAO,CAAC,UAAU;IAClB,OAAO,CAAC,YAAY;IAjCtB,SAAgB,MAAM,EAAE,MAAM,CAAC;IAC/B,OAAO,CAAC,iBAAiB,CAAoB;IAC7C,OAAO,CAAC,OAAO,CAAmB;IAClC,OAAO,CAAC,GAAG,CAAS;IAGpB,OAAO,CAAC,qBAAqB,CAAS;IAEtC,wFAAwF;IACxF,OAAO,CAAC,iBAAiB,CAAC,CAAgB;IAE1C,sDAAsD;IACtD,OAAO,CAAC,sBAAsB,CAAC,CAAqB;IAEpD,OAAO,CAAC,+BAA+B,CAA0B;IACjE,OAAO,CAAC,oBAAoB,CAAiB;IAE7C,OAAO,CAAC,wBAAwB,CAA0B;IAE1D,mFAAmF;IACnF,OAAO,CAAC,oBAAoB,CAAC,CAAyB;IAEtD,SAAS,aACC,QAAQ,EAAE,yBAAyB,EACnC,UAAU,EAAE,UAAU,EACtB,SAAS,EAAE,GAAG,EACd,oBAAoB,EAAE,oBAAoB,EAC1C,WAAW,EAAE,aAAa,EAC1B,kBAAkB,EAAE,0BAA0B,EAC9C,UAAU,EAAE,sBAAsB,EAClC,mBAAmB,EAAE,mBAAmB,EACxC,MAAM,EAAE,yBAAyB,EACjC,UAAU,EAAE,mBAAmB,EAC/B,YAAY,GAAE,YAAiC,EACvD,SAAS,GAAE,eAAsC,EACjD,GAAG,SAA4B,EAiBhC;IAED,OAAc,6BAA6B,CAAC,eAAe,EAAE,eAAe,EAAE,MAAM,CAAC,EAAE,MAAM,QAuB5F;YAEa,0BAA0B;IA2BxC,OAAa,GAAG,CACd,MAAM,EAAE,yBAAyB,EACjC,kBAAkB,EAAE,0BAA0B,EAC9C,UAAU,EAAE,sBAAsB,EAClC,UAAU,EAAE,UAAU,EACtB,SAAS,EAAE,GAAG,EACd,WAAW,EAAE,aAAa,GAAG,WAAW,EACxC,mBAAmB,EAAE,mBAAmB,EACxC,UAAU,EAAE,WAAW,EACvB,eAAe,EAAE,eAAe,EAChC,UAAU,EAAE,mBAAmB,EAC/B,YAAY,GAAE,YAAiC,EAC/C,SAAS,GAAE,eAAsC,4BA+ClD;IAEM,qBAAqB,iBAI3B;IAEM,uBAAuB,yBAE7B;IAEM,eAAe,CAAC,IAAI,EAAE,UAAU,EAAE,GAAG,EAAE,mBAAmB,EAAE,OAAO,EAAE,cAAc,sBAEzF;IAEM,sBAAsB,CAAC,QAAQ,EAAE,UAAU,GAAG,UAAU,CAE9D;IAEM,0BAA0B,CAAC,QAAQ,EAAE,UAAU,GAAG,YAAY,CAEpE;IAEM,SAAS,IAAI,yBAAyB,CAE5C;IAEM,YAAY,CAAC,MAAM,EAAE,OAAO,CAAC,yBAAyB,CAAC,QAE7D;IAEY,KAAK,kBAmBjB;IAEY,IAAI,kBAGhB;IAED,0CAA0C;IAC7B,gBAAgB,kBAkC5B;IAED;;;;OAIG;IACG,qBAAqB,CAAC,QAAQ,EAAE,aAAa,EAAE,cAAc,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAkG7F;IAED;;;;;OAKG;IACG,0BAA0B,CAC9B,QAAQ,EAAE,sBAAsB,EAChC,eAAe,EAAE,MAAM,GACtB,OAAO,CAAC,qBAAqB,EAAE,GAAG,SAAS,CAAC,CAmG9C;IAED;;;OAGG;IACH,OAAO,CAAC,kBAAkB;YAiBZ,wCAAwC;YAsBxC,0BAA0B;IA4HxC;;OAEG;IACH,OAAO,CAAC,0BAA0B;YAepB,wBAAwB;IA0BtC,OAAO,CAAC,iBAAiB;IA2BzB;;;OAGG;IACH,OAAO,CAAC,uBAAuB;IAoB/B;;;OAGG;IACH,OAAO,CAAC,0BAA0B;IAkB5B,mBAAmB,CACvB,WAAW,EAAE,WAAW,EACxB,qBAAqB,EAAE,qBAAqB,EAC5C,MAAM,EAAE,EAAE,EACV,OAAO,EAAE,EAAE,EACX,GAAG,EAAE,EAAE,EAAE,EACT,eAAe,EAAE,UAAU,GAAG,SAAS,EACvC,OAAO,GAAE,oBAAyB,GACjC,OAAO,CAAC,aAAa,CAAC,CAgCxB;IAEK,wBAAwB,CAC5B,gBAAgB,EAAE,gBAAgB,EAClC,OAAO,EAAE,EAAE,EACX,aAAa,EAAE,qCAAqC,GAAG,SAAS,EAChE,eAAe,EAAE,UAAU,GAAG,SAAS,EACvC,OAAO,GAAE,yBAA8B,GACtC,OAAO,CAAC,kBAAkB,CAAC,CAwB7B;IAEK,sBAAsB,CAAC,QAAQ,EAAE,aAAa,GAAG,OAAO,CAAC,IAAI,CAAC,CAEnE;IAEK,0BAA0B,CAC9B,sBAAsB,EAAE,+BAA+B,EACvD,QAAQ,EAAE,UAAU,EACpB,IAAI,EAAE,UAAU,EAChB,WAAW,EAAE,WAAW,GAAG,gBAAgB,GAC1C,OAAO,CAAC,SAAS,CAAC,CAEpB;IAEK,sBAAsB,CAAC,QAAQ,EAAE,kBAAkB,GAAG,OAAO,CAAC,qBAAqB,EAAE,CAAC,CAiB3F;IAEK,mBAAmB,CACvB,QAAQ,EAAE,kBAAkB,EAC5B,QAAQ,EAAE,MAAM,EAChB,QAAQ,EAAE,IAAI,GACb,OAAO,CAAC,qBAAqB,EAAE,CAAC,CAiElC;YAEa,iBAAiB;CAwBhC"}
1
+ {"version":3,"file":"validator.d.ts","sourceRoot":"","sources":["../src/validator.ts"],"names":[],"mappings":"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"}
package/dest/validator.js CHANGED
@@ -1,25 +1,21 @@
1
1
  import { getBlobsPerL1Block } from '@aztec/blob-lib';
2
- import { BlockNumber } from '@aztec/foundation/branded-types';
3
- import { TimeoutError } from '@aztec/foundation/error';
4
2
  import { createLogger } from '@aztec/foundation/log';
5
- import { retryUntil } from '@aztec/foundation/retry';
6
3
  import { RunningPromise } from '@aztec/foundation/running-promise';
7
4
  import { sleep } from '@aztec/foundation/sleep';
8
5
  import { DateProvider } from '@aztec/foundation/timer';
9
6
  import { AuthRequest, AuthResponse, BlockProposalValidator, ReqRespSubProtocol } from '@aztec/p2p';
10
7
  import { OffenseType, WANT_TO_SLASH_EVENT } from '@aztec/slasher';
11
8
  import { getEpochAtSlot } from '@aztec/stdlib/epoch-helpers';
12
- import { accumulateCheckpointOutHashes } from '@aztec/stdlib/messaging';
13
9
  import { AttestationTimeoutError } from '@aztec/stdlib/validators';
14
10
  import { getTelemetryClient } from '@aztec/telemetry-client';
15
- import { createHASigner } from '@aztec/validator-ha-signer/factory';
11
+ import { createHASigner, createLocalSignerWithProtection, createSignerFromSharedDb } from '@aztec/validator-ha-signer/factory';
16
12
  import { DutyType } from '@aztec/validator-ha-signer/types';
17
13
  import { EventEmitter } from 'events';
18
- import { BlockProposalHandler } from './block_proposal_handler.js';
19
14
  import { ValidationService } from './duties/validation_service.js';
20
15
  import { HAKeyStore } from './key_store/ha_key_store.js';
21
16
  import { NodeKeystoreAdapter } from './key_store/node_keystore_adapter.js';
22
17
  import { ValidatorMetrics } from './metrics.js';
18
+ import { ProposalHandler } from './proposal_handler.js';
23
19
  // We maintain a set of proposers who have proposed invalid blocks.
24
20
  // Just cap the set to avoid unbounded growth.
25
21
  const MAX_PROPOSERS_OF_INVALID_BLOCKS = 1000;
@@ -34,13 +30,14 @@ const SLASHABLE_BLOCK_PROPOSAL_VALIDATION_RESULT = [
34
30
  keyStore;
35
31
  epochCache;
36
32
  p2pClient;
37
- blockProposalHandler;
33
+ proposalHandler;
38
34
  blockSource;
39
35
  checkpointsBuilder;
40
36
  worldState;
41
37
  l1ToL2MessageSource;
42
38
  config;
43
39
  blobClient;
40
+ slashingProtectionSigner;
44
41
  dateProvider;
45
42
  tracer;
46
43
  validationService;
@@ -52,10 +49,11 @@ const SLASHABLE_BLOCK_PROPOSAL_VALIDATION_RESULT = [
52
49
  /** Tracks the last checkpoint proposal we created. */ lastProposedCheckpoint;
53
50
  lastEpochForCommitteeUpdateLoop;
54
51
  epochCacheUpdateLoop;
52
+ /** Tracks the last epoch in which each attester successfully submitted at least one attestation. */ lastAttestedEpochByAttester;
55
53
  proposersOfInvalidBlocks;
56
54
  /** Tracks the last checkpoint proposal we attested to, to prevent equivocation. */ lastAttestedProposal;
57
- constructor(keyStore, epochCache, p2pClient, blockProposalHandler, blockSource, checkpointsBuilder, worldState, l1ToL2MessageSource, config, blobClient, dateProvider = new DateProvider(), telemetry = getTelemetryClient(), log = createLogger('validator')){
58
- super(), this.keyStore = keyStore, this.epochCache = epochCache, this.p2pClient = p2pClient, this.blockProposalHandler = blockProposalHandler, this.blockSource = blockSource, this.checkpointsBuilder = checkpointsBuilder, this.worldState = worldState, this.l1ToL2MessageSource = l1ToL2MessageSource, this.config = config, this.blobClient = blobClient, this.dateProvider = dateProvider, this.hasRegisteredHandlers = false, this.proposersOfInvalidBlocks = new Set();
55
+ constructor(keyStore, epochCache, p2pClient, proposalHandler, blockSource, checkpointsBuilder, worldState, l1ToL2MessageSource, config, blobClient, slashingProtectionSigner, dateProvider = new DateProvider(), telemetry = getTelemetryClient(), log = createLogger('validator')){
56
+ super(), this.keyStore = keyStore, this.epochCache = epochCache, this.p2pClient = p2pClient, this.proposalHandler = proposalHandler, this.blockSource = blockSource, this.checkpointsBuilder = checkpointsBuilder, this.worldState = worldState, this.l1ToL2MessageSource = l1ToL2MessageSource, this.config = config, this.blobClient = blobClient, this.slashingProtectionSigner = slashingProtectionSigner, this.dateProvider = dateProvider, this.hasRegisteredHandlers = false, this.lastAttestedEpochByAttester = new Map(), this.proposersOfInvalidBlocks = new Set();
59
57
  // Create child logger with fisherman prefix if in fisherman mode
60
58
  this.log = config.fishermanMode ? log.createChild('[FISHERMAN]') : log;
61
59
  this.tracer = telemetry.getTracer('Validator');
@@ -94,6 +92,7 @@ const SLASHABLE_BLOCK_PROPOSAL_VALIDATION_RESULT = [
94
92
  this.log.trace(`No committee found for slot`);
95
93
  return;
96
94
  }
95
+ this.metrics.setCurrentEpoch(epoch);
97
96
  if (epoch !== this.lastEpochForCommitteeUpdateLoop) {
98
97
  const me = this.getValidatorAddresses();
99
98
  const committeeSet = new Set(committee.map((v)=>v.toString()));
@@ -109,30 +108,49 @@ const SLASHABLE_BLOCK_PROPOSAL_VALIDATION_RESULT = [
109
108
  this.log.error(`Error updating epoch committee`, err);
110
109
  }
111
110
  }
112
- static async new(config, checkpointsBuilder, worldState, epochCache, p2pClient, blockSource, l1ToL2MessageSource, txProvider, keyStoreManager, blobClient, dateProvider = new DateProvider(), telemetry = getTelemetryClient()) {
111
+ static async new(config, checkpointsBuilder, worldState, epochCache, p2pClient, blockSource, l1ToL2MessageSource, txProvider, keyStoreManager, blobClient, dateProvider = new DateProvider(), telemetry = getTelemetryClient(), slashingProtectionDb) {
113
112
  const metrics = new ValidatorMetrics(telemetry);
114
113
  const blockProposalValidator = new BlockProposalValidator(epochCache, {
115
- txsPermitted: !config.disableTransactions
114
+ txsPermitted: !config.disableTransactions,
115
+ maxTxsPerBlock: config.validateMaxTxsPerBlock
116
116
  });
117
- const blockProposalHandler = new BlockProposalHandler(checkpointsBuilder, worldState, blockSource, l1ToL2MessageSource, txProvider, blockProposalValidator, epochCache, config, metrics, dateProvider, telemetry);
118
- let validatorKeyStore = NodeKeystoreAdapter.fromKeyStoreManager(keyStoreManager);
119
- if (config.haSigningEnabled) {
117
+ const proposalHandler = new ProposalHandler(checkpointsBuilder, worldState, blockSource, l1ToL2MessageSource, txProvider, blockProposalValidator, epochCache, config, blobClient, metrics, dateProvider, telemetry);
118
+ const nodeKeystoreAdapter = NodeKeystoreAdapter.fromKeyStoreManager(keyStoreManager);
119
+ let slashingProtectionSigner;
120
+ if (slashingProtectionDb) {
121
+ // Shared database mode: use a pre-existing database (e.g. for testing HA setups).
122
+ ({ signer: slashingProtectionSigner } = createSignerFromSharedDb(slashingProtectionDb, config, {
123
+ telemetryClient: telemetry,
124
+ dateProvider
125
+ }));
126
+ } else if (config.haSigningEnabled) {
127
+ // Multi-node HA mode: use PostgreSQL-backed distributed locking.
120
128
  // If maxStuckDutiesAgeMs is not explicitly set, compute it from Aztec slot duration
121
129
  const haConfig = {
122
130
  ...config,
123
131
  maxStuckDutiesAgeMs: config.maxStuckDutiesAgeMs ?? epochCache.getL1Constants().slotDuration * 2 * 1000
124
132
  };
125
- const { signer } = await createHASigner(haConfig);
126
- validatorKeyStore = new HAKeyStore(validatorKeyStore, signer);
127
- }
128
- const validator = new ValidatorClient(validatorKeyStore, epochCache, p2pClient, blockProposalHandler, blockSource, checkpointsBuilder, worldState, l1ToL2MessageSource, config, blobClient, dateProvider, telemetry);
133
+ ({ signer: slashingProtectionSigner } = await createHASigner(haConfig, {
134
+ telemetryClient: telemetry,
135
+ dateProvider
136
+ }));
137
+ } else {
138
+ // Single-node mode: use LMDB-backed local signing protection.
139
+ // This prevents double-signing if the node crashes and restarts mid-proposal.
140
+ ({ signer: slashingProtectionSigner } = await createLocalSignerWithProtection(config, {
141
+ telemetryClient: telemetry,
142
+ dateProvider
143
+ }));
144
+ }
145
+ const validatorKeyStore = new HAKeyStore(nodeKeystoreAdapter, slashingProtectionSigner);
146
+ const validator = new ValidatorClient(validatorKeyStore, epochCache, p2pClient, proposalHandler, blockSource, checkpointsBuilder, worldState, l1ToL2MessageSource, config, blobClient, slashingProtectionSigner, dateProvider, telemetry);
129
147
  return validator;
130
148
  }
131
149
  getValidatorAddresses() {
132
150
  return this.keyStore.getAddresses().filter((addr)=>!this.config.disabledValidators.some((disabled)=>disabled.equals(addr)));
133
151
  }
134
- getBlockProposalHandler() {
135
- return this.blockProposalHandler;
152
+ getProposalHandler() {
153
+ return this.proposalHandler;
136
154
  }
137
155
  signWithAddress(addr, msg, context) {
138
156
  return this.keyStore.signTypedDataWithAddress(addr, msg, context);
@@ -152,6 +170,11 @@ const SLASHABLE_BLOCK_PROPOSAL_VALIDATION_RESULT = [
152
170
  ...config
153
171
  };
154
172
  }
173
+ reloadKeystore(newManager) {
174
+ const newAdapter = NodeKeystoreAdapter.fromKeyStoreManager(newManager);
175
+ this.keyStore = new HAKeyStore(newAdapter, this.slashingProtectionSigner);
176
+ this.validationService = new ValidationService(this.keyStore, this.log.createChild('validation-service'));
177
+ }
155
178
  async start() {
156
179
  if (this.epochCacheUpdateLoop.isRunning()) {
157
180
  this.log.warn(`Validator client already started`);
@@ -183,7 +206,7 @@ const SLASHABLE_BLOCK_PROPOSAL_VALIDATION_RESULT = [
183
206
  // The checkpoint is received as CheckpointProposalCore since the lastBlock is extracted
184
207
  // and processed separately via the block handler above.
185
208
  const checkpointHandler = (checkpoint, proposalSender)=>this.attestToCheckpointProposal(checkpoint, proposalSender);
186
- this.p2pClient.registerCheckpointProposalHandler(checkpointHandler);
209
+ this.p2pClient.registerValidatorCheckpointProposalHandler(checkpointHandler);
187
210
  // Duplicate proposal handler - triggers slashing for equivocation
188
211
  this.p2pClient.registerDuplicateProposalCallback((info)=>{
189
212
  this.handleDuplicateProposal(info);
@@ -212,13 +235,12 @@ const SLASHABLE_BLOCK_PROPOSAL_VALIDATION_RESULT = [
212
235
  this.log.warn(`Received block proposal with invalid signature for slot ${slotNumber}`);
213
236
  return false;
214
237
  }
215
- // Ignore proposals from ourselves (may happen in HA setups)
238
+ // Log self-proposals from HA peers (same validator key on different nodes)
216
239
  if (this.getValidatorAddresses().some((addr)=>addr.equals(proposer))) {
217
- this.log.warn(`Ignoring block proposal from self for slot ${slotNumber}`, {
240
+ this.log.verbose(`Processing block proposal from HA peer for slot ${slotNumber}`, {
218
241
  proposer: proposer.toString(),
219
242
  slotNumber
220
243
  });
221
- return false;
222
244
  }
223
245
  // Check if we're in the committee (for metrics purposes)
224
246
  const inCommittee = await this.epochCache.filterInCommittee(slotNumber, this.getValidatorAddresses());
@@ -234,12 +256,12 @@ const SLASHABLE_BLOCK_PROPOSAL_VALIDATION_RESULT = [
234
256
  });
235
257
  // Reexecute txs if we are part of the committee, or if slashing is enabled, or if we are configured to always reexecute.
236
258
  // In fisherman mode, we always reexecute to validate proposals.
237
- const { validatorReexecute, slashBroadcastedInvalidBlockPenalty, alwaysReexecuteBlockProposals, fishermanMode } = this.config;
238
- const shouldReexecute = fishermanMode || slashBroadcastedInvalidBlockPenalty > 0n && validatorReexecute || partOfCommittee && validatorReexecute || alwaysReexecuteBlockProposals || this.blobClient.canUpload();
239
- const validationResult = await this.blockProposalHandler.handleBlockProposal(proposal, proposalSender, !!shouldReexecute && !escapeHatchOpen);
259
+ const { slashBroadcastedInvalidBlockPenalty, alwaysReexecuteBlockProposals, fishermanMode } = this.config;
260
+ const shouldReexecute = fishermanMode || slashBroadcastedInvalidBlockPenalty > 0n || partOfCommittee || alwaysReexecuteBlockProposals || this.blobClient.canUpload();
261
+ const validationResult = await this.proposalHandler.handleBlockProposal(proposal, proposalSender, !!shouldReexecute && !escapeHatchOpen);
240
262
  if (!validationResult.isValid) {
241
- this.log.warn(`Block proposal validation failed: ${validationResult.reason}`, proposalInfo);
242
263
  const reason = validationResult.reason || 'unknown';
264
+ this.log.warn(`Block proposal validation failed: ${reason}`, proposalInfo);
243
265
  // Classify failure reason: bad proposal vs node issue
244
266
  const badProposalReasons = [
245
267
  'invalid_proposal',
@@ -279,54 +301,44 @@ const SLASHABLE_BLOCK_PROPOSAL_VALIDATION_RESULT = [
279
301
  * the lastBlock is extracted and processed separately via the block handler.
280
302
  * @returns Checkpoint attestations if valid, undefined otherwise
281
303
  */ async attestToCheckpointProposal(proposal, _proposalSender) {
282
- const slotNumber = proposal.slotNumber;
304
+ const proposalSlotNumber = proposal.slotNumber;
283
305
  const proposer = proposal.getSender();
284
306
  // If escape hatch is open for this slot's epoch, do not attest.
285
- if (await this.epochCache.isEscapeHatchOpenAtSlot(slotNumber)) {
286
- this.log.warn(`Escape hatch open for slot ${slotNumber}, skipping checkpoint attestation handling`);
287
- return undefined;
288
- }
289
- // Reject proposals with invalid signatures
290
- if (!proposer) {
291
- this.log.warn(`Received checkpoint proposal with invalid signature for slot ${slotNumber}`);
307
+ if (await this.epochCache.isEscapeHatchOpenAtSlot(proposalSlotNumber)) {
308
+ this.log.warn(`Escape hatch open for slot ${proposalSlotNumber}, skipping checkpoint attestation handling`);
292
309
  return undefined;
293
310
  }
294
311
  // Ignore proposals from ourselves (may happen in HA setups)
295
- if (this.getValidatorAddresses().some((addr)=>addr.equals(proposer))) {
296
- this.log.warn(`Ignoring block proposal from self for slot ${slotNumber}`, {
312
+ if (proposer && this.getValidatorAddresses().some((addr)=>addr.equals(proposer))) {
313
+ this.log.debug(`Ignoring block proposal from self for slot ${proposalSlotNumber}`, {
297
314
  proposer: proposer.toString(),
298
- slotNumber
315
+ proposalSlotNumber
299
316
  });
300
317
  return undefined;
301
318
  }
302
- // Check that I have any address in current committee before attesting
303
- const inCommittee = await this.epochCache.filterInCommittee(slotNumber, this.getValidatorAddresses());
319
+ // Check that I have any address in the committee where this checkpoint will land before attesting
320
+ const inCommittee = await this.epochCache.filterInCommittee(proposalSlotNumber, this.getValidatorAddresses());
304
321
  const partOfCommittee = inCommittee.length > 0;
305
322
  const proposalInfo = {
306
- slotNumber,
323
+ proposalSlotNumber,
307
324
  archive: proposal.archive.toString(),
308
- proposer: proposer.toString(),
309
- txCount: proposal.txHashes.length
325
+ proposer: proposer?.toString()
310
326
  };
311
- this.log.info(`Received checkpoint proposal for slot ${slotNumber}`, {
327
+ this.log.info(`Received checkpoint proposal for slot ${proposalSlotNumber}`, {
312
328
  ...proposalInfo,
313
- txHashes: proposal.txHashes.map((t)=>t.toString()),
314
329
  fishermanMode: this.config.fishermanMode || false
315
330
  });
316
- // Validate the checkpoint proposal before attesting (unless skipCheckpointProposalValidation is set)
331
+ // Validate the checkpoint proposal before attesting (unless skipCheckpointProposalValidation is set).
332
+ // Uses the cached result from the all-nodes callback if available (avoids double validation).
317
333
  if (this.config.skipCheckpointProposalValidation) {
318
- this.log.warn(`Skipping checkpoint proposal validation for slot ${slotNumber}`, proposalInfo);
334
+ this.log.warn(`Skipping checkpoint proposal validation for slot ${proposalSlotNumber}`, proposalInfo);
319
335
  } else {
320
- const validationResult = await this.validateCheckpointProposal(proposal, proposalInfo);
336
+ const validationResult = await this.proposalHandler.handleCheckpointProposal(proposal, proposalInfo);
321
337
  if (!validationResult.isValid) {
322
338
  this.log.warn(`Checkpoint proposal validation failed: ${validationResult.reason}`, proposalInfo);
323
339
  return undefined;
324
340
  }
325
341
  }
326
- // Upload blobs to filestore if we can (fire and forget)
327
- if (this.blobClient.canUpload()) {
328
- void this.uploadBlobsForCheckpoint(proposal, proposalInfo);
329
- }
330
342
  // Check that I have any address in current committee before attesting
331
343
  // In fisherman mode, we still create attestations for validation even if not in committee
332
344
  if (!partOfCommittee && !this.config.fishermanMode) {
@@ -334,12 +346,22 @@ const SLASHABLE_BLOCK_PROPOSAL_VALIDATION_RESULT = [
334
346
  return undefined;
335
347
  }
336
348
  // Provided all of the above checks pass, we can attest to the proposal
337
- this.log.info(`${partOfCommittee ? 'Attesting to' : 'Validated'} checkpoint proposal for slot ${slotNumber}`, {
349
+ this.log.info(`${partOfCommittee ? 'Attesting to' : 'Validated'} checkpoint proposal for slot ${proposalSlotNumber}`, {
338
350
  ...proposalInfo,
339
351
  inCommittee: partOfCommittee,
340
352
  fishermanMode: this.config.fishermanMode || false
341
353
  });
342
354
  this.metrics.incSuccessfulAttestations(inCommittee.length);
355
+ // Track epoch participation per attester: count each (attester, epoch) pair at most once
356
+ const proposalEpoch = getEpochAtSlot(proposalSlotNumber, this.epochCache.getL1Constants());
357
+ for (const attester of inCommittee){
358
+ const key = attester.toString();
359
+ const lastEpoch = this.lastAttestedEpochByAttester.get(key);
360
+ if (lastEpoch === undefined || proposalEpoch > lastEpoch) {
361
+ this.lastAttestedEpochByAttester.set(key, proposalEpoch);
362
+ this.metrics.incAttestedEpochCount(attester);
363
+ }
364
+ }
343
365
  // Determine which validators should attest
344
366
  let attestors;
345
367
  if (partOfCommittee) {
@@ -356,7 +378,7 @@ const SLASHABLE_BLOCK_PROPOSAL_VALIDATION_RESULT = [
356
378
  }
357
379
  if (this.config.fishermanMode) {
358
380
  // bail out early and don't save attestations to the pool in fisherman mode
359
- this.log.info(`Creating checkpoint attestations for slot ${slotNumber}`, {
381
+ this.log.info(`Creating checkpoint attestations for slot ${proposalSlotNumber}`, {
360
382
  ...proposalInfo,
361
383
  attestors: attestors.map((a)=>a.toString())
362
384
  });
@@ -391,139 +413,6 @@ const SLASHABLE_BLOCK_PROPOSAL_VALIDATION_RESULT = [
391
413
  return attestations;
392
414
  }
393
415
  /**
394
- * Validates a checkpoint proposal by building the full checkpoint and comparing it with the proposal.
395
- * @returns Validation result with isValid flag and reason if invalid.
396
- */ async validateCheckpointProposal(proposal, proposalInfo) {
397
- const slot = proposal.slotNumber;
398
- const timeoutSeconds = 10; // TODO(palla/mbps): This should map to the timetable settings
399
- // Wait for last block to sync by archive
400
- let lastBlockHeader;
401
- try {
402
- lastBlockHeader = await retryUntil(async ()=>{
403
- await this.blockSource.syncImmediate();
404
- return this.blockSource.getBlockHeaderByArchive(proposal.archive);
405
- }, `waiting for block with archive ${proposal.archive.toString()} for slot ${slot}`, timeoutSeconds, 0.5);
406
- } catch (err) {
407
- if (err instanceof TimeoutError) {
408
- this.log.warn(`Timed out waiting for block with archive matching checkpoint proposal`, proposalInfo);
409
- return {
410
- isValid: false,
411
- reason: 'last_block_not_found'
412
- };
413
- }
414
- this.log.error(`Error fetching last block for checkpoint proposal`, err, proposalInfo);
415
- return {
416
- isValid: false,
417
- reason: 'block_fetch_error'
418
- };
419
- }
420
- if (!lastBlockHeader) {
421
- this.log.warn(`Last block not found for checkpoint proposal`, proposalInfo);
422
- return {
423
- isValid: false,
424
- reason: 'last_block_not_found'
425
- };
426
- }
427
- // Get all full blocks for the slot and checkpoint
428
- const blocks = await this.blockSource.getBlocksForSlot(slot);
429
- if (blocks.length === 0) {
430
- this.log.warn(`No blocks found for slot ${slot}`, proposalInfo);
431
- return {
432
- isValid: false,
433
- reason: 'no_blocks_for_slot'
434
- };
435
- }
436
- this.log.debug(`Found ${blocks.length} blocks for slot ${slot}`, {
437
- ...proposalInfo,
438
- blockNumbers: blocks.map((b)=>b.number)
439
- });
440
- // Get checkpoint constants from first block
441
- const firstBlock = blocks[0];
442
- const constants = this.extractCheckpointConstants(firstBlock);
443
- const checkpointNumber = firstBlock.checkpointNumber;
444
- // Get L1-to-L2 messages for this checkpoint
445
- const l1ToL2Messages = await this.l1ToL2MessageSource.getL1ToL2Messages(checkpointNumber);
446
- // Compute the previous checkpoint out hashes for the epoch.
447
- // TODO: There can be a more efficient way to get the previous checkpoint out hashes without having to fetch the
448
- // actual checkpoints and the blocks/txs in them.
449
- const epoch = getEpochAtSlot(slot, this.epochCache.getL1Constants());
450
- const previousCheckpoints = (await this.blockSource.getCheckpointsForEpoch(epoch)).filter((b)=>b.number < checkpointNumber).sort((a, b)=>a.number - b.number);
451
- const previousCheckpointOutHashes = previousCheckpoints.map((c)=>c.getCheckpointOutHash());
452
- // Fork world state at the block before the first block
453
- const parentBlockNumber = BlockNumber(firstBlock.number - 1);
454
- const fork = await this.worldState.fork(parentBlockNumber);
455
- try {
456
- // Create checkpoint builder with all existing blocks
457
- const checkpointBuilder = await this.checkpointsBuilder.openCheckpoint(checkpointNumber, constants, l1ToL2Messages, previousCheckpointOutHashes, fork, blocks, this.log.getBindings());
458
- // Complete the checkpoint to get computed values
459
- const computedCheckpoint = await checkpointBuilder.completeCheckpoint();
460
- // Compare checkpoint header with proposal
461
- if (!computedCheckpoint.header.equals(proposal.checkpointHeader)) {
462
- this.log.warn(`Checkpoint header mismatch`, {
463
- ...proposalInfo,
464
- computed: computedCheckpoint.header.toInspect(),
465
- proposal: proposal.checkpointHeader.toInspect()
466
- });
467
- return {
468
- isValid: false,
469
- reason: 'checkpoint_header_mismatch'
470
- };
471
- }
472
- // Compare archive root with proposal
473
- if (!computedCheckpoint.archive.root.equals(proposal.archive)) {
474
- this.log.warn(`Archive root mismatch`, {
475
- ...proposalInfo,
476
- computed: computedCheckpoint.archive.root.toString(),
477
- proposal: proposal.archive.toString()
478
- });
479
- return {
480
- isValid: false,
481
- reason: 'archive_mismatch'
482
- };
483
- }
484
- // Check that the accumulated epoch out hash matches the value in the proposal.
485
- // The epoch out hash is the accumulated hash of all checkpoint out hashes in the epoch.
486
- const checkpointOutHash = computedCheckpoint.getCheckpointOutHash();
487
- const computedEpochOutHash = accumulateCheckpointOutHashes([
488
- ...previousCheckpointOutHashes,
489
- checkpointOutHash
490
- ]);
491
- const proposalEpochOutHash = proposal.checkpointHeader.epochOutHash;
492
- if (!computedEpochOutHash.equals(proposalEpochOutHash)) {
493
- this.log.warn(`Epoch out hash mismatch`, {
494
- proposalEpochOutHash: proposalEpochOutHash.toString(),
495
- computedEpochOutHash: computedEpochOutHash.toString(),
496
- checkpointOutHash: checkpointOutHash.toString(),
497
- previousCheckpointOutHashes: previousCheckpointOutHashes.map((h)=>h.toString()),
498
- ...proposalInfo
499
- });
500
- return {
501
- isValid: false,
502
- reason: 'out_hash_mismatch'
503
- };
504
- }
505
- this.log.verbose(`Checkpoint proposal validation successful for slot ${slot}`, proposalInfo);
506
- return {
507
- isValid: true
508
- };
509
- } finally{
510
- await fork.close();
511
- }
512
- }
513
- /**
514
- * Extract checkpoint global variables from a block.
515
- */ extractCheckpointConstants(block) {
516
- const gv = block.header.globalVariables;
517
- return {
518
- chainId: gv.chainId,
519
- version: gv.version,
520
- slotNumber: gv.slotNumber,
521
- coinbase: gv.coinbase,
522
- feeRecipient: gv.feeRecipient,
523
- gasFees: gv.gasFees
524
- };
525
- }
526
- /**
527
416
  * Uploads blobs for a checkpoint to the filestore (fire and forget).
528
417
  */ async uploadBlobsForCheckpoint(proposal, proposalInfo) {
529
418
  try {
@@ -538,7 +427,7 @@ const SLASHABLE_BLOCK_PROPOSAL_VALIDATION_RESULT = [
538
427
  return;
539
428
  }
540
429
  const blobFields = blocks.flatMap((b)=>b.toBlobFields());
541
- const blobs = getBlobsPerL1Block(blobFields);
430
+ const blobs = await getBlobsPerL1Block(blobFields);
542
431
  await this.blobClient.sendBlobsToFilestore(blobs);
543
432
  this.log.debug(`Uploaded ${blobs.length} blobs to filestore for checkpoint at slot ${proposal.slotNumber}`, {
544
433
  ...proposalInfo,
@@ -626,7 +515,7 @@ const SLASHABLE_BLOCK_PROPOSAL_VALIDATION_RESULT = [
626
515
  this.lastProposedBlock = newProposal;
627
516
  return newProposal;
628
517
  }
629
- async createCheckpointProposal(checkpointHeader, archive, lastBlockInfo, proposerAddress, options = {}) {
518
+ async createCheckpointProposal(checkpointHeader, archive, feeAssetPriceModifier, lastBlockProposal, proposerAddress, options = {}) {
630
519
  // Validate that we're not creating a proposal for an older or equal slot
631
520
  if (this.lastProposedCheckpoint) {
632
521
  const lastSlot = this.lastProposedCheckpoint.slotNumber;
@@ -636,7 +525,7 @@ const SLASHABLE_BLOCK_PROPOSAL_VALIDATION_RESULT = [
636
525
  }
637
526
  }
638
527
  this.log.info(`Assembling checkpoint proposal for slot ${checkpointHeader.slotNumber}`);
639
- const newProposal = await this.validationService.createCheckpointProposal(checkpointHeader, archive, lastBlockInfo, proposerAddress, options);
528
+ const newProposal = await this.validationService.createCheckpointProposal(checkpointHeader, archive, feeAssetPriceModifier, lastBlockProposal, proposerAddress, options);
640
529
  this.lastProposedCheckpoint = newProposal;
641
530
  return newProposal;
642
531
  }