@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.
- package/README.md +41 -2
- package/dest/checkpoint_builder.d.ts +21 -8
- package/dest/checkpoint_builder.d.ts.map +1 -1
- package/dest/checkpoint_builder.js +124 -46
- package/dest/config.d.ts +1 -1
- package/dest/config.d.ts.map +1 -1
- package/dest/config.js +22 -6
- package/dest/duties/validation_service.d.ts +3 -4
- package/dest/duties/validation_service.d.ts.map +1 -1
- package/dest/duties/validation_service.js +11 -22
- package/dest/factory.d.ts +7 -4
- package/dest/factory.d.ts.map +1 -1
- package/dest/factory.js +6 -5
- package/dest/index.d.ts +2 -3
- package/dest/index.d.ts.map +1 -1
- package/dest/index.js +1 -2
- package/dest/key_store/ha_key_store.js +1 -1
- package/dest/metrics.d.ts +10 -2
- package/dest/metrics.d.ts.map +1 -1
- package/dest/metrics.js +12 -0
- package/dest/proposal_handler.d.ts +107 -0
- package/dest/proposal_handler.d.ts.map +1 -0
- package/dest/proposal_handler.js +966 -0
- package/dest/validator.d.ts +18 -15
- package/dest/validator.d.ts.map +1 -1
- package/dest/validator.js +80 -191
- package/package.json +19 -19
- package/src/checkpoint_builder.ts +142 -39
- package/src/config.ts +22 -6
- package/src/duties/validation_service.ts +18 -24
- package/src/factory.ts +9 -3
- package/src/index.ts +1 -2
- package/src/key_store/ha_key_store.ts +1 -1
- package/src/metrics.ts +19 -1
- package/src/proposal_handler.ts +1033 -0
- package/src/validator.ts +103 -210
- package/dest/block_proposal_handler.d.ts +0 -63
- package/dest/block_proposal_handler.d.ts.map +0 -1
- package/dest/block_proposal_handler.js +0 -546
- package/dest/tx_validator/index.d.ts +0 -3
- package/dest/tx_validator/index.d.ts.map +0 -1
- package/dest/tx_validator/index.js +0 -2
- package/dest/tx_validator/nullifier_cache.d.ts +0 -14
- package/dest/tx_validator/nullifier_cache.d.ts.map +0 -1
- package/dest/tx_validator/nullifier_cache.js +0 -24
- package/dest/tx_validator/tx_validator_factory.d.ts +0 -19
- package/dest/tx_validator/tx_validator_factory.d.ts.map +0 -1
- package/dest/tx_validator/tx_validator_factory.js +0 -54
- package/src/block_proposal_handler.ts +0 -555
- package/src/tx_validator/index.ts +0 -2
- package/src/tx_validator/nullifier_cache.ts +0 -30
- package/src/tx_validator/tx_validator_factory.ts +0 -154
package/dest/validator.d.ts
CHANGED
|
@@ -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 {
|
|
15
|
-
import {
|
|
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
|
|
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,
|
|
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
|
-
|
|
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
|
-
*
|
|
95
|
+
* Uploads blobs for a checkpoint to the filestore (fire and forget).
|
|
92
96
|
*/
|
|
93
|
-
|
|
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,
|
|
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,
|
|
118
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoidmFsaWRhdG9yLmQudHMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi9zcmMvdmFsaWRhdG9yLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBLE9BQU8sS0FBSyxFQUFFLG1CQUFtQixFQUFFLE1BQU0sMkJBQTJCLENBQUM7QUFFckUsT0FBTyxLQUFLLEVBQUUsVUFBVSxFQUFFLE1BQU0sb0JBQW9CLENBQUM7QUFDckQsT0FBTyxFQUNMLFdBQVcsRUFDWCxnQkFBZ0IsRUFFaEIscUJBQXFCLEVBQ3JCLFVBQVUsRUFDWCxNQUFNLGlDQUFpQyxDQUFDO0FBQ3pDLE9BQU8sRUFBRSxFQUFFLEVBQUUsTUFBTSxnQ0FBZ0MsQ0FBQztBQUNwRCxPQUFPLEtBQUssRUFBRSxVQUFVLEVBQUUsTUFBTSwrQkFBK0IsQ0FBQztBQUNoRSxPQUFPLEtBQUssRUFBRSxTQUFTLEVBQUUsTUFBTSxpQ0FBaUMsQ0FBQztBQUNqRSxPQUFPLEVBQUUsS0FBSyxPQUFPLEVBQUUsS0FBSyxNQUFNLEVBQWdCLE1BQU0sdUJBQXVCLENBQUM7QUFHaEYsT0FBTyxFQUFFLFlBQVksRUFBRSxNQUFNLHlCQUF5QixDQUFDO0FBQ3ZELE9BQU8sS0FBSyxFQUFFLGVBQWUsRUFBRSxNQUFNLHNCQUFzQixDQUFDO0FBQzVELE9BQU8sS0FBSyxFQUFtRCxHQUFHLEVBQUUsTUFBTSxFQUFFLE1BQU0sWUFBWSxDQUFDO0FBRS9GLE9BQU8sRUFBb0MsS0FBSyxPQUFPLEVBQUUsS0FBSyxjQUFjLEVBQUUsTUFBTSxnQkFBZ0IsQ0FBQztBQUNyRyxPQUFPLEtBQUssRUFBRSxZQUFZLEVBQUUsTUFBTSw2QkFBNkIsQ0FBQztBQUNoRSxPQUFPLEtBQUssRUFBRSwrQkFBK0IsRUFBRSxXQUFXLEVBQUUsYUFBYSxFQUFFLE1BQU0scUJBQXFCLENBQUM7QUFFdkcsT0FBTyxLQUFLLEVBQ1YsV0FBVyxFQUNYLFNBQVMsRUFDVCx5QkFBeUIsRUFDekIsc0JBQXNCLEVBQ3ZCLE1BQU0saUNBQWlDLENB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
|
package/dest/validator.d.ts.map
CHANGED
|
@@ -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;
|
|
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
|
-
|
|
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,
|
|
58
|
-
super(), this.keyStore = keyStore, this.epochCache = epochCache, this.p2pClient = p2pClient, this.
|
|
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
|
|
118
|
-
|
|
119
|
-
|
|
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
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
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
|
-
|
|
135
|
-
return this.
|
|
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.
|
|
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
|
-
//
|
|
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.
|
|
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 {
|
|
238
|
-
const shouldReexecute = fishermanMode || slashBroadcastedInvalidBlockPenalty > 0n
|
|
239
|
-
const validationResult = await this.
|
|
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
|
|
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(
|
|
286
|
-
this.log.warn(`Escape hatch open for slot ${
|
|
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.
|
|
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
|
-
|
|
315
|
+
proposalSlotNumber
|
|
299
316
|
});
|
|
300
317
|
return undefined;
|
|
301
318
|
}
|
|
302
|
-
// Check that I have any address in
|
|
303
|
-
const inCommittee = await this.epochCache.filterInCommittee(
|
|
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
|
-
|
|
323
|
+
proposalSlotNumber,
|
|
307
324
|
archive: proposal.archive.toString(),
|
|
308
|
-
proposer: proposer
|
|
309
|
-
txCount: proposal.txHashes.length
|
|
325
|
+
proposer: proposer?.toString()
|
|
310
326
|
};
|
|
311
|
-
this.log.info(`Received checkpoint proposal for slot ${
|
|
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 ${
|
|
334
|
+
this.log.warn(`Skipping checkpoint proposal validation for slot ${proposalSlotNumber}`, proposalInfo);
|
|
319
335
|
} else {
|
|
320
|
-
const validationResult = await this.
|
|
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 ${
|
|
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 ${
|
|
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,
|
|
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,
|
|
528
|
+
const newProposal = await this.validationService.createCheckpointProposal(checkpointHeader, archive, feeAssetPriceModifier, lastBlockProposal, proposerAddress, options);
|
|
640
529
|
this.lastProposedCheckpoint = newProposal;
|
|
641
530
|
return newProposal;
|
|
642
531
|
}
|