@aztec/validator-client 5.0.0-rc.1 → 5.0.0
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/key_store/web3signer_key_store.d.ts +10 -2
- package/dest/key_store/web3signer_key_store.d.ts.map +1 -1
- package/dest/key_store/web3signer_key_store.js +32 -41
- package/dest/proposal_handler.d.ts +25 -2
- package/dest/proposal_handler.d.ts.map +1 -1
- package/dest/proposal_handler.js +132 -6
- package/dest/validator.d.ts +11 -5
- package/dest/validator.d.ts.map +1 -1
- package/dest/validator.js +40 -38
- package/package.json +19 -19
- package/src/key_store/web3signer_key_store.ts +43 -59
- package/src/proposal_handler.ts +158 -4
- package/src/validator.ts +51 -44
package/src/validator.ts
CHANGED
|
@@ -4,14 +4,14 @@ import type { EpochCache } from '@aztec/epoch-cache';
|
|
|
4
4
|
import { CheckpointNumber, EpochNumber, IndexWithinCheckpoint, SlotNumber } from '@aztec/foundation/branded-types';
|
|
5
5
|
import { Fr } from '@aztec/foundation/curves/bn254';
|
|
6
6
|
import type { EthAddress } from '@aztec/foundation/eth-address';
|
|
7
|
-
import
|
|
7
|
+
import { Signature } from '@aztec/foundation/eth-signature';
|
|
8
8
|
import { FifoSet } from '@aztec/foundation/fifo-set';
|
|
9
9
|
import { type LogData, type Logger, createLogger } from '@aztec/foundation/log';
|
|
10
10
|
import { RunningPromise } from '@aztec/foundation/running-promise';
|
|
11
11
|
import { sleep } from '@aztec/foundation/sleep';
|
|
12
12
|
import { DateProvider } from '@aztec/foundation/timer';
|
|
13
13
|
import type { KeystoreManager } from '@aztec/node-keystore';
|
|
14
|
-
import type { DuplicateAttestationInfo, DuplicateProposalInfo, P2P, PeerId } from '@aztec/p2p';
|
|
14
|
+
import type { DuplicateAttestationInfo, DuplicateProposalInfo, OversizedProposalInfo, P2P, PeerId } from '@aztec/p2p';
|
|
15
15
|
import { AuthRequest, AuthResponse, BlockProposalValidator, ReqRespSubProtocol } from '@aztec/p2p';
|
|
16
16
|
import {
|
|
17
17
|
OffenseType,
|
|
@@ -35,7 +35,7 @@ import type { L1ToL2MessageSource } from '@aztec/stdlib/messaging';
|
|
|
35
35
|
import {
|
|
36
36
|
type BlockProposal,
|
|
37
37
|
type BlockProposalOptions,
|
|
38
|
-
|
|
38
|
+
CheckpointAttestation,
|
|
39
39
|
CheckpointProposal,
|
|
40
40
|
type CheckpointProposalCore,
|
|
41
41
|
type CheckpointProposalOptions,
|
|
@@ -66,48 +66,18 @@ import { NodeKeystoreAdapter } from './key_store/node_keystore_adapter.js';
|
|
|
66
66
|
import { ValidatorMetrics } from './metrics.js';
|
|
67
67
|
import {
|
|
68
68
|
type BlockProposalValidationFailureReason,
|
|
69
|
-
type CheckpointProposalValidationFailureReason,
|
|
70
69
|
type CheckpointProposalValidationFailureResult,
|
|
71
70
|
ProposalHandler,
|
|
71
|
+
SLASHABLE_BLOCK_PROPOSAL_VALIDATION_RESULT,
|
|
72
|
+
SLASHABLE_CHECKPOINT_PROPOSAL_VALIDATION_RESULT,
|
|
72
73
|
} from './proposal_handler.js';
|
|
73
74
|
|
|
74
75
|
// We maintain a set of proposers who have proposed invalid blocks.
|
|
75
76
|
// Just cap the set to avoid unbounded growth.
|
|
76
77
|
const MAX_PROPOSERS_OF_INVALID_BLOCKS = 1000;
|
|
77
|
-
const MAX_TRACKED_INVALID_PROPOSAL_SLOTS = 1000;
|
|
78
78
|
const MAX_TRACKED_INVALID_CHECKPOINT_PROPOSALS = 1000;
|
|
79
79
|
const MAX_TRACKED_BAD_ATTESTATIONS = 10_000;
|
|
80
80
|
|
|
81
|
-
// What errors from the block proposal handler result in slashing
|
|
82
|
-
const SLASHABLE_BLOCK_PROPOSAL_VALIDATION_RESULT: BlockProposalValidationFailureReason[] = [
|
|
83
|
-
'state_mismatch',
|
|
84
|
-
'failed_txs',
|
|
85
|
-
'global_variables_mismatch',
|
|
86
|
-
'invalid_proposal',
|
|
87
|
-
'parent_block_wrong_slot',
|
|
88
|
-
'in_hash_mismatch',
|
|
89
|
-
];
|
|
90
|
-
|
|
91
|
-
const SLASHABLE_CHECKPOINT_PROPOSAL_VALIDATION_RESULT: Record<CheckpointProposalValidationFailureReason, boolean> = {
|
|
92
|
-
// enabled
|
|
93
|
-
['invalid_fee_asset_price_modifier']: true,
|
|
94
|
-
['checkpoint_header_mismatch']: true,
|
|
95
|
-
// These late mismatches should normally be caught by earlier checks, but if reached after validating the local
|
|
96
|
-
// checkpoint inputs, the proposer-signed payload disagrees with deterministic recomputation.
|
|
97
|
-
['archive_mismatch']: true,
|
|
98
|
-
['out_hash_mismatch']: true,
|
|
99
|
-
['no_blocks_for_slot']: true,
|
|
100
|
-
['too_many_blocks_in_checkpoint']: true,
|
|
101
|
-
['checkpoint_validation_failed']: true,
|
|
102
|
-
['last_block_archive_mismatch']: true,
|
|
103
|
-
|
|
104
|
-
// disabled
|
|
105
|
-
['invalid_signature']: false,
|
|
106
|
-
['last_block_not_found']: false,
|
|
107
|
-
['block_fetch_error']: false,
|
|
108
|
-
['checkpoint_already_published']: false,
|
|
109
|
-
};
|
|
110
|
-
|
|
111
81
|
/**
|
|
112
82
|
* Validator Client
|
|
113
83
|
*/
|
|
@@ -131,10 +101,9 @@ export class ValidatorClient extends (EventEmitter as new () => WatcherEmitter)
|
|
|
131
101
|
private lastAttestedEpochByAttester: Map<string, EpochNumber> = new Map();
|
|
132
102
|
|
|
133
103
|
private proposersOfInvalidBlocks = FifoSet.withLimit<string>(MAX_PROPOSERS_OF_INVALID_BLOCKS);
|
|
134
|
-
private slotsWithInvalidProposals = FifoSet.withLimit<SlotNumber>(MAX_TRACKED_INVALID_PROPOSAL_SLOTS);
|
|
135
104
|
private invalidCheckpointProposalOffenseKeys = FifoSet.withLimit<string>(MAX_TRACKED_INVALID_CHECKPOINT_PROPOSALS);
|
|
105
|
+
private oversizedProposalOffenseKeys = FifoSet.withLimit<string>(MAX_TRACKED_INVALID_CHECKPOINT_PROPOSALS);
|
|
136
106
|
private badAttestationOffenseKeys = FifoSet.withLimit<string>(MAX_TRACKED_BAD_ATTESTATIONS);
|
|
137
|
-
private slotsWithProposalEquivocation = FifoSet.withLimit<SlotNumber>(MAX_TRACKED_INVALID_PROPOSAL_SLOTS);
|
|
138
107
|
|
|
139
108
|
/** Tracks the last checkpoint proposal we attested to, to prevent equivocation. */
|
|
140
109
|
private lastAttestedProposal?: CheckpointProposalCore;
|
|
@@ -363,11 +332,11 @@ export class ValidatorClient extends (EventEmitter as new () => WatcherEmitter)
|
|
|
363
332
|
}
|
|
364
333
|
|
|
365
334
|
public hasProposalEquivocation(slotNumber: SlotNumber): boolean {
|
|
366
|
-
return this.
|
|
335
|
+
return this.proposalHandler.hasProposalEquivocation(slotNumber);
|
|
367
336
|
}
|
|
368
337
|
|
|
369
338
|
public hasInvalidProposals(slotNumber: SlotNumber): boolean {
|
|
370
|
-
return this.
|
|
339
|
+
return this.proposalHandler.hasInvalidProposals(slotNumber);
|
|
371
340
|
}
|
|
372
341
|
|
|
373
342
|
public updateConfig(config: Partial<ValidatorClientFullConfig>) {
|
|
@@ -436,6 +405,11 @@ export class ValidatorClient extends (EventEmitter as new () => WatcherEmitter)
|
|
|
436
405
|
this.handleDuplicateProposal(info);
|
|
437
406
|
});
|
|
438
407
|
|
|
408
|
+
// Oversized proposal handler - triggers slashing for proposals beyond the per-checkpoint block limit
|
|
409
|
+
this.p2pClient.registerOversizedProposalCallback((info: OversizedProposalInfo) => {
|
|
410
|
+
this.handleOversizedProposal(info);
|
|
411
|
+
});
|
|
412
|
+
|
|
439
413
|
// Duplicate attestation handler - triggers slashing for attestation equivocation
|
|
440
414
|
this.p2pClient.registerDuplicateAttestationCallback((info: DuplicateAttestationInfo) => {
|
|
441
415
|
this.handleDuplicateAttestation(info);
|
|
@@ -765,8 +739,8 @@ export class ValidatorClient extends (EventEmitter as new () => WatcherEmitter)
|
|
|
765
739
|
return;
|
|
766
740
|
}
|
|
767
741
|
|
|
768
|
-
this
|
|
769
|
-
|
|
742
|
+
// The slot is already marked invalid by the all-nodes checkpoint handler that invokes this callback,
|
|
743
|
+
// so we only emit the proposer slash event here.
|
|
770
744
|
if (this.slashInvalidCheckpointProposal(proposal)) {
|
|
771
745
|
this.log.info(`Detected invalid checkpoint proposal offense`, {
|
|
772
746
|
...proposalInfo,
|
|
@@ -805,12 +779,15 @@ export class ValidatorClient extends (EventEmitter as new () => WatcherEmitter)
|
|
|
805
779
|
}
|
|
806
780
|
|
|
807
781
|
private markInvalidProposalSlot(slotNumber: SlotNumber): void {
|
|
808
|
-
this.
|
|
782
|
+
this.proposalHandler.markInvalidProposalSlot(slotNumber);
|
|
809
783
|
}
|
|
810
784
|
|
|
811
785
|
private handleCheckpointAttestation(attestation: CheckpointAttestation): void {
|
|
812
786
|
const slotNumber = attestation.slotNumber;
|
|
813
|
-
if (
|
|
787
|
+
if (
|
|
788
|
+
!this.proposalHandler.hasInvalidProposals(slotNumber) ||
|
|
789
|
+
this.proposalHandler.hasProposalEquivocation(slotNumber)
|
|
790
|
+
) {
|
|
814
791
|
return;
|
|
815
792
|
}
|
|
816
793
|
|
|
@@ -849,13 +826,43 @@ export class ValidatorClient extends (EventEmitter as new () => WatcherEmitter)
|
|
|
849
826
|
]);
|
|
850
827
|
}
|
|
851
828
|
|
|
829
|
+
/**
|
|
830
|
+
* Handle detection of an oversized block proposal: one whose index within its checkpoint lands at or
|
|
831
|
+
* beyond the consensus per-checkpoint block limit. A single signed proposal at an illegal index is
|
|
832
|
+
* self-contained evidence, so emit an invalid-block-proposal slash event for the proposer, deduped per
|
|
833
|
+
* (proposer, slot) since the p2p layer reports every oversized proposal it stores.
|
|
834
|
+
*/
|
|
835
|
+
private handleOversizedProposal(info: OversizedProposalInfo): void {
|
|
836
|
+
const { slot, proposer } = info;
|
|
837
|
+
const offenseType = OffenseType.BROADCASTED_INVALID_BLOCK_PROPOSAL;
|
|
838
|
+
if (!this.oversizedProposalOffenseKeys.addIfAbsent(`${proposer.toString()}:${offenseType}:${slot}`)) {
|
|
839
|
+
return;
|
|
840
|
+
}
|
|
841
|
+
|
|
842
|
+
this.log.info(`Detected oversized block proposal offense from ${proposer.toString()} at slot ${slot}`, {
|
|
843
|
+
proposer: proposer.toString(),
|
|
844
|
+
slot,
|
|
845
|
+
amount: this.config.slashBroadcastedInvalidBlockPenalty,
|
|
846
|
+
offenseType: getOffenseTypeName(offenseType),
|
|
847
|
+
});
|
|
848
|
+
|
|
849
|
+
this.emit(WANT_TO_SLASH_EVENT, [
|
|
850
|
+
{
|
|
851
|
+
validator: proposer,
|
|
852
|
+
amount: this.config.slashBroadcastedInvalidBlockPenalty,
|
|
853
|
+
offenseType,
|
|
854
|
+
epochOrSlot: BigInt(slot),
|
|
855
|
+
},
|
|
856
|
+
]);
|
|
857
|
+
}
|
|
858
|
+
|
|
852
859
|
/**
|
|
853
860
|
* Handle detection of a duplicate proposal (equivocation).
|
|
854
861
|
* Emits a slash event when a proposer sends multiple proposals for the same position.
|
|
855
862
|
*/
|
|
856
863
|
private handleDuplicateProposal(info: DuplicateProposalInfo): void {
|
|
857
864
|
const { slot, proposer, type } = info;
|
|
858
|
-
this.
|
|
865
|
+
this.proposalHandler.markProposalEquivocation(slot);
|
|
859
866
|
|
|
860
867
|
this.log.info(`Detected duplicate ${type} proposal offense from ${proposer.toString()} at slot ${slot}`, {
|
|
861
868
|
proposer: proposer.toString(),
|