@aztec/validator-client 0.0.1-commit.dbf9cec → 0.0.1-commit.e0f15ab9b
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 -0
- package/dest/block_proposal_handler.d.ts +4 -3
- package/dest/block_proposal_handler.d.ts.map +1 -1
- package/dest/block_proposal_handler.js +112 -30
- package/dest/checkpoint_builder.d.ts +14 -4
- package/dest/checkpoint_builder.d.ts.map +1 -1
- package/dest/checkpoint_builder.js +101 -30
- 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 +3 -9
- package/dest/factory.d.ts +3 -1
- package/dest/factory.d.ts.map +1 -1
- package/dest/factory.js +3 -2
- package/dest/key_store/ha_key_store.js +1 -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 +7 -5
- package/dest/validator.d.ts.map +1 -1
- package/dest/validator.js +75 -45
- package/package.json +19 -19
- package/src/block_proposal_handler.ts +134 -37
- package/src/checkpoint_builder.ts +124 -35
- package/src/config.ts +22 -1
- package/src/duties/validation_service.ts +3 -9
- package/src/factory.ts +4 -0
- package/src/key_store/ha_key_store.ts +1 -1
- package/src/metrics.ts +18 -0
- package/src/validator.ts +87 -52
package/src/factory.ts
CHANGED
|
@@ -7,6 +7,7 @@ import type { L2BlockSink, L2BlockSource } from '@aztec/stdlib/block';
|
|
|
7
7
|
import type { ValidatorClientFullConfig, WorldStateSynchronizer } from '@aztec/stdlib/interfaces/server';
|
|
8
8
|
import type { L1ToL2MessageSource } from '@aztec/stdlib/messaging';
|
|
9
9
|
import type { TelemetryClient } from '@aztec/telemetry-client';
|
|
10
|
+
import type { SlashingProtectionDatabase } from '@aztec/validator-ha-signer/types';
|
|
10
11
|
|
|
11
12
|
import { BlockProposalHandler } from './block_proposal_handler.js';
|
|
12
13
|
import type { FullNodeCheckpointsBuilder } from './checkpoint_builder.js';
|
|
@@ -29,6 +30,7 @@ export function createBlockProposalHandler(
|
|
|
29
30
|
const metrics = new ValidatorMetrics(deps.telemetry);
|
|
30
31
|
const blockProposalValidator = new BlockProposalValidator(deps.epochCache, {
|
|
31
32
|
txsPermitted: !config.disableTransactions,
|
|
33
|
+
maxTxsPerBlock: config.validateMaxTxsPerBlock ?? config.validateMaxTxsPerCheckpoint,
|
|
32
34
|
});
|
|
33
35
|
return new BlockProposalHandler(
|
|
34
36
|
deps.checkpointsBuilder,
|
|
@@ -58,6 +60,7 @@ export function createValidatorClient(
|
|
|
58
60
|
epochCache: EpochCache;
|
|
59
61
|
keyStoreManager: KeystoreManager | undefined;
|
|
60
62
|
blobClient: BlobClientInterface;
|
|
63
|
+
slashingProtectionDb?: SlashingProtectionDatabase;
|
|
61
64
|
},
|
|
62
65
|
) {
|
|
63
66
|
if (config.disableValidator || !deps.keyStoreManager) {
|
|
@@ -78,5 +81,6 @@ export function createValidatorClient(
|
|
|
78
81
|
deps.blobClient,
|
|
79
82
|
deps.dateProvider,
|
|
80
83
|
deps.telemetry,
|
|
84
|
+
deps.slashingProtectionDb,
|
|
81
85
|
);
|
|
82
86
|
}
|
|
@@ -240,7 +240,7 @@ export class HAKeyStore implements ExtendedValidatorKeyStore {
|
|
|
240
240
|
}
|
|
241
241
|
|
|
242
242
|
if (error instanceof SlashingProtectionError) {
|
|
243
|
-
this.log.
|
|
243
|
+
this.log.info(`Duty already signed by another node with different payload`, {
|
|
244
244
|
dutyType: context.dutyType,
|
|
245
245
|
slot: context.slot,
|
|
246
246
|
existingMessageHash: error.existingMessageHash,
|
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,8 +46,12 @@ 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 {
|
|
49
|
-
|
|
49
|
+
import {
|
|
50
|
+
createHASigner,
|
|
51
|
+
createLocalSignerWithProtection,
|
|
52
|
+
createSignerFromSharedDb,
|
|
53
|
+
} from '@aztec/validator-ha-signer/factory';
|
|
54
|
+
import { DutyType, type SigningContext, type SlashingProtectionDatabase } from '@aztec/validator-ha-signer/types';
|
|
50
55
|
import type { ValidatorHASigner } from '@aztec/validator-ha-signer/validator-ha-signer';
|
|
51
56
|
|
|
52
57
|
import { EventEmitter } from 'events';
|
|
@@ -89,6 +94,8 @@ export class ValidatorClient extends (EventEmitter as new () => WatcherEmitter)
|
|
|
89
94
|
|
|
90
95
|
private lastEpochForCommitteeUpdateLoop: EpochNumber | undefined;
|
|
91
96
|
private epochCacheUpdateLoop: RunningPromise;
|
|
97
|
+
/** Tracks the last epoch in which each attester successfully submitted at least one attestation. */
|
|
98
|
+
private lastAttestedEpochByAttester: Map<string, EpochNumber> = new Map();
|
|
92
99
|
|
|
93
100
|
private proposersOfInvalidBlocks: Set<string> = new Set();
|
|
94
101
|
|
|
@@ -106,7 +113,7 @@ export class ValidatorClient extends (EventEmitter as new () => WatcherEmitter)
|
|
|
106
113
|
private l1ToL2MessageSource: L1ToL2MessageSource,
|
|
107
114
|
private config: ValidatorClientFullConfig,
|
|
108
115
|
private blobClient: BlobClientInterface,
|
|
109
|
-
private
|
|
116
|
+
private slashingProtectionSigner: ValidatorHASigner,
|
|
110
117
|
private dateProvider: DateProvider = new DateProvider(),
|
|
111
118
|
telemetry: TelemetryClient = getTelemetryClient(),
|
|
112
119
|
log = createLogger('validator'),
|
|
@@ -160,6 +167,7 @@ export class ValidatorClient extends (EventEmitter as new () => WatcherEmitter)
|
|
|
160
167
|
this.log.trace(`No committee found for slot`);
|
|
161
168
|
return;
|
|
162
169
|
}
|
|
170
|
+
this.metrics.setCurrentEpoch(epoch);
|
|
163
171
|
if (epoch !== this.lastEpochForCommitteeUpdateLoop) {
|
|
164
172
|
const me = this.getValidatorAddresses();
|
|
165
173
|
const committeeSet = new Set(committee.map(v => v.toString()));
|
|
@@ -193,10 +201,12 @@ export class ValidatorClient extends (EventEmitter as new () => WatcherEmitter)
|
|
|
193
201
|
blobClient: BlobClientInterface,
|
|
194
202
|
dateProvider: DateProvider = new DateProvider(),
|
|
195
203
|
telemetry: TelemetryClient = getTelemetryClient(),
|
|
204
|
+
slashingProtectionDb?: SlashingProtectionDatabase,
|
|
196
205
|
) {
|
|
197
206
|
const metrics = new ValidatorMetrics(telemetry);
|
|
198
207
|
const blockProposalValidator = new BlockProposalValidator(epochCache, {
|
|
199
208
|
txsPermitted: !config.disableTransactions,
|
|
209
|
+
maxTxsPerBlock: config.validateMaxTxsPerBlock,
|
|
200
210
|
});
|
|
201
211
|
const blockProposalHandler = new BlockProposalHandler(
|
|
202
212
|
checkpointsBuilder,
|
|
@@ -213,18 +223,33 @@ export class ValidatorClient extends (EventEmitter as new () => WatcherEmitter)
|
|
|
213
223
|
);
|
|
214
224
|
|
|
215
225
|
const nodeKeystoreAdapter = NodeKeystoreAdapter.fromKeyStoreManager(keyStoreManager);
|
|
216
|
-
let
|
|
217
|
-
|
|
218
|
-
|
|
226
|
+
let slashingProtectionSigner: ValidatorHASigner;
|
|
227
|
+
if (slashingProtectionDb) {
|
|
228
|
+
// Shared database mode: use a pre-existing database (e.g. for testing HA setups).
|
|
229
|
+
({ signer: slashingProtectionSigner } = createSignerFromSharedDb(slashingProtectionDb, config, {
|
|
230
|
+
telemetryClient: telemetry,
|
|
231
|
+
dateProvider,
|
|
232
|
+
}));
|
|
233
|
+
} else if (config.haSigningEnabled) {
|
|
234
|
+
// Multi-node HA mode: use PostgreSQL-backed distributed locking.
|
|
219
235
|
// If maxStuckDutiesAgeMs is not explicitly set, compute it from Aztec slot duration
|
|
220
236
|
const haConfig = {
|
|
221
237
|
...config,
|
|
222
238
|
maxStuckDutiesAgeMs: config.maxStuckDutiesAgeMs ?? epochCache.getL1Constants().slotDuration * 2 * 1000,
|
|
223
239
|
};
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
240
|
+
({ signer: slashingProtectionSigner } = await createHASigner(haConfig, {
|
|
241
|
+
telemetryClient: telemetry,
|
|
242
|
+
dateProvider,
|
|
243
|
+
}));
|
|
244
|
+
} else {
|
|
245
|
+
// Single-node mode: use LMDB-backed local signing protection.
|
|
246
|
+
// This prevents double-signing if the node crashes and restarts mid-proposal.
|
|
247
|
+
({ signer: slashingProtectionSigner } = await createLocalSignerWithProtection(config, {
|
|
248
|
+
telemetryClient: telemetry,
|
|
249
|
+
dateProvider,
|
|
250
|
+
}));
|
|
227
251
|
}
|
|
252
|
+
const validatorKeyStore: ExtendedValidatorKeyStore = new HAKeyStore(nodeKeystoreAdapter, slashingProtectionSigner);
|
|
228
253
|
|
|
229
254
|
const validator = new ValidatorClient(
|
|
230
255
|
validatorKeyStore,
|
|
@@ -237,7 +262,7 @@ export class ValidatorClient extends (EventEmitter as new () => WatcherEmitter)
|
|
|
237
262
|
l1ToL2MessageSource,
|
|
238
263
|
config,
|
|
239
264
|
blobClient,
|
|
240
|
-
|
|
265
|
+
slashingProtectionSigner,
|
|
241
266
|
dateProvider,
|
|
242
267
|
telemetry,
|
|
243
268
|
);
|
|
@@ -276,24 +301,8 @@ export class ValidatorClient extends (EventEmitter as new () => WatcherEmitter)
|
|
|
276
301
|
}
|
|
277
302
|
|
|
278
303
|
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
304
|
const newAdapter = NodeKeystoreAdapter.fromKeyStoreManager(newManager);
|
|
292
|
-
|
|
293
|
-
this.keyStore = new HAKeyStore(newAdapter, this.haSigner);
|
|
294
|
-
} else {
|
|
295
|
-
this.keyStore = newAdapter;
|
|
296
|
-
}
|
|
305
|
+
this.keyStore = new HAKeyStore(newAdapter, this.slashingProtectionSigner);
|
|
297
306
|
this.validationService = new ValidationService(this.keyStore, this.log.createChild('validation-service'));
|
|
298
307
|
}
|
|
299
308
|
|
|
@@ -380,13 +389,12 @@ export class ValidatorClient extends (EventEmitter as new () => WatcherEmitter)
|
|
|
380
389
|
return false;
|
|
381
390
|
}
|
|
382
391
|
|
|
383
|
-
//
|
|
392
|
+
// Log self-proposals from HA peers (same validator key on different nodes)
|
|
384
393
|
if (this.getValidatorAddresses().some(addr => addr.equals(proposer))) {
|
|
385
|
-
this.log.
|
|
394
|
+
this.log.verbose(`Processing block proposal from HA peer for slot ${slotNumber}`, {
|
|
386
395
|
proposer: proposer.toString(),
|
|
387
396
|
slotNumber,
|
|
388
397
|
});
|
|
389
|
-
return false;
|
|
390
398
|
}
|
|
391
399
|
|
|
392
400
|
// Check if we're in the committee (for metrics purposes)
|
|
@@ -418,9 +426,10 @@ export class ValidatorClient extends (EventEmitter as new () => WatcherEmitter)
|
|
|
418
426
|
);
|
|
419
427
|
|
|
420
428
|
if (!validationResult.isValid) {
|
|
421
|
-
this.log.warn(`Block proposal validation failed: ${validationResult.reason}`, proposalInfo);
|
|
422
|
-
|
|
423
429
|
const reason = validationResult.reason || 'unknown';
|
|
430
|
+
|
|
431
|
+
this.log.warn(`Block proposal validation failed: ${reason}`, proposalInfo);
|
|
432
|
+
|
|
424
433
|
// Classify failure reason: bad proposal vs node issue
|
|
425
434
|
const badProposalReasons: BlockProposalValidationFailureReason[] = [
|
|
426
435
|
'invalid_proposal',
|
|
@@ -475,26 +484,26 @@ export class ValidatorClient extends (EventEmitter as new () => WatcherEmitter)
|
|
|
475
484
|
proposal: CheckpointProposalCore,
|
|
476
485
|
_proposalSender: PeerId,
|
|
477
486
|
): Promise<CheckpointAttestation[] | undefined> {
|
|
478
|
-
const
|
|
487
|
+
const proposalSlotNumber = proposal.slotNumber;
|
|
479
488
|
const proposer = proposal.getSender();
|
|
480
489
|
|
|
481
490
|
// If escape hatch is open for this slot's epoch, do not attest.
|
|
482
|
-
if (await this.epochCache.isEscapeHatchOpenAtSlot(
|
|
483
|
-
this.log.warn(`Escape hatch open for slot ${
|
|
491
|
+
if (await this.epochCache.isEscapeHatchOpenAtSlot(proposalSlotNumber)) {
|
|
492
|
+
this.log.warn(`Escape hatch open for slot ${proposalSlotNumber}, skipping checkpoint attestation handling`);
|
|
484
493
|
return undefined;
|
|
485
494
|
}
|
|
486
495
|
|
|
487
496
|
// Reject proposals with invalid signatures
|
|
488
497
|
if (!proposer) {
|
|
489
|
-
this.log.warn(`Received checkpoint proposal with invalid signature for slot ${
|
|
498
|
+
this.log.warn(`Received checkpoint proposal with invalid signature for proposal slot ${proposalSlotNumber}`);
|
|
490
499
|
return undefined;
|
|
491
500
|
}
|
|
492
501
|
|
|
493
502
|
// Ignore proposals from ourselves (may happen in HA setups)
|
|
494
503
|
if (this.getValidatorAddresses().some(addr => addr.equals(proposer))) {
|
|
495
|
-
this.log.
|
|
504
|
+
this.log.debug(`Ignoring block proposal from self for slot ${proposalSlotNumber}`, {
|
|
496
505
|
proposer: proposer.toString(),
|
|
497
|
-
|
|
506
|
+
proposalSlotNumber,
|
|
498
507
|
});
|
|
499
508
|
return undefined;
|
|
500
509
|
}
|
|
@@ -502,30 +511,28 @@ export class ValidatorClient extends (EventEmitter as new () => WatcherEmitter)
|
|
|
502
511
|
// Validate fee asset price modifier is within allowed range
|
|
503
512
|
if (!validateFeeAssetPriceModifier(proposal.feeAssetPriceModifier)) {
|
|
504
513
|
this.log.warn(
|
|
505
|
-
`Received checkpoint proposal with invalid feeAssetPriceModifier ${proposal.feeAssetPriceModifier} for slot ${
|
|
514
|
+
`Received checkpoint proposal with invalid feeAssetPriceModifier ${proposal.feeAssetPriceModifier} for slot ${proposalSlotNumber}`,
|
|
506
515
|
);
|
|
507
516
|
return undefined;
|
|
508
517
|
}
|
|
509
518
|
|
|
510
|
-
// Check that I have any address in
|
|
511
|
-
const inCommittee = await this.epochCache.filterInCommittee(
|
|
519
|
+
// Check that I have any address in the committee where this checkpoint will land before attesting
|
|
520
|
+
const inCommittee = await this.epochCache.filterInCommittee(proposalSlotNumber, this.getValidatorAddresses());
|
|
512
521
|
const partOfCommittee = inCommittee.length > 0;
|
|
513
522
|
|
|
514
523
|
const proposalInfo = {
|
|
515
|
-
|
|
524
|
+
proposalSlotNumber,
|
|
516
525
|
archive: proposal.archive.toString(),
|
|
517
526
|
proposer: proposer.toString(),
|
|
518
|
-
txCount: proposal.txHashes.length,
|
|
519
527
|
};
|
|
520
|
-
this.log.info(`Received checkpoint proposal for slot ${
|
|
528
|
+
this.log.info(`Received checkpoint proposal for slot ${proposalSlotNumber}`, {
|
|
521
529
|
...proposalInfo,
|
|
522
|
-
txHashes: proposal.txHashes.map(t => t.toString()),
|
|
523
530
|
fishermanMode: this.config.fishermanMode || false,
|
|
524
531
|
});
|
|
525
532
|
|
|
526
533
|
// Validate the checkpoint proposal before attesting (unless skipCheckpointProposalValidation is set)
|
|
527
534
|
if (this.config.skipCheckpointProposalValidation) {
|
|
528
|
-
this.log.warn(`Skipping checkpoint proposal validation for slot ${
|
|
535
|
+
this.log.warn(`Skipping checkpoint proposal validation for slot ${proposalSlotNumber}`, proposalInfo);
|
|
529
536
|
} else {
|
|
530
537
|
const validationResult = await this.validateCheckpointProposal(proposal, proposalInfo);
|
|
531
538
|
if (!validationResult.isValid) {
|
|
@@ -547,14 +554,28 @@ export class ValidatorClient extends (EventEmitter as new () => WatcherEmitter)
|
|
|
547
554
|
}
|
|
548
555
|
|
|
549
556
|
// Provided all of the above checks pass, we can attest to the proposal
|
|
550
|
-
this.log.info(
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
557
|
+
this.log.info(
|
|
558
|
+
`${partOfCommittee ? 'Attesting to' : 'Validated'} checkpoint proposal for slot ${proposalSlotNumber}`,
|
|
559
|
+
{
|
|
560
|
+
...proposalInfo,
|
|
561
|
+
inCommittee: partOfCommittee,
|
|
562
|
+
fishermanMode: this.config.fishermanMode || false,
|
|
563
|
+
},
|
|
564
|
+
);
|
|
555
565
|
|
|
556
566
|
this.metrics.incSuccessfulAttestations(inCommittee.length);
|
|
557
567
|
|
|
568
|
+
// Track epoch participation per attester: count each (attester, epoch) pair at most once
|
|
569
|
+
const proposalEpoch = getEpochAtSlot(proposalSlotNumber, this.epochCache.getL1Constants());
|
|
570
|
+
for (const attester of inCommittee) {
|
|
571
|
+
const key = attester.toString();
|
|
572
|
+
const lastEpoch = this.lastAttestedEpochByAttester.get(key);
|
|
573
|
+
if (lastEpoch === undefined || proposalEpoch > lastEpoch) {
|
|
574
|
+
this.lastAttestedEpochByAttester.set(key, proposalEpoch);
|
|
575
|
+
this.metrics.incAttestedEpochCount(attester);
|
|
576
|
+
}
|
|
577
|
+
}
|
|
578
|
+
|
|
558
579
|
// Determine which validators should attest
|
|
559
580
|
let attestors: EthAddress[];
|
|
560
581
|
if (partOfCommittee) {
|
|
@@ -573,7 +594,7 @@ export class ValidatorClient extends (EventEmitter as new () => WatcherEmitter)
|
|
|
573
594
|
|
|
574
595
|
if (this.config.fishermanMode) {
|
|
575
596
|
// bail out early and don't save attestations to the pool in fisherman mode
|
|
576
|
-
this.log.info(`Creating checkpoint attestations for slot ${
|
|
597
|
+
this.log.info(`Creating checkpoint attestations for slot ${proposalSlotNumber}`, {
|
|
577
598
|
...proposalInfo,
|
|
578
599
|
attestors: attestors.map(a => a.toString()),
|
|
579
600
|
});
|
|
@@ -751,6 +772,20 @@ export class ValidatorClient extends (EventEmitter as new () => WatcherEmitter)
|
|
|
751
772
|
return { isValid: false, reason: 'out_hash_mismatch' };
|
|
752
773
|
}
|
|
753
774
|
|
|
775
|
+
// Final round of validations on the checkpoint, just in case.
|
|
776
|
+
try {
|
|
777
|
+
validateCheckpoint(computedCheckpoint, {
|
|
778
|
+
rollupManaLimit: this.checkpointsBuilder.getConfig().rollupManaLimit,
|
|
779
|
+
maxDABlockGas: this.config.validateMaxDABlockGas,
|
|
780
|
+
maxL2BlockGas: this.config.validateMaxL2BlockGas,
|
|
781
|
+
maxTxsPerBlock: this.config.validateMaxTxsPerBlock,
|
|
782
|
+
maxTxsPerCheckpoint: this.config.validateMaxTxsPerCheckpoint,
|
|
783
|
+
});
|
|
784
|
+
} catch (err) {
|
|
785
|
+
this.log.warn(`Checkpoint validation failed: ${err}`, proposalInfo);
|
|
786
|
+
return { isValid: false, reason: 'checkpoint_validation_failed' };
|
|
787
|
+
}
|
|
788
|
+
|
|
754
789
|
this.log.verbose(`Checkpoint proposal validation successful for slot ${slot}`, proposalInfo);
|
|
755
790
|
return { isValid: true };
|
|
756
791
|
} finally {
|