@aztec/validator-client 3.0.0-devnet.5 → 3.0.0-devnet.6-patch.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dest/block_proposal_handler.d.ts +7 -6
- package/dest/block_proposal_handler.d.ts.map +1 -1
- package/dest/block_proposal_handler.js +17 -13
- package/dest/config.d.ts +1 -1
- package/dest/config.d.ts.map +1 -1
- package/dest/config.js +5 -0
- package/dest/duties/validation_service.d.ts +4 -4
- package/dest/duties/validation_service.d.ts.map +1 -1
- package/dest/duties/validation_service.js +7 -8
- package/dest/factory.d.ts +1 -1
- package/dest/index.d.ts +1 -1
- package/dest/key_store/index.d.ts +1 -1
- package/dest/key_store/interface.d.ts +1 -1
- package/dest/key_store/local_key_store.d.ts +1 -1
- package/dest/key_store/local_key_store.d.ts.map +1 -1
- package/dest/key_store/local_key_store.js +1 -1
- package/dest/key_store/node_keystore_adapter.d.ts +1 -1
- package/dest/key_store/node_keystore_adapter.d.ts.map +1 -1
- package/dest/key_store/web3signer_key_store.d.ts +1 -7
- package/dest/key_store/web3signer_key_store.d.ts.map +1 -1
- package/dest/key_store/web3signer_key_store.js +7 -8
- package/dest/metrics.d.ts +3 -3
- package/dest/metrics.d.ts.map +1 -1
- package/dest/metrics.js +6 -4
- package/dest/validator.d.ts +7 -6
- package/dest/validator.d.ts.map +1 -1
- package/dest/validator.js +72 -38
- package/package.json +14 -14
- package/src/block_proposal_handler.ts +26 -23
- package/src/config.ts +6 -0
- package/src/duties/validation_service.ts +7 -10
- package/src/key_store/local_key_store.ts +1 -1
- package/src/key_store/node_keystore_adapter.ts +1 -1
- package/src/key_store/web3signer_key_store.ts +7 -10
- package/src/metrics.ts +4 -2
- package/src/validator.ts +87 -52
package/src/validator.ts
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import type { EpochCache } from '@aztec/epoch-cache';
|
|
2
|
+
import { BlockNumber, EpochNumber } from '@aztec/foundation/branded-types';
|
|
3
|
+
import { Fr } from '@aztec/foundation/curves/bn254';
|
|
2
4
|
import type { EthAddress } from '@aztec/foundation/eth-address';
|
|
3
5
|
import type { Signature } from '@aztec/foundation/eth-signature';
|
|
4
|
-
import { Fr } from '@aztec/foundation/fields';
|
|
5
6
|
import { type Logger, createLogger } from '@aztec/foundation/log';
|
|
6
7
|
import { RunningPromise } from '@aztec/foundation/running-promise';
|
|
7
8
|
import { sleep } from '@aztec/foundation/sleep';
|
|
@@ -16,7 +17,7 @@ import type { IFullNodeBlockBuilder, Validator, ValidatorClientFullConfig } from
|
|
|
16
17
|
import type { L1ToL2MessageSource } from '@aztec/stdlib/messaging';
|
|
17
18
|
import type { BlockAttestation, BlockProposal, BlockProposalOptions } from '@aztec/stdlib/p2p';
|
|
18
19
|
import type { CheckpointHeader } from '@aztec/stdlib/rollup';
|
|
19
|
-
import type {
|
|
20
|
+
import type { Tx } from '@aztec/stdlib/tx';
|
|
20
21
|
import { AttestationTimeoutError } from '@aztec/stdlib/validators';
|
|
21
22
|
import { type TelemetryClient, type Tracer, getTelemetryClient } from '@aztec/telemetry-client';
|
|
22
23
|
|
|
@@ -45,6 +46,7 @@ export class ValidatorClient extends (EventEmitter as new () => WatcherEmitter)
|
|
|
45
46
|
public readonly tracer: Tracer;
|
|
46
47
|
private validationService: ValidationService;
|
|
47
48
|
private metrics: ValidatorMetrics;
|
|
49
|
+
private log: Logger;
|
|
48
50
|
|
|
49
51
|
// Whether it has already registered handlers on the p2p client
|
|
50
52
|
private hasRegisteredHandlers = false;
|
|
@@ -52,7 +54,7 @@ export class ValidatorClient extends (EventEmitter as new () => WatcherEmitter)
|
|
|
52
54
|
// Used to check if we are sending the same proposal twice
|
|
53
55
|
private previousProposal?: BlockProposal;
|
|
54
56
|
|
|
55
|
-
private lastEpochForCommitteeUpdateLoop:
|
|
57
|
+
private lastEpochForCommitteeUpdateLoop: EpochNumber | undefined;
|
|
56
58
|
private epochCacheUpdateLoop: RunningPromise;
|
|
57
59
|
|
|
58
60
|
private proposersOfInvalidBlocks: Set<string> = new Set();
|
|
@@ -65,16 +67,20 @@ export class ValidatorClient extends (EventEmitter as new () => WatcherEmitter)
|
|
|
65
67
|
private config: ValidatorClientFullConfig,
|
|
66
68
|
private dateProvider: DateProvider = new DateProvider(),
|
|
67
69
|
telemetry: TelemetryClient = getTelemetryClient(),
|
|
68
|
-
|
|
70
|
+
log = createLogger('validator'),
|
|
69
71
|
) {
|
|
70
72
|
super();
|
|
73
|
+
|
|
74
|
+
// Create child logger with fisherman prefix if in fisherman mode
|
|
75
|
+
this.log = config.fishermanMode ? log.createChild('[FISHERMAN]') : log;
|
|
76
|
+
|
|
71
77
|
this.tracer = telemetry.getTracer('Validator');
|
|
72
78
|
this.metrics = new ValidatorMetrics(telemetry);
|
|
73
79
|
|
|
74
|
-
this.validationService = new ValidationService(keyStore, log.createChild('validation-service'));
|
|
80
|
+
this.validationService = new ValidationService(keyStore, this.log.createChild('validation-service'));
|
|
75
81
|
|
|
76
82
|
// Refresh epoch cache every second to trigger alert if participation in committee changes
|
|
77
|
-
this.epochCacheUpdateLoop = new RunningPromise(this.handleEpochCommitteeUpdate.bind(this), log, 1000);
|
|
83
|
+
this.epochCacheUpdateLoop = new RunningPromise(this.handleEpochCommitteeUpdate.bind(this), this.log, 1000);
|
|
78
84
|
|
|
79
85
|
const myAddresses = this.getValidatorAddresses();
|
|
80
86
|
this.log.verbose(`Initialized validator with addresses: ${myAddresses.map(a => a.toString()).join(', ')}`);
|
|
@@ -186,7 +192,7 @@ export class ValidatorClient extends (EventEmitter as new () => WatcherEmitter)
|
|
|
186
192
|
// Proxy method for backwards compatibility with tests
|
|
187
193
|
public reExecuteTransactions(
|
|
188
194
|
proposal: BlockProposal,
|
|
189
|
-
blockNumber:
|
|
195
|
+
blockNumber: BlockNumber,
|
|
190
196
|
txs: any[],
|
|
191
197
|
l1ToL2Messages: Fr[],
|
|
192
198
|
): Promise<any> {
|
|
@@ -223,14 +229,9 @@ export class ValidatorClient extends (EventEmitter as new () => WatcherEmitter)
|
|
|
223
229
|
|
|
224
230
|
const myAddresses = this.getValidatorAddresses();
|
|
225
231
|
const inCommittee = await this.epochCache.filterInCommittee('now', myAddresses);
|
|
232
|
+
this.log.info(`Started validator with addresses: ${myAddresses.map(a => a.toString()).join(', ')}`);
|
|
226
233
|
if (inCommittee.length > 0) {
|
|
227
|
-
this.log.info(
|
|
228
|
-
`Started validator with addresses in current validator committee: ${inCommittee
|
|
229
|
-
.map(a => a.toString())
|
|
230
|
-
.join(', ')}`,
|
|
231
|
-
);
|
|
232
|
-
} else {
|
|
233
|
-
this.log.info(`Started validator with addresses: ${myAddresses.map(a => a.toString()).join(', ')}`);
|
|
234
|
+
this.log.info(`Addresses in current validator committee: ${inCommittee.map(a => a.toString()).join(', ')}`);
|
|
234
235
|
}
|
|
235
236
|
this.epochCacheUpdateLoop.start();
|
|
236
237
|
|
|
@@ -259,7 +260,7 @@ export class ValidatorClient extends (EventEmitter as new () => WatcherEmitter)
|
|
|
259
260
|
}
|
|
260
261
|
|
|
261
262
|
async attestToProposal(proposal: BlockProposal, proposalSender: PeerId): Promise<BlockAttestation[] | undefined> {
|
|
262
|
-
const slotNumber = proposal.slotNumber
|
|
263
|
+
const slotNumber = proposal.slotNumber;
|
|
263
264
|
const proposer = proposal.getSender();
|
|
264
265
|
|
|
265
266
|
// Reject proposals with invalid signatures
|
|
@@ -276,12 +277,16 @@ export class ValidatorClient extends (EventEmitter as new () => WatcherEmitter)
|
|
|
276
277
|
this.log.info(`Received proposal for slot ${slotNumber}`, {
|
|
277
278
|
...proposalInfo,
|
|
278
279
|
txHashes: proposal.txHashes.map(t => t.toString()),
|
|
280
|
+
fishermanMode: this.config.fishermanMode || false,
|
|
279
281
|
});
|
|
280
282
|
|
|
281
283
|
// Reexecute txs if we are part of the committee so we can attest, or if slashing is enabled so we can slash
|
|
282
284
|
// invalid proposals even when not in the committee, or if we are configured to always reexecute for monitoring purposes.
|
|
283
|
-
|
|
285
|
+
// In fisherman mode, we always reexecute to validate proposals.
|
|
286
|
+
const { validatorReexecute, slashBroadcastedInvalidBlockPenalty, alwaysReexecuteBlockProposals, fishermanMode } =
|
|
287
|
+
this.config;
|
|
284
288
|
const shouldReexecute =
|
|
289
|
+
fishermanMode ||
|
|
285
290
|
(slashBroadcastedInvalidBlockPenalty > 0n && validatorReexecute) ||
|
|
286
291
|
(partOfCommittee && validatorReexecute) ||
|
|
287
292
|
alwaysReexecuteBlockProposals;
|
|
@@ -295,24 +300,21 @@ export class ValidatorClient extends (EventEmitter as new () => WatcherEmitter)
|
|
|
295
300
|
if (!validationResult.isValid) {
|
|
296
301
|
this.log.warn(`Proposal validation failed: ${validationResult.reason}`, proposalInfo);
|
|
297
302
|
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
// Node issues: parent_block_not_found, block_number_already_exists, txs_not_available, timeout, unknown_error
|
|
314
|
-
this.metrics.incFailedAttestationsNodeIssue(1, reason);
|
|
315
|
-
}
|
|
303
|
+
const reason = validationResult.reason || 'unknown';
|
|
304
|
+
// Classify failure reason: bad proposal vs node issue
|
|
305
|
+
const badProposalReasons: BlockProposalValidationFailureReason[] = [
|
|
306
|
+
'invalid_proposal',
|
|
307
|
+
'state_mismatch',
|
|
308
|
+
'failed_txs',
|
|
309
|
+
'in_hash_mismatch',
|
|
310
|
+
'parent_block_wrong_slot',
|
|
311
|
+
];
|
|
312
|
+
|
|
313
|
+
if (badProposalReasons.includes(reason as BlockProposalValidationFailureReason)) {
|
|
314
|
+
this.metrics.incFailedAttestationsBadProposal(1, reason, partOfCommittee);
|
|
315
|
+
} else {
|
|
316
|
+
// Node issues so we can't attest
|
|
317
|
+
this.metrics.incFailedAttestationsNodeIssue(1, reason, partOfCommittee);
|
|
316
318
|
}
|
|
317
319
|
|
|
318
320
|
// Slash invalid block proposals (can happen even when not in committee)
|
|
@@ -328,17 +330,47 @@ export class ValidatorClient extends (EventEmitter as new () => WatcherEmitter)
|
|
|
328
330
|
}
|
|
329
331
|
|
|
330
332
|
// Check that I have any address in current committee before attesting
|
|
331
|
-
if
|
|
333
|
+
// In fisherman mode, we still create attestations for validation even if not in committee
|
|
334
|
+
if (!partOfCommittee && !this.config.fishermanMode) {
|
|
332
335
|
this.log.verbose(`No validator in the current committee, skipping attestation`, proposalInfo);
|
|
333
336
|
return undefined;
|
|
334
337
|
}
|
|
335
338
|
|
|
336
339
|
// Provided all of the above checks pass, we can attest to the proposal
|
|
337
|
-
this.log.info(
|
|
340
|
+
this.log.info(`${partOfCommittee ? 'Attesting to' : 'Validated'} proposal for slot ${slotNumber}`, {
|
|
341
|
+
...proposalInfo,
|
|
342
|
+
inCommittee: partOfCommittee,
|
|
343
|
+
fishermanMode: this.config.fishermanMode || false,
|
|
344
|
+
});
|
|
345
|
+
|
|
338
346
|
this.metrics.incSuccessfulAttestations(inCommittee.length);
|
|
339
347
|
|
|
340
348
|
// If the above function does not throw an error, then we can attest to the proposal
|
|
341
|
-
|
|
349
|
+
// Determine which validators should attest
|
|
350
|
+
let attestors: EthAddress[];
|
|
351
|
+
if (partOfCommittee) {
|
|
352
|
+
attestors = inCommittee;
|
|
353
|
+
} else if (this.config.fishermanMode) {
|
|
354
|
+
// In fisherman mode, create attestations for validation purposes even if not in committee. These won't be broadcast.
|
|
355
|
+
attestors = this.getValidatorAddresses();
|
|
356
|
+
} else {
|
|
357
|
+
attestors = [];
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
// Only create attestations if we have attestors
|
|
361
|
+
if (attestors.length === 0) {
|
|
362
|
+
return undefined;
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
if (this.config.fishermanMode) {
|
|
366
|
+
// bail out early and don't save attestations to the pool in fisherman mode
|
|
367
|
+
this.log.info(`Creating attestations for proposal for slot ${slotNumber}`, {
|
|
368
|
+
...proposalInfo,
|
|
369
|
+
attestors: attestors.map(a => a.toString()),
|
|
370
|
+
});
|
|
371
|
+
return undefined;
|
|
372
|
+
}
|
|
373
|
+
return this.createBlockAttestationsFromProposal(proposal, attestors);
|
|
342
374
|
}
|
|
343
375
|
|
|
344
376
|
private slashInvalidBlock(proposal: BlockProposal) {
|
|
@@ -363,33 +395,28 @@ export class ValidatorClient extends (EventEmitter as new () => WatcherEmitter)
|
|
|
363
395
|
validator: proposer,
|
|
364
396
|
amount: this.config.slashBroadcastedInvalidBlockPenalty,
|
|
365
397
|
offenseType: OffenseType.BROADCASTED_INVALID_BLOCK_PROPOSAL,
|
|
366
|
-
epochOrSlot: proposal.slotNumber
|
|
398
|
+
epochOrSlot: BigInt(proposal.slotNumber),
|
|
367
399
|
},
|
|
368
400
|
]);
|
|
369
401
|
}
|
|
370
402
|
|
|
371
403
|
async createBlockProposal(
|
|
372
|
-
blockNumber:
|
|
404
|
+
blockNumber: BlockNumber,
|
|
373
405
|
header: CheckpointHeader,
|
|
374
406
|
archive: Fr,
|
|
375
|
-
stateReference: StateReference,
|
|
376
407
|
txs: Tx[],
|
|
377
408
|
proposerAddress: EthAddress | undefined,
|
|
378
409
|
options: BlockProposalOptions,
|
|
379
410
|
): Promise<BlockProposal | undefined> {
|
|
380
|
-
if (this.previousProposal?.slotNumber
|
|
411
|
+
if (this.previousProposal?.slotNumber === header.slotNumber) {
|
|
381
412
|
this.log.verbose(`Already made a proposal for the same slot, skipping proposal`);
|
|
382
413
|
return Promise.resolve(undefined);
|
|
383
414
|
}
|
|
384
415
|
|
|
385
|
-
const newProposal = await this.validationService.createBlockProposal(
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
txs,
|
|
390
|
-
proposerAddress,
|
|
391
|
-
{ ...options, broadcastInvalidBlockProposal: this.config.broadcastInvalidBlockProposal },
|
|
392
|
-
);
|
|
416
|
+
const newProposal = await this.validationService.createBlockProposal(header, archive, txs, proposerAddress, {
|
|
417
|
+
...options,
|
|
418
|
+
broadcastInvalidBlockProposal: this.config.broadcastInvalidBlockProposal,
|
|
419
|
+
});
|
|
393
420
|
this.previousProposal = newProposal;
|
|
394
421
|
return newProposal;
|
|
395
422
|
}
|
|
@@ -406,15 +433,23 @@ export class ValidatorClient extends (EventEmitter as new () => WatcherEmitter)
|
|
|
406
433
|
}
|
|
407
434
|
|
|
408
435
|
async collectOwnAttestations(proposal: BlockProposal): Promise<BlockAttestation[]> {
|
|
409
|
-
const slot = proposal.payload.header.slotNumber
|
|
436
|
+
const slot = proposal.payload.header.slotNumber;
|
|
410
437
|
const inCommittee = await this.epochCache.filterInCommittee(slot, this.getValidatorAddresses());
|
|
411
438
|
this.log.debug(`Collecting ${inCommittee.length} self-attestations for slot ${slot}`, { inCommittee });
|
|
412
|
-
|
|
439
|
+
const attestations = await this.createBlockAttestationsFromProposal(proposal, inCommittee);
|
|
440
|
+
|
|
441
|
+
// We broadcast our own attestations to our peers so, in case our block does not get mined on L1,
|
|
442
|
+
// other nodes can see that our validators did attest to this block proposal, and do not slash us
|
|
443
|
+
// due to inactivity for missed attestations.
|
|
444
|
+
void this.p2pClient.broadcastAttestations(attestations).catch(err => {
|
|
445
|
+
this.log.error(`Failed to broadcast self-attestations for slot ${slot}`, err);
|
|
446
|
+
});
|
|
447
|
+
return attestations;
|
|
413
448
|
}
|
|
414
449
|
|
|
415
450
|
async collectAttestations(proposal: BlockProposal, required: number, deadline: Date): Promise<BlockAttestation[]> {
|
|
416
451
|
// Wait and poll the p2pClient's attestation pool for this block until we have enough attestations
|
|
417
|
-
const slot = proposal.payload.header.slotNumber
|
|
452
|
+
const slot = proposal.payload.header.slotNumber;
|
|
418
453
|
this.log.debug(`Collecting ${required} attestations for slot ${slot} with deadline ${deadline.toISOString()}`);
|
|
419
454
|
|
|
420
455
|
if (+deadline < this.dateProvider.now()) {
|