@aztec/validator-client 0.0.1-commit.358457c → 0.0.1-commit.3895657bc
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 +42 -0
- package/dest/block_proposal_handler.d.ts +2 -2
- package/dest/block_proposal_handler.d.ts.map +1 -1
- package/dest/block_proposal_handler.js +45 -26
- package/dest/checkpoint_builder.d.ts +8 -1
- package/dest/checkpoint_builder.d.ts.map +1 -1
- package/dest/checkpoint_builder.js +56 -8
- package/dest/config.d.ts +1 -1
- package/dest/config.d.ts.map +1 -1
- package/dest/config.js +22 -1
- package/dest/duties/validation_service.d.ts +1 -1
- package/dest/duties/validation_service.d.ts.map +1 -1
- package/dest/duties/validation_service.js +2 -8
- package/dest/factory.d.ts +1 -1
- package/dest/factory.d.ts.map +1 -1
- package/dest/factory.js +2 -1
- package/dest/metrics.d.ts +9 -1
- package/dest/metrics.d.ts.map +1 -1
- package/dest/metrics.js +12 -0
- package/dest/validator.d.ts +5 -3
- package/dest/validator.d.ts.map +1 -1
- package/dest/validator.js +50 -25
- package/package.json +19 -19
- package/src/block_proposal_handler.ts +52 -33
- package/src/checkpoint_builder.ts +71 -9
- package/src/config.ts +22 -1
- package/src/duties/validation_service.ts +2 -8
- package/src/factory.ts +1 -0
- package/src/metrics.ts +18 -0
- package/src/validator.ts +48 -27
package/src/factory.ts
CHANGED
|
@@ -29,6 +29,7 @@ export function createBlockProposalHandler(
|
|
|
29
29
|
const metrics = new ValidatorMetrics(deps.telemetry);
|
|
30
30
|
const blockProposalValidator = new BlockProposalValidator(deps.epochCache, {
|
|
31
31
|
txsPermitted: !config.disableTransactions,
|
|
32
|
+
maxTxsPerBlock: config.validateMaxTxsPerBlock,
|
|
32
33
|
});
|
|
33
34
|
return new BlockProposalHandler(
|
|
34
35
|
deps.checkpointsBuilder,
|
package/src/metrics.ts
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import type { EpochNumber } from '@aztec/foundation/branded-types';
|
|
2
|
+
import type { EthAddress } from '@aztec/foundation/eth-address';
|
|
1
3
|
import type { BlockProposal } from '@aztec/stdlib/p2p';
|
|
2
4
|
import {
|
|
3
5
|
Attributes,
|
|
@@ -16,6 +18,8 @@ export class ValidatorMetrics {
|
|
|
16
18
|
private successfulAttestationsCount: UpDownCounter;
|
|
17
19
|
private failedAttestationsBadProposalCount: UpDownCounter;
|
|
18
20
|
private failedAttestationsNodeIssueCount: UpDownCounter;
|
|
21
|
+
private currentEpoch: Gauge;
|
|
22
|
+
private attestedEpochCount: UpDownCounter;
|
|
19
23
|
|
|
20
24
|
private reexMana: Histogram;
|
|
21
25
|
private reexTx: Histogram;
|
|
@@ -64,6 +68,10 @@ export class ValidatorMetrics {
|
|
|
64
68
|
},
|
|
65
69
|
);
|
|
66
70
|
|
|
71
|
+
this.currentEpoch = meter.createGauge(Metrics.VALIDATOR_CURRENT_EPOCH);
|
|
72
|
+
|
|
73
|
+
this.attestedEpochCount = createUpDownCounterWithDefault(meter, Metrics.VALIDATOR_ATTESTED_EPOCH_COUNT);
|
|
74
|
+
|
|
67
75
|
this.reexMana = meter.createHistogram(Metrics.VALIDATOR_RE_EXECUTION_MANA);
|
|
68
76
|
|
|
69
77
|
this.reexTx = meter.createHistogram(Metrics.VALIDATOR_RE_EXECUTION_TX_COUNT);
|
|
@@ -110,4 +118,14 @@ export class ValidatorMetrics {
|
|
|
110
118
|
[Attributes.IS_COMMITTEE_MEMBER]: inCommittee,
|
|
111
119
|
});
|
|
112
120
|
}
|
|
121
|
+
|
|
122
|
+
/** Update the gauge tracking the current epoch number (proxy for total epochs elapsed). */
|
|
123
|
+
public setCurrentEpoch(epoch: EpochNumber) {
|
|
124
|
+
this.currentEpoch.record(Number(epoch));
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/** Increment the count of epochs in which the given attester submitted at least one attestation. */
|
|
128
|
+
public incAttestedEpochCount(attester: EthAddress) {
|
|
129
|
+
this.attestedEpochCount.add(1, { [Attributes.ATTESTER_ADDRESS]: attester.toString() });
|
|
130
|
+
}
|
|
113
131
|
}
|
package/src/validator.ts
CHANGED
|
@@ -24,6 +24,7 @@ import { AuthRequest, AuthResponse, BlockProposalValidator, ReqRespSubProtocol }
|
|
|
24
24
|
import { OffenseType, WANT_TO_SLASH_EVENT, type Watcher, type WatcherEmitter } from '@aztec/slasher';
|
|
25
25
|
import type { AztecAddress } from '@aztec/stdlib/aztec-address';
|
|
26
26
|
import type { CommitteeAttestationsAndSigners, L2Block, L2BlockSink, L2BlockSource } from '@aztec/stdlib/block';
|
|
27
|
+
import { validateCheckpoint } from '@aztec/stdlib/checkpoint';
|
|
27
28
|
import { getEpochAtSlot, getTimestampForSlot } from '@aztec/stdlib/epoch-helpers';
|
|
28
29
|
import type {
|
|
29
30
|
CreateCheckpointProposalLastBlockData,
|
|
@@ -45,7 +46,7 @@ import type { CheckpointHeader } from '@aztec/stdlib/rollup';
|
|
|
45
46
|
import type { BlockHeader, CheckpointGlobalVariables, Tx } from '@aztec/stdlib/tx';
|
|
46
47
|
import { AttestationTimeoutError } from '@aztec/stdlib/validators';
|
|
47
48
|
import { type TelemetryClient, type Tracer, getTelemetryClient } from '@aztec/telemetry-client';
|
|
48
|
-
import { createHASigner } from '@aztec/validator-ha-signer/factory';
|
|
49
|
+
import { createHASigner, createLocalSignerWithProtection } from '@aztec/validator-ha-signer/factory';
|
|
49
50
|
import { DutyType, type SigningContext } from '@aztec/validator-ha-signer/types';
|
|
50
51
|
import type { ValidatorHASigner } from '@aztec/validator-ha-signer/validator-ha-signer';
|
|
51
52
|
|
|
@@ -89,6 +90,8 @@ export class ValidatorClient extends (EventEmitter as new () => WatcherEmitter)
|
|
|
89
90
|
|
|
90
91
|
private lastEpochForCommitteeUpdateLoop: EpochNumber | undefined;
|
|
91
92
|
private epochCacheUpdateLoop: RunningPromise;
|
|
93
|
+
/** Tracks the last epoch in which each attester successfully submitted at least one attestation. */
|
|
94
|
+
private lastAttestedEpochByAttester: Map<string, EpochNumber> = new Map();
|
|
92
95
|
|
|
93
96
|
private proposersOfInvalidBlocks: Set<string> = new Set();
|
|
94
97
|
|
|
@@ -106,7 +109,7 @@ export class ValidatorClient extends (EventEmitter as new () => WatcherEmitter)
|
|
|
106
109
|
private l1ToL2MessageSource: L1ToL2MessageSource,
|
|
107
110
|
private config: ValidatorClientFullConfig,
|
|
108
111
|
private blobClient: BlobClientInterface,
|
|
109
|
-
private
|
|
112
|
+
private slashingProtectionSigner: ValidatorHASigner,
|
|
110
113
|
private dateProvider: DateProvider = new DateProvider(),
|
|
111
114
|
telemetry: TelemetryClient = getTelemetryClient(),
|
|
112
115
|
log = createLogger('validator'),
|
|
@@ -160,6 +163,7 @@ export class ValidatorClient extends (EventEmitter as new () => WatcherEmitter)
|
|
|
160
163
|
this.log.trace(`No committee found for slot`);
|
|
161
164
|
return;
|
|
162
165
|
}
|
|
166
|
+
this.metrics.setCurrentEpoch(epoch);
|
|
163
167
|
if (epoch !== this.lastEpochForCommitteeUpdateLoop) {
|
|
164
168
|
const me = this.getValidatorAddresses();
|
|
165
169
|
const committeeSet = new Set(committee.map(v => v.toString()));
|
|
@@ -197,6 +201,7 @@ export class ValidatorClient extends (EventEmitter as new () => WatcherEmitter)
|
|
|
197
201
|
const metrics = new ValidatorMetrics(telemetry);
|
|
198
202
|
const blockProposalValidator = new BlockProposalValidator(epochCache, {
|
|
199
203
|
txsPermitted: !config.disableTransactions,
|
|
204
|
+
maxTxsPerBlock: config.validateMaxTxsPerBlock,
|
|
200
205
|
});
|
|
201
206
|
const blockProposalHandler = new BlockProposalHandler(
|
|
202
207
|
checkpointsBuilder,
|
|
@@ -213,18 +218,27 @@ export class ValidatorClient extends (EventEmitter as new () => WatcherEmitter)
|
|
|
213
218
|
);
|
|
214
219
|
|
|
215
220
|
const nodeKeystoreAdapter = NodeKeystoreAdapter.fromKeyStoreManager(keyStoreManager);
|
|
216
|
-
let
|
|
217
|
-
let haSigner: ValidatorHASigner | undefined;
|
|
221
|
+
let slashingProtectionSigner: ValidatorHASigner;
|
|
218
222
|
if (config.haSigningEnabled) {
|
|
223
|
+
// Multi-node HA mode: use PostgreSQL-backed distributed locking.
|
|
219
224
|
// If maxStuckDutiesAgeMs is not explicitly set, compute it from Aztec slot duration
|
|
220
225
|
const haConfig = {
|
|
221
226
|
...config,
|
|
222
227
|
maxStuckDutiesAgeMs: config.maxStuckDutiesAgeMs ?? epochCache.getL1Constants().slotDuration * 2 * 1000,
|
|
223
228
|
};
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
229
|
+
({ signer: slashingProtectionSigner } = await createHASigner(haConfig, {
|
|
230
|
+
telemetryClient: telemetry,
|
|
231
|
+
dateProvider,
|
|
232
|
+
}));
|
|
233
|
+
} else {
|
|
234
|
+
// Single-node mode: use LMDB-backed local signing protection.
|
|
235
|
+
// This prevents double-signing if the node crashes and restarts mid-proposal.
|
|
236
|
+
({ signer: slashingProtectionSigner } = await createLocalSignerWithProtection(config, {
|
|
237
|
+
telemetryClient: telemetry,
|
|
238
|
+
dateProvider,
|
|
239
|
+
}));
|
|
227
240
|
}
|
|
241
|
+
const validatorKeyStore: ExtendedValidatorKeyStore = new HAKeyStore(nodeKeystoreAdapter, slashingProtectionSigner);
|
|
228
242
|
|
|
229
243
|
const validator = new ValidatorClient(
|
|
230
244
|
validatorKeyStore,
|
|
@@ -237,7 +251,7 @@ export class ValidatorClient extends (EventEmitter as new () => WatcherEmitter)
|
|
|
237
251
|
l1ToL2MessageSource,
|
|
238
252
|
config,
|
|
239
253
|
blobClient,
|
|
240
|
-
|
|
254
|
+
slashingProtectionSigner,
|
|
241
255
|
dateProvider,
|
|
242
256
|
telemetry,
|
|
243
257
|
);
|
|
@@ -276,24 +290,8 @@ export class ValidatorClient extends (EventEmitter as new () => WatcherEmitter)
|
|
|
276
290
|
}
|
|
277
291
|
|
|
278
292
|
public reloadKeystore(newManager: KeystoreManager): void {
|
|
279
|
-
if (this.config.haSigningEnabled && !this.haSigner) {
|
|
280
|
-
this.log.warn(
|
|
281
|
-
'HA signing is enabled in config but was not initialized at startup. ' +
|
|
282
|
-
'Restart the node to enable HA signing.',
|
|
283
|
-
);
|
|
284
|
-
} else if (!this.config.haSigningEnabled && this.haSigner) {
|
|
285
|
-
this.log.warn(
|
|
286
|
-
'HA signing was disabled via config update but the HA signer is still active. ' +
|
|
287
|
-
'Restart the node to fully disable HA signing.',
|
|
288
|
-
);
|
|
289
|
-
}
|
|
290
|
-
|
|
291
293
|
const newAdapter = NodeKeystoreAdapter.fromKeyStoreManager(newManager);
|
|
292
|
-
|
|
293
|
-
this.keyStore = new HAKeyStore(newAdapter, this.haSigner);
|
|
294
|
-
} else {
|
|
295
|
-
this.keyStore = newAdapter;
|
|
296
|
-
}
|
|
294
|
+
this.keyStore = new HAKeyStore(newAdapter, this.slashingProtectionSigner);
|
|
297
295
|
this.validationService = new ValidationService(this.keyStore, this.log.createChild('validation-service'));
|
|
298
296
|
}
|
|
299
297
|
|
|
@@ -515,11 +513,9 @@ export class ValidatorClient extends (EventEmitter as new () => WatcherEmitter)
|
|
|
515
513
|
slotNumber,
|
|
516
514
|
archive: proposal.archive.toString(),
|
|
517
515
|
proposer: proposer.toString(),
|
|
518
|
-
txCount: proposal.txHashes.length,
|
|
519
516
|
};
|
|
520
517
|
this.log.info(`Received checkpoint proposal for slot ${slotNumber}`, {
|
|
521
518
|
...proposalInfo,
|
|
522
|
-
txHashes: proposal.txHashes.map(t => t.toString()),
|
|
523
519
|
fishermanMode: this.config.fishermanMode || false,
|
|
524
520
|
});
|
|
525
521
|
|
|
@@ -555,6 +551,17 @@ export class ValidatorClient extends (EventEmitter as new () => WatcherEmitter)
|
|
|
555
551
|
|
|
556
552
|
this.metrics.incSuccessfulAttestations(inCommittee.length);
|
|
557
553
|
|
|
554
|
+
// Track epoch participation per attester: count each (attester, epoch) pair at most once
|
|
555
|
+
const proposalEpoch = getEpochAtSlot(slotNumber, this.epochCache.getL1Constants());
|
|
556
|
+
for (const attester of inCommittee) {
|
|
557
|
+
const key = attester.toString();
|
|
558
|
+
const lastEpoch = this.lastAttestedEpochByAttester.get(key);
|
|
559
|
+
if (lastEpoch === undefined || proposalEpoch > lastEpoch) {
|
|
560
|
+
this.lastAttestedEpochByAttester.set(key, proposalEpoch);
|
|
561
|
+
this.metrics.incAttestedEpochCount(attester);
|
|
562
|
+
}
|
|
563
|
+
}
|
|
564
|
+
|
|
558
565
|
// Determine which validators should attest
|
|
559
566
|
let attestors: EthAddress[];
|
|
560
567
|
if (partOfCommittee) {
|
|
@@ -751,6 +758,20 @@ export class ValidatorClient extends (EventEmitter as new () => WatcherEmitter)
|
|
|
751
758
|
return { isValid: false, reason: 'out_hash_mismatch' };
|
|
752
759
|
}
|
|
753
760
|
|
|
761
|
+
// Final round of validations on the checkpoint, just in case.
|
|
762
|
+
try {
|
|
763
|
+
validateCheckpoint(computedCheckpoint, {
|
|
764
|
+
rollupManaLimit: this.checkpointsBuilder.getConfig().rollupManaLimit,
|
|
765
|
+
maxDABlockGas: this.config.validateMaxDABlockGas,
|
|
766
|
+
maxL2BlockGas: this.config.validateMaxL2BlockGas,
|
|
767
|
+
maxTxsPerBlock: this.config.validateMaxTxsPerBlock,
|
|
768
|
+
maxTxsPerCheckpoint: this.config.validateMaxTxsPerCheckpoint,
|
|
769
|
+
});
|
|
770
|
+
} catch (err) {
|
|
771
|
+
this.log.warn(`Checkpoint validation failed: ${err}`, proposalInfo);
|
|
772
|
+
return { isValid: false, reason: 'checkpoint_validation_failed' };
|
|
773
|
+
}
|
|
774
|
+
|
|
754
775
|
this.log.verbose(`Checkpoint proposal validation successful for slot ${slot}`, proposalInfo);
|
|
755
776
|
return { isValid: true };
|
|
756
777
|
} finally {
|