@aztec/validator-client 5.0.0-rc.1 → 5.0.0-rc.2
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/proposal_handler.d.ts +25 -2
- package/dest/proposal_handler.d.ts.map +1 -1
- package/dest/proposal_handler.js +131 -5
- package/dest/validator.d.ts +9 -3
- package/dest/validator.d.ts.map +1 -1
- package/dest/validator.js +40 -38
- package/package.json +19 -19
- package/src/proposal_handler.ts +157 -3
- package/src/validator.ts +49 -42
|
@@ -48,7 +48,17 @@ export type CheckpointProposalValidationFailureResult = {
|
|
|
48
48
|
};
|
|
49
49
|
export type CheckpointProposalValidationResult = CheckpointProposalValidationSuccessResult | CheckpointProposalValidationFailureResult;
|
|
50
50
|
export type CheckpointProposalValidationFailureCallback = (proposal: CheckpointProposalCore, result: CheckpointProposalValidationFailureResult, proposalInfo: LogData) => void | Promise<void>;
|
|
51
|
-
/**
|
|
51
|
+
/** Block-proposal validation failures that constitute a slashable invalid-block offense. */
|
|
52
|
+
export declare const SLASHABLE_BLOCK_PROPOSAL_VALIDATION_RESULT: BlockProposalValidationFailureReason[];
|
|
53
|
+
/** Checkpoint-proposal validation failures that constitute a slashable invalid-checkpoint offense. */
|
|
54
|
+
export declare const SLASHABLE_CHECKPOINT_PROPOSAL_VALIDATION_RESULT: Record<CheckpointProposalValidationFailureReason, boolean>;
|
|
55
|
+
/**
|
|
56
|
+
* Handles block and checkpoint proposals for both validator and non-validator nodes. Also tracks which slots
|
|
57
|
+
* had a slashable invalid proposal or a proposal equivocation, exposing them via the
|
|
58
|
+
* `InvalidProposalSlotSource` interface consumed by the attested-invalid-proposal slashing watcher. The
|
|
59
|
+
* tracking is populated as a side effect of validating/re-executing proposals, so any node that re-executes
|
|
60
|
+
* proposals (the default) can serve it — not only validators.
|
|
61
|
+
*/
|
|
52
62
|
export declare class ProposalHandler {
|
|
53
63
|
private checkpointsBuilder;
|
|
54
64
|
private worldState;
|
|
@@ -76,6 +86,10 @@ export declare class ProposalHandler {
|
|
|
76
86
|
/** P2P proposal pool access for deciding when retained proposals should block archiver processing. */
|
|
77
87
|
private p2pClient?;
|
|
78
88
|
private checkpointProposalValidationFailureCallback?;
|
|
89
|
+
/** Slots at which a slashable invalid block or checkpoint proposal was observed. */
|
|
90
|
+
private readonly slotsWithInvalidProposals;
|
|
91
|
+
/** Slots at which a proposal equivocation was observed; suppresses attested-to-invalid-proposal slashing. */
|
|
92
|
+
private readonly slotsWithProposalEquivocation;
|
|
79
93
|
constructor(checkpointsBuilder: FullNodeCheckpointsBuilder, worldState: WorldStateSynchronizer, blockSource: L2BlockSource & L2BlockSink, l1ToL2MessageSource: L1ToL2MessageSource, txProvider: ITxProvider, blockProposalValidator: BlockProposalValidator, epochCache: EpochCache, timetable: ConsensusTimetable, config: ValidatorClientFullConfig, blobClient: BlobClientInterface, reexecutionTracker: CheckpointReexecutionTracker, metrics?: ValidatorMetrics | undefined, dateProvider?: DateProvider, telemetry?: TelemetryClient, log?: import("@aztec/foundation/log").Logger);
|
|
80
94
|
updateConfig(config: Partial<ValidatorClientFullConfig>): void;
|
|
81
95
|
setCheckpointProposalValidationFailureCallback(callback?: CheckpointProposalValidationFailureCallback): void;
|
|
@@ -91,6 +105,14 @@ export declare class ProposalHandler {
|
|
|
91
105
|
* proposer's own view of its own work.
|
|
92
106
|
*/
|
|
93
107
|
recordOwnCheckpointProposalAsValid(slot: SlotNumber, archive: Fr, checkpointNumber: CheckpointNumber): void;
|
|
108
|
+
/** Whether a slashable invalid block or checkpoint proposal was observed at the given slot (InvalidProposalSlotSource). */
|
|
109
|
+
hasInvalidProposals(slotNumber: SlotNumber): boolean;
|
|
110
|
+
/** Whether a proposal equivocation was observed at the given slot (InvalidProposalSlotSource). */
|
|
111
|
+
hasProposalEquivocation(slotNumber: SlotNumber): boolean;
|
|
112
|
+
/** Records a slot as having a slashable invalid proposal, for offense observers (sentinel/slasher watchers). */
|
|
113
|
+
markInvalidProposalSlot(slotNumber: SlotNumber): void;
|
|
114
|
+
/** Records a slot as having a proposal equivocation, which suppresses attested-to-invalid-proposal slashing. */
|
|
115
|
+
markProposalEquivocation(slotNumber: SlotNumber): void;
|
|
94
116
|
/**
|
|
95
117
|
* Registers handlers for block and checkpoint proposals on the p2p client.
|
|
96
118
|
* Records the p2p client so validation can inspect retained proposals.
|
|
@@ -103,6 +125,7 @@ export declare class ProposalHandler {
|
|
|
103
125
|
handleBlockProposal(proposal: BlockProposal, proposalSender: PeerId, shouldReexecute: boolean): Promise<BlockProposalValidationResult>;
|
|
104
126
|
private validateNewBlockInSlot;
|
|
105
127
|
private getParentBlock;
|
|
128
|
+
private resolveExistingBlockAtNumber;
|
|
106
129
|
private computeCheckpointNumber;
|
|
107
130
|
/**
|
|
108
131
|
* Validates that a non-first block in a checkpoint has consistent global variables with its parent.
|
|
@@ -139,4 +162,4 @@ export declare class ProposalHandler {
|
|
|
139
162
|
private setProposedCheckpoint;
|
|
140
163
|
}
|
|
141
164
|
export {};
|
|
142
|
-
//# sourceMappingURL=data:application/json;base64,
|
|
165
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicHJvcG9zYWxfaGFuZGxlci5kLnRzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vc3JjL3Byb3Bvc2FsX2hhbmRsZXIudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsT0FBTyxLQUFLLEVBQUUsUUFBUSxFQUFFLE1BQU0saUJBQWlCLENBQUM7QUFDaEQsT0FBTyxLQUFLLEVBQUUsbUJBQW1CLEVBQUUsTUFBTSwyQkFBMkIsQ0FBQztBQUdyRSxPQUFPLEtBQUssRUFBRSxVQUFVLEVBQUUsTUFBTSxvQkFBb0IsQ0FBQztBQUVyRCxPQUFPLEVBQ0wsV0FBVyxFQUNYLGdCQUFnQixFQUVoQixVQUFVLEVBQ1gsTUFBTSxpQ0FBaUMsQ0FBQztBQUV6QyxPQUFPLEVBQUUsRUFBRSxFQUFFLE1BQU0sZ0NBQWdDLENBQUM7QUFHcEQsT0FBTyxLQUFLLEVBQUUsT0FBTyxFQUFFLE1BQU0sdUJBQXVCLENBQUM7QUFHckQsT0FBTyxFQUFFLFlBQVksRUFBUyxNQUFNLHlCQUF5QixDQUFDO0FBQzlELE9BQU8sS0FBSyxFQUFFLEdBQUcsRUFBRSxNQUFNLEVBQUUsTUFBTSxZQUFZLENBQUM7QUFDOUMsT0FBTyxFQUFFLHNCQUFzQixFQUFFLE1BQU0sMkJBQTJCLENBQUM7QUFDbkUsT0FBTyxLQUFLLEVBQWEsT0FBTyxFQUFFLFdBQVcsRUFBRSxhQUFhLEVBQUUsTUFBTSxxQkFBcUIsQ0FBQztBQUMxRixPQUFPLEtBQUssRUFBRSw0QkFBNEIsRUFBc0IsTUFBTSwwQkFBMEIsQ0FBQztBQUlqRyxPQUFPLEtBQUssRUFBRSxXQUFXLEVBQUUseUJBQXlCLEVBQUUsc0JBQXNCLEVBQUUsTUFBTSxpQ0FBaUMsQ0FBQztBQUN0SCxPQUFPLEVBQ0wsS0FBSyxtQkFBbUIsRUFHekIsTUFBTSx5QkFBeUIsQ0FBQztBQUNqQyxPQUFPLEtBQUssRUFBRSxhQUFhLEVBQXlCLHNCQUFzQixFQUFFLE1BQU0sbUJBQW1CLENBQUM7QUFDdEcsT0FBTyxLQUFLLEVBQUUsa0JBQWtC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
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"proposal_handler.d.ts","sourceRoot":"","sources":["../src/proposal_handler.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,iBAAiB,CAAC;AAChD,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,2BAA2B,CAAC;AAGrE,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAC;AAErD,OAAO,EACL,WAAW,EACX,gBAAgB,EAEhB,UAAU,EACX,MAAM,iCAAiC,CAAC;AAEzC,OAAO,EAAE,EAAE,EAAE,MAAM,gCAAgC,CAAC;
|
|
1
|
+
{"version":3,"file":"proposal_handler.d.ts","sourceRoot":"","sources":["../src/proposal_handler.ts"],"names":[],"mappings":"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"}
|
package/dest/proposal_handler.js
CHANGED
|
@@ -70,6 +70,7 @@ import { BlockNumber, CheckpointNumber } from '@aztec/foundation/branded-types';
|
|
|
70
70
|
import { pick } from '@aztec/foundation/collection';
|
|
71
71
|
import { Fr } from '@aztec/foundation/curves/bn254';
|
|
72
72
|
import { TimeoutError } from '@aztec/foundation/error';
|
|
73
|
+
import { FifoSet } from '@aztec/foundation/fifo-set';
|
|
73
74
|
import { createLogger } from '@aztec/foundation/log';
|
|
74
75
|
import { retryUntil } from '@aztec/foundation/retry';
|
|
75
76
|
import { DateProvider, Timer } from '@aztec/foundation/timer';
|
|
@@ -98,7 +99,40 @@ import { getTelemetryClient } from '@aztec/telemetry-client';
|
|
|
98
99
|
out_hash_mismatch: 'invalid',
|
|
99
100
|
checkpoint_validation_failed: 'invalid'
|
|
100
101
|
};
|
|
101
|
-
|
|
102
|
+
const MAX_TRACKED_INVALID_PROPOSAL_SLOTS = 1000;
|
|
103
|
+
/** Block-proposal validation failures that constitute a slashable invalid-block offense. */ export const SLASHABLE_BLOCK_PROPOSAL_VALIDATION_RESULT = [
|
|
104
|
+
'state_mismatch',
|
|
105
|
+
'failed_txs',
|
|
106
|
+
'global_variables_mismatch',
|
|
107
|
+
'invalid_proposal',
|
|
108
|
+
'parent_block_wrong_slot',
|
|
109
|
+
'in_hash_mismatch'
|
|
110
|
+
];
|
|
111
|
+
/** Checkpoint-proposal validation failures that constitute a slashable invalid-checkpoint offense. */ export const SLASHABLE_CHECKPOINT_PROPOSAL_VALIDATION_RESULT = {
|
|
112
|
+
// enabled
|
|
113
|
+
['invalid_fee_asset_price_modifier']: true,
|
|
114
|
+
['checkpoint_header_mismatch']: true,
|
|
115
|
+
// These late mismatches should normally be caught by earlier checks, but if reached after validating the local
|
|
116
|
+
// checkpoint inputs, the proposer-signed payload disagrees with deterministic recomputation.
|
|
117
|
+
['archive_mismatch']: true,
|
|
118
|
+
['out_hash_mismatch']: true,
|
|
119
|
+
['no_blocks_for_slot']: true,
|
|
120
|
+
['too_many_blocks_in_checkpoint']: true,
|
|
121
|
+
['checkpoint_validation_failed']: true,
|
|
122
|
+
['last_block_archive_mismatch']: true,
|
|
123
|
+
// disabled
|
|
124
|
+
['invalid_signature']: false,
|
|
125
|
+
['last_block_not_found']: false,
|
|
126
|
+
['block_fetch_error']: false,
|
|
127
|
+
['checkpoint_already_published']: false
|
|
128
|
+
};
|
|
129
|
+
/**
|
|
130
|
+
* Handles block and checkpoint proposals for both validator and non-validator nodes. Also tracks which slots
|
|
131
|
+
* had a slashable invalid proposal or a proposal equivocation, exposing them via the
|
|
132
|
+
* `InvalidProposalSlotSource` interface consumed by the attested-invalid-proposal slashing watcher. The
|
|
133
|
+
* tracking is populated as a side effect of validating/re-executing proposals, so any node that re-executes
|
|
134
|
+
* proposals (the default) can serve it — not only validators.
|
|
135
|
+
*/ export class ProposalHandler {
|
|
102
136
|
checkpointsBuilder;
|
|
103
137
|
worldState;
|
|
104
138
|
blockSource;
|
|
@@ -121,6 +155,8 @@ import { getTelemetryClient } from '@aztec/telemetry-client';
|
|
|
121
155
|
/** Returns current validator addresses for own-proposal detection. Set via register(). */ getOwnValidatorAddresses;
|
|
122
156
|
/** P2P proposal pool access for deciding when retained proposals should block archiver processing. */ p2pClient;
|
|
123
157
|
checkpointProposalValidationFailureCallback;
|
|
158
|
+
/** Slots at which a slashable invalid block or checkpoint proposal was observed. */ slotsWithInvalidProposals;
|
|
159
|
+
/** Slots at which a proposal equivocation was observed; suppresses attested-to-invalid-proposal slashing. */ slotsWithProposalEquivocation;
|
|
124
160
|
constructor(checkpointsBuilder, worldState, blockSource, l1ToL2MessageSource, txProvider, blockProposalValidator, epochCache, timetable, config, blobClient, reexecutionTracker, metrics, dateProvider = new DateProvider(), telemetry = getTelemetryClient(), log = createLogger('validator:proposal-handler')){
|
|
125
161
|
this.checkpointsBuilder = checkpointsBuilder;
|
|
126
162
|
this.worldState = worldState;
|
|
@@ -136,6 +172,8 @@ import { getTelemetryClient } from '@aztec/telemetry-client';
|
|
|
136
172
|
this.metrics = metrics;
|
|
137
173
|
this.dateProvider = dateProvider;
|
|
138
174
|
this.log = log;
|
|
175
|
+
this.slotsWithInvalidProposals = FifoSet.withLimit(MAX_TRACKED_INVALID_PROPOSAL_SLOTS);
|
|
176
|
+
this.slotsWithProposalEquivocation = FifoSet.withLimit(MAX_TRACKED_INVALID_PROPOSAL_SLOTS);
|
|
139
177
|
if (config.fishermanMode) {
|
|
140
178
|
this.log = this.log.createChild('[FISHERMAN]');
|
|
141
179
|
}
|
|
@@ -163,6 +201,18 @@ import { getTelemetryClient } from '@aztec/telemetry-client';
|
|
|
163
201
|
*/ recordOwnCheckpointProposalAsValid(slot, archive, checkpointNumber) {
|
|
164
202
|
this.reexecutionTracker.recordOutcome(slot, archive, 'valid', checkpointNumber);
|
|
165
203
|
}
|
|
204
|
+
/** Whether a slashable invalid block or checkpoint proposal was observed at the given slot (InvalidProposalSlotSource). */ hasInvalidProposals(slotNumber) {
|
|
205
|
+
return this.slotsWithInvalidProposals.has(slotNumber);
|
|
206
|
+
}
|
|
207
|
+
/** Whether a proposal equivocation was observed at the given slot (InvalidProposalSlotSource). */ hasProposalEquivocation(slotNumber) {
|
|
208
|
+
return this.slotsWithProposalEquivocation.has(slotNumber);
|
|
209
|
+
}
|
|
210
|
+
/** Records a slot as having a slashable invalid proposal, for offense observers (sentinel/slasher watchers). */ markInvalidProposalSlot(slotNumber) {
|
|
211
|
+
this.slotsWithInvalidProposals.add(slotNumber);
|
|
212
|
+
}
|
|
213
|
+
/** Records a slot as having a proposal equivocation, which suppresses attested-to-invalid-proposal slashing. */ markProposalEquivocation(slotNumber) {
|
|
214
|
+
this.slotsWithProposalEquivocation.add(slotNumber);
|
|
215
|
+
}
|
|
166
216
|
/**
|
|
167
217
|
* Registers handlers for block and checkpoint proposals on the p2p client.
|
|
168
218
|
* Records the p2p client so validation can inspect retained proposals.
|
|
@@ -191,6 +241,15 @@ import { getTelemetryClient } from '@aztec/telemetry-client';
|
|
|
191
241
|
});
|
|
192
242
|
return true;
|
|
193
243
|
} else {
|
|
244
|
+
// Track invalid proposals / equivocations so offense observers (the attested-invalid-proposal
|
|
245
|
+
// watcher) work on non-validator nodes too. Validators populate these via their own handlers.
|
|
246
|
+
// Skip invalid-proposal marking while the escape hatch is open, matching the validator path,
|
|
247
|
+
// which intentionally disables invalid-block slashing then.
|
|
248
|
+
if (result.reason === 'checkpoint_proposal_equivocation') {
|
|
249
|
+
this.markProposalEquivocation(slotNumber);
|
|
250
|
+
} else if (SLASHABLE_BLOCK_PROPOSAL_VALIDATION_RESULT.includes(result.reason) && !await this.epochCache.isEscapeHatchOpenAtSlot(slotNumber)) {
|
|
251
|
+
this.markInvalidProposalSlot(slotNumber);
|
|
252
|
+
}
|
|
194
253
|
this.log.warn(`Non-validator block proposal ${blockNumber} at slot ${slotNumber} failed processing with ${result.reason}`, {
|
|
195
254
|
blockNumber: result.blockNumber,
|
|
196
255
|
slotNumber,
|
|
@@ -204,6 +263,10 @@ import { getTelemetryClient } from '@aztec/telemetry-client';
|
|
|
204
263
|
}
|
|
205
264
|
};
|
|
206
265
|
p2pClient.registerBlockProposalHandler(blockHandler);
|
|
266
|
+
// p2p detects duplicate (equivocated) proposals without routing them through the handlers above, so mark
|
|
267
|
+
// the slot as equivocated here. This suppresses false-positive attested-to-invalid-proposal slashing on
|
|
268
|
+
// non-validator offense collectors. Validators overwrite this with their own richer handler.
|
|
269
|
+
p2pClient.registerDuplicateProposalCallback((info)=>this.markProposalEquivocation(info.slot));
|
|
207
270
|
// All-nodes checkpoint proposal handler: validates, caches, and sets proposed checkpoint for pipelining.
|
|
208
271
|
// Runs for all nodes (validators and non-validators). Validators get the cached result in the
|
|
209
272
|
// validator-specific callback (attestToCheckpointProposal) which runs after this one.
|
|
@@ -243,6 +306,12 @@ import { getTelemetryClient } from '@aztec/telemetry-client';
|
|
|
243
306
|
}
|
|
244
307
|
const result = await this.handleCheckpointProposal(proposal, proposalInfo);
|
|
245
308
|
if (!result.isValid) {
|
|
309
|
+
// Track invalid checkpoint proposals so offense observers (the attested-invalid-proposal watcher)
|
|
310
|
+
// work on non-validator nodes too. This handler runs for all nodes; validators also mark via the
|
|
311
|
+
// failure callback below (idempotent).
|
|
312
|
+
if (SLASHABLE_CHECKPOINT_PROPOSAL_VALIDATION_RESULT[result.reason]) {
|
|
313
|
+
this.markInvalidProposalSlot(proposal.slotNumber);
|
|
314
|
+
}
|
|
246
315
|
await this.checkpointProposalValidationFailureCallback?.(proposal, result, proposalInfo);
|
|
247
316
|
} else if (this.archiver) {
|
|
248
317
|
const set = await this.setProposedCheckpoint(proposal);
|
|
@@ -333,10 +402,12 @@ import { getTelemetryClient } from '@aztec/telemetry-client';
|
|
|
333
402
|
// Compute the block number based on the parent block
|
|
334
403
|
const blockNumber = parentBlock === 'genesis' ? BlockNumber(INITIAL_L2_BLOCK_NUM) : BlockNumber(parentBlock.header.getBlockNumber() + 1);
|
|
335
404
|
proposalInfo.blockNumber = blockNumber;
|
|
336
|
-
// Check that this block number does not exist already
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
405
|
+
// Check that this block number does not exist already. During a reorg the archiver can still hold a
|
|
406
|
+
// stale block at this number (a different archive, about to be pruned) while the proposal carries the
|
|
407
|
+
// rebuilt replacement; resolveExistingBlockAtNumber waits for the local prune in that case so the
|
|
408
|
+
// rebuilt block is processed in time to attest, rather than being permanently dropped on a bare
|
|
409
|
+
// number collision.
|
|
410
|
+
const existingBlock = await this.resolveExistingBlockAtNumber(blockNumber, proposal.archive, slotNumber);
|
|
340
411
|
if (existingBlock) {
|
|
341
412
|
this.log.warn(`Block number ${blockNumber} already exists, skipping processing`, proposalInfo);
|
|
342
413
|
return {
|
|
@@ -498,6 +569,60 @@ import { getTelemetryClient } from '@aztec/telemetry-client';
|
|
|
498
569
|
return undefined;
|
|
499
570
|
}
|
|
500
571
|
}
|
|
572
|
+
/**
|
|
573
|
+
* Resolves whether a block genuinely already exists at `blockNumber`. Returns the existing block only if
|
|
574
|
+
* it is a true duplicate of the proposal (matching archive). During a reorg the archiver can still hold a
|
|
575
|
+
* stale fork at this number (different archive) that is about to be pruned; in that case this forces L1
|
|
576
|
+
* sync and waits, bounded by the re-execution deadline, for the prune to land, then returns `undefined` so
|
|
577
|
+
* the rebuilt block proposal can be processed in time to attest. If the prune does not complete before the
|
|
578
|
+
* deadline it returns the stale block, so the caller falls back to the safe `block_number_already_exists`
|
|
579
|
+
* rejection.
|
|
580
|
+
*/ async resolveExistingBlockAtNumber(blockNumber, proposalArchive, slotNumber) {
|
|
581
|
+
const existingBlock = await this.blockSource.getBlockData({
|
|
582
|
+
number: blockNumber
|
|
583
|
+
});
|
|
584
|
+
if (!existingBlock || existingBlock.archive.root.equals(proposalArchive)) {
|
|
585
|
+
return existingBlock;
|
|
586
|
+
}
|
|
587
|
+
// A different block already occupies this number: it may be a stale fork being pruned during a reorg, not a
|
|
588
|
+
// genuine duplicate. Wait for the local prune rather than permanently rejecting the proposal.
|
|
589
|
+
const deadline = this.getReexecutionDeadline(slotNumber);
|
|
590
|
+
if (deadline.getTime() - this.dateProvider.now() <= 0) {
|
|
591
|
+
return existingBlock;
|
|
592
|
+
}
|
|
593
|
+
this.log.warn(`Block number ${blockNumber} already exists, awaiting potential prune`, {
|
|
594
|
+
blockNumber,
|
|
595
|
+
existingArchive: existingBlock.archive.root.toString(),
|
|
596
|
+
proposalArchive: proposalArchive.toString()
|
|
597
|
+
});
|
|
598
|
+
try {
|
|
599
|
+
const { block } = await retryUntil(async ()=>{
|
|
600
|
+
await this.blockSource.syncImmediate();
|
|
601
|
+
const block = await this.blockSource.getBlockData({
|
|
602
|
+
number: blockNumber
|
|
603
|
+
});
|
|
604
|
+
// Resolve once the existing block is gone (pruned) or has been replaced by one matching the
|
|
605
|
+
// proposal — the same condition as the early return above. A matching block is returned so the
|
|
606
|
+
// caller still treats it as a genuine duplicate; an `undefined` (pruned) block lets the proposal
|
|
607
|
+
// be processed. Wrap in an object so the `undefined` case is still a truthy retry result.
|
|
608
|
+
return block === undefined || block.archive.root.equals(proposalArchive) ? {
|
|
609
|
+
block
|
|
610
|
+
} : undefined;
|
|
611
|
+
}, `prune of stale block ${blockNumber}`, {
|
|
612
|
+
deadline,
|
|
613
|
+
dateProvider: this.dateProvider
|
|
614
|
+
}, 0.5);
|
|
615
|
+
return block;
|
|
616
|
+
} catch (err) {
|
|
617
|
+
if (err instanceof TimeoutError) {
|
|
618
|
+
this.log.warn(`Timed out waiting for stale block ${blockNumber} to be pruned`, {
|
|
619
|
+
blockNumber
|
|
620
|
+
});
|
|
621
|
+
return existingBlock;
|
|
622
|
+
}
|
|
623
|
+
throw err;
|
|
624
|
+
}
|
|
625
|
+
}
|
|
501
626
|
computeCheckpointNumber(proposal, parentBlock, proposalInfo) {
|
|
502
627
|
if (parentBlock === 'genesis') {
|
|
503
628
|
// First block is in checkpoint 1
|
|
@@ -889,6 +1014,7 @@ import { getTelemetryClient } from '@aztec/telemetry-client';
|
|
|
889
1014
|
checkpointNumber: lastBlockData.checkpointNumber
|
|
890
1015
|
};
|
|
891
1016
|
}
|
|
1017
|
+
// Note this condition should never trigger, since we dont process block proposals that exceed indexWithinCheckpoint
|
|
892
1018
|
const maxBlocksPerCheckpoint = this.config.maxBlocksPerCheckpoint;
|
|
893
1019
|
if (maxBlocksPerCheckpoint !== undefined && blocks.length > maxBlocksPerCheckpoint) {
|
|
894
1020
|
this.log.warn(`Checkpoint proposal exceeds maxBlocksPerCheckpoint`, {
|
package/dest/validator.d.ts
CHANGED
|
@@ -55,10 +55,9 @@ export declare class ValidatorClient extends ValidatorClient_base implements Val
|
|
|
55
55
|
/** Tracks the last epoch in which each attester successfully submitted at least one attestation. */
|
|
56
56
|
private lastAttestedEpochByAttester;
|
|
57
57
|
private proposersOfInvalidBlocks;
|
|
58
|
-
private slotsWithInvalidProposals;
|
|
59
58
|
private invalidCheckpointProposalOffenseKeys;
|
|
59
|
+
private oversizedProposalOffenseKeys;
|
|
60
60
|
private badAttestationOffenseKeys;
|
|
61
|
-
private slotsWithProposalEquivocation;
|
|
62
61
|
/** Tracks the last checkpoint proposal we attested to, to prevent equivocation. */
|
|
63
62
|
private lastAttestedProposal?;
|
|
64
63
|
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);
|
|
@@ -109,6 +108,13 @@ export declare class ValidatorClient extends ValidatorClient_base implements Val
|
|
|
109
108
|
private markInvalidProposalSlot;
|
|
110
109
|
private handleCheckpointAttestation;
|
|
111
110
|
private slashAttestedToInvalidCheckpointProposal;
|
|
111
|
+
/**
|
|
112
|
+
* Handle detection of an oversized block proposal: one whose index within its checkpoint lands at or
|
|
113
|
+
* beyond the consensus per-checkpoint block limit. A single signed proposal at an illegal index is
|
|
114
|
+
* self-contained evidence, so emit an invalid-block-proposal slash event for the proposer, deduped per
|
|
115
|
+
* (proposer, slot) since the p2p layer reports every oversized proposal it stores.
|
|
116
|
+
*/
|
|
117
|
+
private handleOversizedProposal;
|
|
112
118
|
/**
|
|
113
119
|
* Handle detection of a duplicate proposal (equivocation).
|
|
114
120
|
* Emits a slash event when a proposer sends multiple proposals for the same position.
|
|
@@ -128,4 +134,4 @@ export declare class ValidatorClient extends ValidatorClient_base implements Val
|
|
|
128
134
|
private handleAuthRequest;
|
|
129
135
|
}
|
|
130
136
|
export {};
|
|
131
|
-
//# sourceMappingURL=data:application/json;base64,
|
|
137
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoidmFsaWRhdG9yLmQudHMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi9zcmMvdmFsaWRhdG9yLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBLE9BQU8sS0FBSyxFQUFFLG1CQUFtQixFQUFFLE1BQU0sMkJBQTJCLENBQUM7QUFFckUsT0FBTyxLQUFLLEVBQUUsVUFBVSxFQUFFLE1BQU0sb0JBQW9CLENBQUM7QUFDckQsT0FBTyxFQUFFLGdCQUFnQixFQUFlLHFCQUFxQixFQUFFLFVBQVUsRUFBRSxNQUFNLGlDQUFpQyxDQUFDO0FBQ25ILE9BQU8sRUFBRSxFQUFFLEVBQUUsTUFBTSxnQ0FBZ0MsQ0FBQztBQUNwRCxPQUFPLEtBQUssRUFBRSxVQUFVLEVBQUUsTUFBTSwrQkFBK0IsQ0FBQztBQUNoRSxPQUFPLEtBQUssRUFBRSxTQUFTLEVBQUUsTUFBTSxpQ0FBaUMsQ0FBQztBQUVqRSxPQUFPLEVBQUUsS0FBSyxPQUFPLEVBQUUsS0FBSyxNQUFNLEVBQWdCLE1BQU0sdUJBQXVCLENBQUM7QUFHaEYsT0FBTyxFQUFFLFlBQVksRUFBRSxNQUFNLHlCQUF5QixDQUFDO0FBQ3ZELE9BQU8sS0FBSyxFQUFFLGVBQWUsRUFBRSxNQUFNLHNCQUFzQixDQUFDO0FBQzVELE9BQU8sS0FBSyxFQUEwRSxHQUFHLEVBQUUsTUFBTSxFQUFFLE1BQU0sWUFBWSxDQUFDO0FBRXRILE9BQU8sRUFJTCxLQUFLLE9BQU8sRUFDWixLQUFLLGNBQWMsRUFFcEIsTUFBTSxnQkFBZ0IsQ0FBQztBQUN4QixPQUFPLEtBQUssRUFBRSxZQUFZLEVBQUUsTUFBTSw2QkFBNkIsQ0FBQztBQUNoRSxPQUFPLEtBQUssRUFBRSwrQkFBK0IsRUFBRSxXQUFXLEVBQUUsYUFBYSxFQUFFLE1BQU0scUJBQXFCLENBQUM7QUFDdkcsT0FBTyxLQUFLLEVBQUUsNEJBQTRCLEVBQUUsTUFBTSwwQkFBMEIsQ0FBQztBQUU3RSxPQUFPLEtBQUssRUFDVixXQUFXLEVBQ1gsU0FBUyxFQUNULHlCQUF5QixFQUN6QixzQkFBc0IsRUFDdkIsTUFBTSxpQ0FBaUMsQ0FBQztBQUN6QyxPQUFPLEtBQUssRUFBRSxtQkFBbUIsRUFBRSxNQUFNLHlCQUF5QixDQUFDO0FBQ25FLE9BQU8sRUFDTCxLQUFLLGFBQWEsRUFDbEIsS0FBSyxvQkFBb0IsRUFDekIsS0FBSyxxQkFBcUIsRUFDMUIsa0JBQWtCLEVBQ2xCLEtBQUssc0JBQXNCLEVBQzNCLEtBQUsseUJBQXlCLEVBRS9CLE1BQU0sbUJBQW1CLENBQUM7QUFDM0IsT0FBTyxLQUFLLEVBQUUsZ0JBQWdCLEVBQUUsTUFBTSxzQkFBc0IsQ0FBQztBQUU3RCxPQUFPLEtBQUssRUFBRSxXQUFXLEVBQUUsRUFBRSxFQUFFLE1BQU0sa0JBQWtCLENBQUM7QUFFeEQsT0FBTyxFQUFFLEtBQUssZUFBZSxFQUFFLEtBQUssTUFBTSxFQUFzQixNQUFNLHlCQUF5QixDQUFDO0FBTWhHLE9BQU8sRUFBWSxLQUFLLGNBQWMsRUFBRSxLQUFLLDBCQUEwQixFQUFFLE1BQU0sa0NBQWtDLENBQUM7QUFDbEgsT0FBTyxLQUFLLEVBQUUsaUJBQWlCLEVBQUUsTUFBTSxnREFBZ0QsQ0FBQztBQUd4RixPQUFPLEtBQUssRUFBRSxtQkFBbUIsRUFBRSxNQUFNLE1BQU0sQ0FBQztBQUVoRCxPQUFPLEtBQUssRUFBRSwwQkFBMEIsRUFBRSxNQUFNLHlCQUF5QixDQUFDO0FBSTFFLE9BQU8sS0FBSyxFQUFFLHlCQUF5QixFQUFFLE1BQU0sMEJBQTBCLENBQUM7QUFHMUUsT0FBTyxFQUdMLGVBQWUsRUFHaEIsTUFBTSx1QkFBdUIsQ0FBQzs7QUFRL0I7O0dBRUc7QUFDSCxxQkFBYSxlQUFnQixTQUFRLG9CQUEyQyxZQUFXLFNBQVMsRUFBRSxPQUFPO0lBNEJ6RyxPQUFPLENBQUMsUUFBUTtJQUNoQixPQUFPLENBQUMsVUFBVTtJQUNsQixPQUFPLENBQUMsU0FBUztJQUNqQixPQUFPLENBQUMsZUFBZTtJQUN2QixPQUFPLENBQUMsV0FBVztJQUNuQixPQUFPLENBQUMsa0JBQWtCO0lBQzFCLE9BQU8sQ0FBQyxVQUFVO0lBQ2xCLE9BQU8sQ0FBQyxtQkFBbUI7SUFDM0IsT0FBTyxDQUFDLE1BQU07SUFDZCxPQUFPLENBQUMsVUFBVTtJQUNsQixPQUFPLENBQUMsd0JBQXdCO0lBQ2hDLE9BQU8sQ0FBQyxZQUFZO0lBdEN0QixTQUFnQixNQUFNLEVBQUUsTUFBTSxDQUFDO0lBQy9CLE9BQU8sQ0FBQyxpQkFBaUIsQ0FBb0I7SUFDN0MsT0FBTyxDQUFDLE9BQU8sQ0FBbUI7SUFDbEMsT0FBTyxDQUFDLEdBQUcsQ0FBUztJQUVwQixPQUFPLENBQUMscUJBQXFCLENBQVM7SUFFdEMsd0ZBQXdGO0lBQ3hGLE9BQU8sQ0FBQyxpQkFBaUIsQ0FBQyxDQUFnQjtJQUUxQyxzREFBc0Q7SUFDdEQsT0FBTyxDQUFDLHNCQUFzQixDQUFDLENBQXFCO0lBRXBELE9BQU8sQ0FBQywrQkFBK0IsQ0FBMEI7SUFDakUsT0FBTyxDQUFDLG9CQUFvQixDQUFpQjtJQUM3QyxvR0FBb0c7SUFDcEcsT0FBTyxDQUFDLDJCQUEyQixDQUF1QztJQUUxRSxPQUFPLENBQUMsd0JBQXdCLENBQThEO0lBQzlGLE9BQU8sQ0FBQyxvQ0FBb0MsQ0FBdUU7SUFDbkgsT0FBTyxDQUFDLDRCQUE0QixDQUF1RTtJQUMzRyxPQUFPLENBQUMseUJBQXlCLENBQTJEO0lBRTVGLG1GQUFtRjtJQUNuRixPQUFPLENBQUMsb0JBQW9CLENBQUMsQ0FBeUI7SUFFdEQsU0FBUyxhQUNDLFFBQVEsRUFBRSx5QkFBeUIsRUFDbkMsVUFBVSxFQUFFLFVBQVUsRUFDdEIsU0FBUyxFQUFFLEdBQUcsRUFDZCxlQUFlLEVBQUUsZUFBZSxFQUNoQyxXQUFXLEVBQUUsYUFBYSxFQUMxQixrQkFBa0IsRUFBRSwwQkFBMEIsRUFDOUMsVUFBVSxFQUFFLHNCQUFzQixFQUNsQyxtQkFBbUIsRUFBRSxtQkFBbUIsRUFDeEMsTUFBTSxFQUFFLHlCQUF5QixFQUNqQyxVQUFVLEVBQUUsbUJBQW1CLEVBQy9CLHdCQUF3QixFQUFFLGlCQUFpQixFQUMzQyxZQUFZLEdBQUUsWUFBaUMsRUFDdkQsU0FBUyxHQUFFLGVBQXNDLEVBQ2pELEdBQUcsU0FBNEIsRUF1QmhDO0lBRUQsT0FBYyw2QkFBNkIsQ0FBQyxlQUFlLEVBQUUsZUFBZSxFQUFFLE1BQU0sQ0FBQyxFQUFFLE1BQU0sUUF1QjVGO1lBRWEsMEJBQTBCO0lBNEJ4QyxPQUFhLEdBQUcsQ0FDZCxNQUFNLEVBQUUseUJBQXlCLEVBQ2pDLGtCQUFrQixFQUFFLDBCQUEwQixFQUM5QyxVQUFVLEVBQUUsc0JBQXNCLEVBQ2xDLFVBQVUsRUFBRSxVQUFVLEVBQ3RCLFNBQVMsRUFBRSxHQUFHLEVBQ2QsV0FBVyxFQUFFLGFBQWEsR0FBRyxXQUFXLEVBQ3hDLG1CQUFtQixFQUFFLG1CQUFtQixFQUN4QyxVQUFVLEVBQUUsV0FBVyxFQUN2QixlQUFlLEVBQUUsZUFBZSxFQUNoQyxVQUFVLEVBQUUsbUJBQW1CLEVBQy9CLGtCQUFrQixFQUFFLDRCQUE0QixFQUNoRCxZQUFZLEdBQUUsWUFBaUMsRUFDL0MsU0FBUyxHQUFFLGVBQXNDLEVBQ2pELG9CQUFvQixDQUFDLEVBQUUsMEJBQTBCLDRCQWtGbEQ7SUFFTSxxQkFBcUIsaUJBSTNCO0lBRU0sa0JBQWtCLG9CQUV4QjtJQUVNLGVBQWUsQ0FBQyxJQUFJLEVBQUUsVUFBVSxFQUFFLEdBQUcsRUFBRSxtQkFBbUIsRUFBRSxPQUFPLEVBQUUsY0FBYyxzQkFFekY7SUFFRCxPQUFPLENBQUMsbUJBQW1CO0lBT3BCLHNCQUFzQixDQUFDLFFBQVEsRUFBRSxVQUFVLEdBQUcsVUFBVSxDQUU5RDtJQUVNLDBCQUEwQixDQUFDLFFBQVEsRUFBRSxVQUFVLEdBQUcsWUFBWSxDQUVwRTtJQUVNLFNBQVMsSUFBSSx5QkFBeUIsQ0FFNUM7SUFFTSx1QkFBdUIsQ0FBQyxVQUFVLEVBQUUsVUFBVSxHQUFHLE9BQU8sQ0FFOUQ7SUFFTSxtQkFBbUIsQ0FBQyxVQUFVLEVBQUUsVUFBVSxHQUFHLE9BQU8sQ0FFMUQ7SUFFTSxZQUFZLENBQUMsTUFBTSxFQUFFLE9BQU8sQ0FBQyx5QkFBeUIsQ0FBQyxRQUc3RDtJQUVNLGNBQWMsQ0FBQyxVQUFVLEVBQUUsZUFBZSxHQUFHLElBQUksQ0FRdkQ7SUFFWSxLQUFLLGtCQW1CakI7SUFFWSxJQUFJLGtCQUdoQjtJQUVELDBDQUEwQztJQUM3QixnQkFBZ0Isa0JBMkM1QjtJQUVEOzs7O09BSUc7SUFDRyxxQkFBcUIsQ0FBQyxRQUFRLEVBQUUsYUFBYSxFQUFFLGNBQWMsRUFBRSxNQUFNLEdBQUcsT0FBTyxDQUFDLE9BQU8sQ0FBQyxDQXVGN0Y7SUFFRDs7Ozs7T0FLRztJQUNHLDBCQUEwQixDQUM5QixRQUFRLEVBQUUsc0JBQXNCLEVBQ2hDLGVBQWUsRUFBRSxNQUFNLEdBQ3RCLE9BQU8sQ0FBQyxxQkFBcUIsRUFBRSxHQUFHLFNBQVMsQ0FBQyxDQTZHOUM7SUFFRDs7O09BR0c7SUFDSCxPQUFPLENBQUMsa0JBQWtCO1lBaUJaLHdDQUF3QztJQW1CdEQ7O09BRUc7SUFDSCxVQUFnQix3QkFBd0IsQ0FBQyxRQUFRLEVBQUUsc0JBQXNCLEVBQUUsWUFBWSxFQUFFLE9BQU8sR0FBRyxPQUFPLENBQUMsSUFBSSxDQUFDLENBd0IvRztJQUVELE9BQU8sQ0FBQyxpQkFBaUI7SUFxQnpCLE9BQU8sQ0FBQywrQkFBK0I7SUFxQnZDLE9BQU8sQ0FBQyw4QkFBOEI7SUEyQnRDLE9BQU8sQ0FBQyx1QkFBdUI7SUFJL0IsT0FBTyxDQUFDLDJCQUEyQjtJQXFCbkMsT0FBTyxDQUFDLHdDQUF3QztJQXVCaEQ7Ozs7O09BS0c7SUFDSCxPQUFPLENBQUMsdUJBQXVCO0lBd0IvQjs7O09BR0c7SUFDSCxPQUFPLENBQUMsdUJBQXVCO0lBNkIvQjs7O09BR0c7SUFDSCxPQUFPLENBQUMsMEJBQTBCO0lBb0I1QixtQkFBbUIsQ0FDdkIsV0FBVyxFQUFFLFdBQVcsRUFDeEIsZ0JBQWdCLEVBQUUsZ0JBQWdCLEVBQ2xDLHFCQUFxQixFQUFFLHFCQUFxQixFQUM1QyxNQUFNLEVBQUUsRUFBRSxFQUNWLE9BQU8sRUFBRSxFQUFFLEVBQ1gsR0FBRyxFQUFFLEVBQUUsRUFBRSxFQUNULGVBQWUsRUFBRSxVQUFVLEdBQUcsU0FBUyxFQUN2QyxPQUFPLEdBQUUsb0JBQXlCLEdBQ2pDLE9BQU8sQ0FBQyxhQUFhLENBQUMsQ0FrQ3hCO0lBRUssd0JBQXdCLENBQzVCLGdCQUFnQixFQUFFLGdCQUFnQixFQUNsQyxPQUFPLEVBQUUsRUFBRSxFQUNYLGdCQUFnQixFQUFFLGdCQUFnQixFQUNsQyxxQkFBcUIsRUFBRSxNQUFNLEVBQzdCLGlCQUFpQixFQUFFLGFBQWEsR0FBRyxTQUFTLEVBQzVDLGVBQWUsRUFBRSxVQUFVLEdBQUcsU0FBUyxFQUN2QyxPQUFPLEdBQUUseUJBQThCLEdBQ3RDLE9BQU8sQ0FBQyxrQkFBa0IsQ0FBQyxDQWlDN0I7SUFFSyxzQkFBc0IsQ0FBQyxRQUFRLEVBQUUsYUFBYSxHQUFHLE9BQU8sQ0FBQyxJQUFJLENBQUMsQ0FFbkU7SUFFSywwQkFBMEIsQ0FDOUIsc0JBQXNCLEVBQUUsK0JBQStCLEVBQ3ZELFFBQVEsRUFBRSxVQUFVLEVBQ3BCLElBQUksRUFBRSxVQUFVLEVBQ2hCLGdCQUFnQixFQUFFLGdCQUFnQixHQUNqQyxPQUFPLENBQUMsU0FBUyxDQUFDLENBT3BCO0lBRUssc0JBQXNCLENBQzFCLFFBQVEsRUFBRSxrQkFBa0IsRUFDNUIsZ0JBQWdCLEVBQUUsZ0JBQWdCLEdBQ2pDLE9BQU8sQ0FBQyxxQkFBcUIsRUFBRSxDQUFDLENBaUJsQztJQUVLLG1CQUFtQixDQUN2QixRQUFRLEVBQUUsa0JBQWtCLEVBQzVCLFFBQVEsRUFBRSxNQUFNLEVBQ2hCLFFBQVEsRUFBRSxJQUFJLEVBQ2QsZ0JBQWdCLEVBQUUsZ0JBQWdCLEdBQ2pDLE9BQU8sQ0FBQyxxQkFBcUIsRUFBRSxDQUFDLENBdURsQztZQUVhLGlCQUFpQjtDQXdCaEMifQ==
|
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,EAAE,gBAAgB,EAAe,qBAAqB,EAAE,UAAU,EAAE,MAAM,iCAAiC,CAAC;AACnH,OAAO,EAAE,EAAE,EAAE,MAAM,gCAAgC,CAAC;AACpD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,+BAA+B,CAAC;AAChE,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,iCAAiC,CAAC;AAEjE,OAAO,EAAE,KAAK,OAAO,EAAE,KAAK,MAAM,EAAgB,MAAM,uBAAuB,CAAC;AAGhF,OAAO,EAAE,YAAY,EAAE,MAAM,yBAAyB,CAAC;AACvD,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAC;AAC5D,OAAO,KAAK,
|
|
1
|
+
{"version":3,"file":"validator.d.ts","sourceRoot":"","sources":["../src/validator.ts"],"names":[],"mappings":"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"}
|
package/dest/validator.js
CHANGED
|
@@ -19,40 +19,12 @@ import { ValidationService } from './duties/validation_service.js';
|
|
|
19
19
|
import { HAKeyStore } from './key_store/ha_key_store.js';
|
|
20
20
|
import { NodeKeystoreAdapter } from './key_store/node_keystore_adapter.js';
|
|
21
21
|
import { ValidatorMetrics } from './metrics.js';
|
|
22
|
-
import { ProposalHandler } from './proposal_handler.js';
|
|
22
|
+
import { ProposalHandler, SLASHABLE_BLOCK_PROPOSAL_VALIDATION_RESULT, SLASHABLE_CHECKPOINT_PROPOSAL_VALIDATION_RESULT } from './proposal_handler.js';
|
|
23
23
|
// We maintain a set of proposers who have proposed invalid blocks.
|
|
24
24
|
// Just cap the set to avoid unbounded growth.
|
|
25
25
|
const MAX_PROPOSERS_OF_INVALID_BLOCKS = 1000;
|
|
26
|
-
const MAX_TRACKED_INVALID_PROPOSAL_SLOTS = 1000;
|
|
27
26
|
const MAX_TRACKED_INVALID_CHECKPOINT_PROPOSALS = 1000;
|
|
28
27
|
const MAX_TRACKED_BAD_ATTESTATIONS = 10_000;
|
|
29
|
-
// What errors from the block proposal handler result in slashing
|
|
30
|
-
const SLASHABLE_BLOCK_PROPOSAL_VALIDATION_RESULT = [
|
|
31
|
-
'state_mismatch',
|
|
32
|
-
'failed_txs',
|
|
33
|
-
'global_variables_mismatch',
|
|
34
|
-
'invalid_proposal',
|
|
35
|
-
'parent_block_wrong_slot',
|
|
36
|
-
'in_hash_mismatch'
|
|
37
|
-
];
|
|
38
|
-
const SLASHABLE_CHECKPOINT_PROPOSAL_VALIDATION_RESULT = {
|
|
39
|
-
// enabled
|
|
40
|
-
['invalid_fee_asset_price_modifier']: true,
|
|
41
|
-
['checkpoint_header_mismatch']: true,
|
|
42
|
-
// These late mismatches should normally be caught by earlier checks, but if reached after validating the local
|
|
43
|
-
// checkpoint inputs, the proposer-signed payload disagrees with deterministic recomputation.
|
|
44
|
-
['archive_mismatch']: true,
|
|
45
|
-
['out_hash_mismatch']: true,
|
|
46
|
-
['no_blocks_for_slot']: true,
|
|
47
|
-
['too_many_blocks_in_checkpoint']: true,
|
|
48
|
-
['checkpoint_validation_failed']: true,
|
|
49
|
-
['last_block_archive_mismatch']: true,
|
|
50
|
-
// disabled
|
|
51
|
-
['invalid_signature']: false,
|
|
52
|
-
['last_block_not_found']: false,
|
|
53
|
-
['block_fetch_error']: false,
|
|
54
|
-
['checkpoint_already_published']: false
|
|
55
|
-
};
|
|
56
28
|
/**
|
|
57
29
|
* Validator Client
|
|
58
30
|
*/ export class ValidatorClient extends EventEmitter {
|
|
@@ -80,13 +52,12 @@ const SLASHABLE_CHECKPOINT_PROPOSAL_VALIDATION_RESULT = {
|
|
|
80
52
|
epochCacheUpdateLoop;
|
|
81
53
|
/** Tracks the last epoch in which each attester successfully submitted at least one attestation. */ lastAttestedEpochByAttester;
|
|
82
54
|
proposersOfInvalidBlocks;
|
|
83
|
-
slotsWithInvalidProposals;
|
|
84
55
|
invalidCheckpointProposalOffenseKeys;
|
|
56
|
+
oversizedProposalOffenseKeys;
|
|
85
57
|
badAttestationOffenseKeys;
|
|
86
|
-
slotsWithProposalEquivocation;
|
|
87
58
|
/** Tracks the last checkpoint proposal we attested to, to prevent equivocation. */ lastAttestedProposal;
|
|
88
59
|
constructor(keyStore, epochCache, p2pClient, proposalHandler, blockSource, checkpointsBuilder, worldState, l1ToL2MessageSource, config, blobClient, slashingProtectionSigner, dateProvider = new DateProvider(), telemetry = getTelemetryClient(), log = createLogger('validator')){
|
|
89
|
-
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 = FifoSet.withLimit(MAX_PROPOSERS_OF_INVALID_BLOCKS), this.
|
|
60
|
+
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 = FifoSet.withLimit(MAX_PROPOSERS_OF_INVALID_BLOCKS), this.invalidCheckpointProposalOffenseKeys = FifoSet.withLimit(MAX_TRACKED_INVALID_CHECKPOINT_PROPOSALS), this.oversizedProposalOffenseKeys = FifoSet.withLimit(MAX_TRACKED_INVALID_CHECKPOINT_PROPOSALS), this.badAttestationOffenseKeys = FifoSet.withLimit(MAX_TRACKED_BAD_ATTESTATIONS);
|
|
90
61
|
// Create child logger with fisherman prefix if in fisherman mode
|
|
91
62
|
this.log = config.fishermanMode ? log.createChild('[FISHERMAN]') : log;
|
|
92
63
|
this.tracer = telemetry.getTracer('Validator');
|
|
@@ -216,10 +187,10 @@ const SLASHABLE_CHECKPOINT_PROPOSAL_VALIDATION_RESULT = {
|
|
|
216
187
|
return this.config;
|
|
217
188
|
}
|
|
218
189
|
hasProposalEquivocation(slotNumber) {
|
|
219
|
-
return this.
|
|
190
|
+
return this.proposalHandler.hasProposalEquivocation(slotNumber);
|
|
220
191
|
}
|
|
221
192
|
hasInvalidProposals(slotNumber) {
|
|
222
|
-
return this.
|
|
193
|
+
return this.proposalHandler.hasInvalidProposals(slotNumber);
|
|
223
194
|
}
|
|
224
195
|
updateConfig(config) {
|
|
225
196
|
this.config = {
|
|
@@ -269,6 +240,10 @@ const SLASHABLE_CHECKPOINT_PROPOSAL_VALIDATION_RESULT = {
|
|
|
269
240
|
this.p2pClient.registerDuplicateProposalCallback((info)=>{
|
|
270
241
|
this.handleDuplicateProposal(info);
|
|
271
242
|
});
|
|
243
|
+
// Oversized proposal handler - triggers slashing for proposals beyond the per-checkpoint block limit
|
|
244
|
+
this.p2pClient.registerOversizedProposalCallback((info)=>{
|
|
245
|
+
this.handleOversizedProposal(info);
|
|
246
|
+
});
|
|
272
247
|
// Duplicate attestation handler - triggers slashing for attestation equivocation
|
|
273
248
|
this.p2pClient.registerDuplicateAttestationCallback((info)=>{
|
|
274
249
|
this.handleDuplicateAttestation(info);
|
|
@@ -529,7 +504,8 @@ const SLASHABLE_CHECKPOINT_PROPOSAL_VALIDATION_RESULT = {
|
|
|
529
504
|
if (!SLASHABLE_CHECKPOINT_PROPOSAL_VALIDATION_RESULT[result.reason]) {
|
|
530
505
|
return;
|
|
531
506
|
}
|
|
532
|
-
this
|
|
507
|
+
// The slot is already marked invalid by the all-nodes checkpoint handler that invokes this callback,
|
|
508
|
+
// so we only emit the proposer slash event here.
|
|
533
509
|
if (this.slashInvalidCheckpointProposal(proposal)) {
|
|
534
510
|
this.log.info(`Detected invalid checkpoint proposal offense`, {
|
|
535
511
|
...proposalInfo,
|
|
@@ -564,11 +540,11 @@ const SLASHABLE_CHECKPOINT_PROPOSAL_VALIDATION_RESULT = {
|
|
|
564
540
|
return true;
|
|
565
541
|
}
|
|
566
542
|
markInvalidProposalSlot(slotNumber) {
|
|
567
|
-
this.
|
|
543
|
+
this.proposalHandler.markInvalidProposalSlot(slotNumber);
|
|
568
544
|
}
|
|
569
545
|
handleCheckpointAttestation(attestation) {
|
|
570
546
|
const slotNumber = attestation.slotNumber;
|
|
571
|
-
if (!this.
|
|
547
|
+
if (!this.proposalHandler.hasInvalidProposals(slotNumber) || this.proposalHandler.hasProposalEquivocation(slotNumber)) {
|
|
572
548
|
return;
|
|
573
549
|
}
|
|
574
550
|
const attester = attestation.getSender();
|
|
@@ -602,11 +578,37 @@ const SLASHABLE_CHECKPOINT_PROPOSAL_VALIDATION_RESULT = {
|
|
|
602
578
|
]);
|
|
603
579
|
}
|
|
604
580
|
/**
|
|
581
|
+
* Handle detection of an oversized block proposal: one whose index within its checkpoint lands at or
|
|
582
|
+
* beyond the consensus per-checkpoint block limit. A single signed proposal at an illegal index is
|
|
583
|
+
* self-contained evidence, so emit an invalid-block-proposal slash event for the proposer, deduped per
|
|
584
|
+
* (proposer, slot) since the p2p layer reports every oversized proposal it stores.
|
|
585
|
+
*/ handleOversizedProposal(info) {
|
|
586
|
+
const { slot, proposer } = info;
|
|
587
|
+
const offenseType = OffenseType.BROADCASTED_INVALID_BLOCK_PROPOSAL;
|
|
588
|
+
if (!this.oversizedProposalOffenseKeys.addIfAbsent(`${proposer.toString()}:${offenseType}:${slot}`)) {
|
|
589
|
+
return;
|
|
590
|
+
}
|
|
591
|
+
this.log.info(`Detected oversized block proposal offense from ${proposer.toString()} at slot ${slot}`, {
|
|
592
|
+
proposer: proposer.toString(),
|
|
593
|
+
slot,
|
|
594
|
+
amount: this.config.slashBroadcastedInvalidBlockPenalty,
|
|
595
|
+
offenseType: getOffenseTypeName(offenseType)
|
|
596
|
+
});
|
|
597
|
+
this.emit(WANT_TO_SLASH_EVENT, [
|
|
598
|
+
{
|
|
599
|
+
validator: proposer,
|
|
600
|
+
amount: this.config.slashBroadcastedInvalidBlockPenalty,
|
|
601
|
+
offenseType,
|
|
602
|
+
epochOrSlot: BigInt(slot)
|
|
603
|
+
}
|
|
604
|
+
]);
|
|
605
|
+
}
|
|
606
|
+
/**
|
|
605
607
|
* Handle detection of a duplicate proposal (equivocation).
|
|
606
608
|
* Emits a slash event when a proposer sends multiple proposals for the same position.
|
|
607
609
|
*/ handleDuplicateProposal(info) {
|
|
608
610
|
const { slot, proposer, type } = info;
|
|
609
|
-
this.
|
|
611
|
+
this.proposalHandler.markProposalEquivocation(slot);
|
|
610
612
|
this.log.info(`Detected duplicate ${type} proposal offense from ${proposer.toString()} at slot ${slot}`, {
|
|
611
613
|
proposer: proposer.toString(),
|
|
612
614
|
slot,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aztec/validator-client",
|
|
3
|
-
"version": "5.0.0-rc.
|
|
3
|
+
"version": "5.0.0-rc.2",
|
|
4
4
|
"main": "dest/index.js",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"exports": {
|
|
@@ -64,30 +64,30 @@
|
|
|
64
64
|
]
|
|
65
65
|
},
|
|
66
66
|
"dependencies": {
|
|
67
|
-
"@aztec/blob-client": "5.0.0-rc.
|
|
68
|
-
"@aztec/blob-lib": "5.0.0-rc.
|
|
69
|
-
"@aztec/constants": "5.0.0-rc.
|
|
70
|
-
"@aztec/epoch-cache": "5.0.0-rc.
|
|
71
|
-
"@aztec/ethereum": "5.0.0-rc.
|
|
72
|
-
"@aztec/foundation": "5.0.0-rc.
|
|
73
|
-
"@aztec/node-keystore": "5.0.0-rc.
|
|
74
|
-
"@aztec/noir-protocol-circuits-types": "5.0.0-rc.
|
|
75
|
-
"@aztec/p2p": "5.0.0-rc.
|
|
76
|
-
"@aztec/protocol-contracts": "5.0.0-rc.
|
|
77
|
-
"@aztec/prover-client": "5.0.0-rc.
|
|
78
|
-
"@aztec/simulator": "5.0.0-rc.
|
|
79
|
-
"@aztec/slasher": "5.0.0-rc.
|
|
80
|
-
"@aztec/stdlib": "5.0.0-rc.
|
|
81
|
-
"@aztec/telemetry-client": "5.0.0-rc.
|
|
82
|
-
"@aztec/validator-ha-signer": "5.0.0-rc.
|
|
67
|
+
"@aztec/blob-client": "5.0.0-rc.2",
|
|
68
|
+
"@aztec/blob-lib": "5.0.0-rc.2",
|
|
69
|
+
"@aztec/constants": "5.0.0-rc.2",
|
|
70
|
+
"@aztec/epoch-cache": "5.0.0-rc.2",
|
|
71
|
+
"@aztec/ethereum": "5.0.0-rc.2",
|
|
72
|
+
"@aztec/foundation": "5.0.0-rc.2",
|
|
73
|
+
"@aztec/node-keystore": "5.0.0-rc.2",
|
|
74
|
+
"@aztec/noir-protocol-circuits-types": "5.0.0-rc.2",
|
|
75
|
+
"@aztec/p2p": "5.0.0-rc.2",
|
|
76
|
+
"@aztec/protocol-contracts": "5.0.0-rc.2",
|
|
77
|
+
"@aztec/prover-client": "5.0.0-rc.2",
|
|
78
|
+
"@aztec/simulator": "5.0.0-rc.2",
|
|
79
|
+
"@aztec/slasher": "5.0.0-rc.2",
|
|
80
|
+
"@aztec/stdlib": "5.0.0-rc.2",
|
|
81
|
+
"@aztec/telemetry-client": "5.0.0-rc.2",
|
|
82
|
+
"@aztec/validator-ha-signer": "5.0.0-rc.2",
|
|
83
83
|
"koa": "^2.16.1",
|
|
84
84
|
"koa-router": "^13.1.1",
|
|
85
85
|
"tslib": "^2.4.0",
|
|
86
86
|
"viem": "npm:@aztec/viem@2.38.2"
|
|
87
87
|
},
|
|
88
88
|
"devDependencies": {
|
|
89
|
-
"@aztec/archiver": "5.0.0-rc.
|
|
90
|
-
"@aztec/world-state": "5.0.0-rc.
|
|
89
|
+
"@aztec/archiver": "5.0.0-rc.2",
|
|
90
|
+
"@aztec/world-state": "5.0.0-rc.2",
|
|
91
91
|
"@electric-sql/pglite": "^0.3.14",
|
|
92
92
|
"@jest/globals": "^30.0.0",
|
|
93
93
|
"@types/jest": "^30.0.0",
|
package/src/proposal_handler.ts
CHANGED
|
@@ -13,6 +13,7 @@ import {
|
|
|
13
13
|
import { pick } from '@aztec/foundation/collection';
|
|
14
14
|
import { Fr } from '@aztec/foundation/curves/bn254';
|
|
15
15
|
import { TimeoutError } from '@aztec/foundation/error';
|
|
16
|
+
import { FifoSet } from '@aztec/foundation/fifo-set';
|
|
16
17
|
import type { LogData } from '@aztec/foundation/log';
|
|
17
18
|
import { createLogger } from '@aztec/foundation/log';
|
|
18
19
|
import { retryUntil } from '@aztec/foundation/retry';
|
|
@@ -152,7 +153,49 @@ type BlockProposalSlotValidationResult =
|
|
|
152
153
|
| { isValid: true }
|
|
153
154
|
| { isValid: false; reason: 'block_proposal_beyond_checkpoint' | 'checkpoint_proposal_equivocation' };
|
|
154
155
|
|
|
155
|
-
|
|
156
|
+
const MAX_TRACKED_INVALID_PROPOSAL_SLOTS = 1000;
|
|
157
|
+
|
|
158
|
+
/** Block-proposal validation failures that constitute a slashable invalid-block offense. */
|
|
159
|
+
export const SLASHABLE_BLOCK_PROPOSAL_VALIDATION_RESULT: BlockProposalValidationFailureReason[] = [
|
|
160
|
+
'state_mismatch',
|
|
161
|
+
'failed_txs',
|
|
162
|
+
'global_variables_mismatch',
|
|
163
|
+
'invalid_proposal',
|
|
164
|
+
'parent_block_wrong_slot',
|
|
165
|
+
'in_hash_mismatch',
|
|
166
|
+
];
|
|
167
|
+
|
|
168
|
+
/** Checkpoint-proposal validation failures that constitute a slashable invalid-checkpoint offense. */
|
|
169
|
+
export const SLASHABLE_CHECKPOINT_PROPOSAL_VALIDATION_RESULT: Record<
|
|
170
|
+
CheckpointProposalValidationFailureReason,
|
|
171
|
+
boolean
|
|
172
|
+
> = {
|
|
173
|
+
// enabled
|
|
174
|
+
['invalid_fee_asset_price_modifier']: true,
|
|
175
|
+
['checkpoint_header_mismatch']: true,
|
|
176
|
+
// These late mismatches should normally be caught by earlier checks, but if reached after validating the local
|
|
177
|
+
// checkpoint inputs, the proposer-signed payload disagrees with deterministic recomputation.
|
|
178
|
+
['archive_mismatch']: true,
|
|
179
|
+
['out_hash_mismatch']: true,
|
|
180
|
+
['no_blocks_for_slot']: true,
|
|
181
|
+
['too_many_blocks_in_checkpoint']: true,
|
|
182
|
+
['checkpoint_validation_failed']: true,
|
|
183
|
+
['last_block_archive_mismatch']: true,
|
|
184
|
+
|
|
185
|
+
// disabled
|
|
186
|
+
['invalid_signature']: false,
|
|
187
|
+
['last_block_not_found']: false,
|
|
188
|
+
['block_fetch_error']: false,
|
|
189
|
+
['checkpoint_already_published']: false,
|
|
190
|
+
};
|
|
191
|
+
|
|
192
|
+
/**
|
|
193
|
+
* Handles block and checkpoint proposals for both validator and non-validator nodes. Also tracks which slots
|
|
194
|
+
* had a slashable invalid proposal or a proposal equivocation, exposing them via the
|
|
195
|
+
* `InvalidProposalSlotSource` interface consumed by the attested-invalid-proposal slashing watcher. The
|
|
196
|
+
* tracking is populated as a side effect of validating/re-executing proposals, so any node that re-executes
|
|
197
|
+
* proposals (the default) can serve it — not only validators.
|
|
198
|
+
*/
|
|
156
199
|
export class ProposalHandler {
|
|
157
200
|
public readonly tracer: Tracer;
|
|
158
201
|
|
|
@@ -175,6 +218,12 @@ export class ProposalHandler {
|
|
|
175
218
|
|
|
176
219
|
private checkpointProposalValidationFailureCallback?: CheckpointProposalValidationFailureCallback;
|
|
177
220
|
|
|
221
|
+
/** Slots at which a slashable invalid block or checkpoint proposal was observed. */
|
|
222
|
+
private readonly slotsWithInvalidProposals = FifoSet.withLimit<SlotNumber>(MAX_TRACKED_INVALID_PROPOSAL_SLOTS);
|
|
223
|
+
|
|
224
|
+
/** Slots at which a proposal equivocation was observed; suppresses attested-to-invalid-proposal slashing. */
|
|
225
|
+
private readonly slotsWithProposalEquivocation = FifoSet.withLimit<SlotNumber>(MAX_TRACKED_INVALID_PROPOSAL_SLOTS);
|
|
226
|
+
|
|
178
227
|
constructor(
|
|
179
228
|
private checkpointsBuilder: FullNodeCheckpointsBuilder,
|
|
180
229
|
private worldState: WorldStateSynchronizer,
|
|
@@ -221,6 +270,26 @@ export class ProposalHandler {
|
|
|
221
270
|
this.reexecutionTracker.recordOutcome(slot, archive, 'valid', checkpointNumber);
|
|
222
271
|
}
|
|
223
272
|
|
|
273
|
+
/** Whether a slashable invalid block or checkpoint proposal was observed at the given slot (InvalidProposalSlotSource). */
|
|
274
|
+
public hasInvalidProposals(slotNumber: SlotNumber): boolean {
|
|
275
|
+
return this.slotsWithInvalidProposals.has(slotNumber);
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
/** Whether a proposal equivocation was observed at the given slot (InvalidProposalSlotSource). */
|
|
279
|
+
public hasProposalEquivocation(slotNumber: SlotNumber): boolean {
|
|
280
|
+
return this.slotsWithProposalEquivocation.has(slotNumber);
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
/** Records a slot as having a slashable invalid proposal, for offense observers (sentinel/slasher watchers). */
|
|
284
|
+
public markInvalidProposalSlot(slotNumber: SlotNumber): void {
|
|
285
|
+
this.slotsWithInvalidProposals.add(slotNumber);
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
/** Records a slot as having a proposal equivocation, which suppresses attested-to-invalid-proposal slashing. */
|
|
289
|
+
public markProposalEquivocation(slotNumber: SlotNumber): void {
|
|
290
|
+
this.slotsWithProposalEquivocation.add(slotNumber);
|
|
291
|
+
}
|
|
292
|
+
|
|
224
293
|
/**
|
|
225
294
|
* Registers handlers for block and checkpoint proposals on the p2p client.
|
|
226
295
|
* Records the p2p client so validation can inspect retained proposals.
|
|
@@ -256,6 +325,18 @@ export class ProposalHandler {
|
|
|
256
325
|
});
|
|
257
326
|
return true;
|
|
258
327
|
} else {
|
|
328
|
+
// Track invalid proposals / equivocations so offense observers (the attested-invalid-proposal
|
|
329
|
+
// watcher) work on non-validator nodes too. Validators populate these via their own handlers.
|
|
330
|
+
// Skip invalid-proposal marking while the escape hatch is open, matching the validator path,
|
|
331
|
+
// which intentionally disables invalid-block slashing then.
|
|
332
|
+
if (result.reason === 'checkpoint_proposal_equivocation') {
|
|
333
|
+
this.markProposalEquivocation(slotNumber);
|
|
334
|
+
} else if (
|
|
335
|
+
SLASHABLE_BLOCK_PROPOSAL_VALIDATION_RESULT.includes(result.reason) &&
|
|
336
|
+
!(await this.epochCache.isEscapeHatchOpenAtSlot(slotNumber))
|
|
337
|
+
) {
|
|
338
|
+
this.markInvalidProposalSlot(slotNumber);
|
|
339
|
+
}
|
|
259
340
|
this.log.warn(
|
|
260
341
|
`Non-validator block proposal ${blockNumber} at slot ${slotNumber} failed processing with ${result.reason}`,
|
|
261
342
|
{ blockNumber: result.blockNumber, slotNumber, reason: result.reason },
|
|
@@ -270,6 +351,11 @@ export class ProposalHandler {
|
|
|
270
351
|
|
|
271
352
|
p2pClient.registerBlockProposalHandler(blockHandler);
|
|
272
353
|
|
|
354
|
+
// p2p detects duplicate (equivocated) proposals without routing them through the handlers above, so mark
|
|
355
|
+
// the slot as equivocated here. This suppresses false-positive attested-to-invalid-proposal slashing on
|
|
356
|
+
// non-validator offense collectors. Validators overwrite this with their own richer handler.
|
|
357
|
+
p2pClient.registerDuplicateProposalCallback(info => this.markProposalEquivocation(info.slot));
|
|
358
|
+
|
|
273
359
|
// All-nodes checkpoint proposal handler: validates, caches, and sets proposed checkpoint for pipelining.
|
|
274
360
|
// Runs for all nodes (validators and non-validators). Validators get the cached result in the
|
|
275
361
|
// validator-specific callback (attestToCheckpointProposal) which runs after this one.
|
|
@@ -318,6 +404,12 @@ export class ProposalHandler {
|
|
|
318
404
|
|
|
319
405
|
const result = await this.handleCheckpointProposal(proposal, proposalInfo);
|
|
320
406
|
if (!result.isValid) {
|
|
407
|
+
// Track invalid checkpoint proposals so offense observers (the attested-invalid-proposal watcher)
|
|
408
|
+
// work on non-validator nodes too. This handler runs for all nodes; validators also mark via the
|
|
409
|
+
// failure callback below (idempotent).
|
|
410
|
+
if (SLASHABLE_CHECKPOINT_PROPOSAL_VALIDATION_RESULT[result.reason]) {
|
|
411
|
+
this.markInvalidProposalSlot(proposal.slotNumber);
|
|
412
|
+
}
|
|
321
413
|
await this.checkpointProposalValidationFailureCallback?.(proposal, result, proposalInfo);
|
|
322
414
|
} else if (this.archiver) {
|
|
323
415
|
const set = await this.setProposedCheckpoint(proposal);
|
|
@@ -410,8 +502,12 @@ export class ProposalHandler {
|
|
|
410
502
|
: BlockNumber(parentBlock.header.getBlockNumber() + 1);
|
|
411
503
|
proposalInfo.blockNumber = blockNumber;
|
|
412
504
|
|
|
413
|
-
// Check that this block number does not exist already
|
|
414
|
-
|
|
505
|
+
// Check that this block number does not exist already. During a reorg the archiver can still hold a
|
|
506
|
+
// stale block at this number (a different archive, about to be pruned) while the proposal carries the
|
|
507
|
+
// rebuilt replacement; resolveExistingBlockAtNumber waits for the local prune in that case so the
|
|
508
|
+
// rebuilt block is processed in time to attest, rather than being permanently dropped on a bare
|
|
509
|
+
// number collision.
|
|
510
|
+
const existingBlock = await this.resolveExistingBlockAtNumber(blockNumber, proposal.archive, slotNumber);
|
|
415
511
|
if (existingBlock) {
|
|
416
512
|
this.log.warn(`Block number ${blockNumber} already exists, skipping processing`, proposalInfo);
|
|
417
513
|
return { isValid: false, blockNumber, reason: 'block_number_already_exists' };
|
|
@@ -561,6 +657,63 @@ export class ProposalHandler {
|
|
|
561
657
|
}
|
|
562
658
|
}
|
|
563
659
|
|
|
660
|
+
/**
|
|
661
|
+
* Resolves whether a block genuinely already exists at `blockNumber`. Returns the existing block only if
|
|
662
|
+
* it is a true duplicate of the proposal (matching archive). During a reorg the archiver can still hold a
|
|
663
|
+
* stale fork at this number (different archive) that is about to be pruned; in that case this forces L1
|
|
664
|
+
* sync and waits, bounded by the re-execution deadline, for the prune to land, then returns `undefined` so
|
|
665
|
+
* the rebuilt block proposal can be processed in time to attest. If the prune does not complete before the
|
|
666
|
+
* deadline it returns the stale block, so the caller falls back to the safe `block_number_already_exists`
|
|
667
|
+
* rejection.
|
|
668
|
+
*/
|
|
669
|
+
private async resolveExistingBlockAtNumber(
|
|
670
|
+
blockNumber: BlockNumber,
|
|
671
|
+
proposalArchive: Fr,
|
|
672
|
+
slotNumber: SlotNumber,
|
|
673
|
+
): Promise<BlockData | undefined> {
|
|
674
|
+
const existingBlock = await this.blockSource.getBlockData({ number: blockNumber });
|
|
675
|
+
if (!existingBlock || existingBlock.archive.root.equals(proposalArchive)) {
|
|
676
|
+
return existingBlock;
|
|
677
|
+
}
|
|
678
|
+
|
|
679
|
+
// A different block already occupies this number: it may be a stale fork being pruned during a reorg, not a
|
|
680
|
+
// genuine duplicate. Wait for the local prune rather than permanently rejecting the proposal.
|
|
681
|
+
const deadline = this.getReexecutionDeadline(slotNumber);
|
|
682
|
+
if (deadline.getTime() - this.dateProvider.now() <= 0) {
|
|
683
|
+
return existingBlock;
|
|
684
|
+
}
|
|
685
|
+
|
|
686
|
+
this.log.warn(`Block number ${blockNumber} already exists, awaiting potential prune`, {
|
|
687
|
+
blockNumber,
|
|
688
|
+
existingArchive: existingBlock.archive.root.toString(),
|
|
689
|
+
proposalArchive: proposalArchive.toString(),
|
|
690
|
+
});
|
|
691
|
+
|
|
692
|
+
try {
|
|
693
|
+
const { block } = await retryUntil(
|
|
694
|
+
async () => {
|
|
695
|
+
await this.blockSource.syncImmediate();
|
|
696
|
+
const block = await this.blockSource.getBlockData({ number: blockNumber });
|
|
697
|
+
// Resolve once the existing block is gone (pruned) or has been replaced by one matching the
|
|
698
|
+
// proposal — the same condition as the early return above. A matching block is returned so the
|
|
699
|
+
// caller still treats it as a genuine duplicate; an `undefined` (pruned) block lets the proposal
|
|
700
|
+
// be processed. Wrap in an object so the `undefined` case is still a truthy retry result.
|
|
701
|
+
return block === undefined || block.archive.root.equals(proposalArchive) ? { block } : undefined;
|
|
702
|
+
},
|
|
703
|
+
`prune of stale block ${blockNumber}`,
|
|
704
|
+
{ deadline, dateProvider: this.dateProvider },
|
|
705
|
+
0.5,
|
|
706
|
+
);
|
|
707
|
+
return block;
|
|
708
|
+
} catch (err) {
|
|
709
|
+
if (err instanceof TimeoutError) {
|
|
710
|
+
this.log.warn(`Timed out waiting for stale block ${blockNumber} to be pruned`, { blockNumber });
|
|
711
|
+
return existingBlock;
|
|
712
|
+
}
|
|
713
|
+
throw err;
|
|
714
|
+
}
|
|
715
|
+
}
|
|
716
|
+
|
|
564
717
|
private computeCheckpointNumber(
|
|
565
718
|
proposal: BlockProposal,
|
|
566
719
|
parentBlock: 'genesis' | BlockData,
|
|
@@ -968,6 +1121,7 @@ export class ProposalHandler {
|
|
|
968
1121
|
};
|
|
969
1122
|
}
|
|
970
1123
|
|
|
1124
|
+
// Note this condition should never trigger, since we dont process block proposals that exceed indexWithinCheckpoint
|
|
971
1125
|
const maxBlocksPerCheckpoint = this.config.maxBlocksPerCheckpoint;
|
|
972
1126
|
if (maxBlocksPerCheckpoint !== undefined && blocks.length > maxBlocksPerCheckpoint) {
|
|
973
1127
|
this.log.warn(`Checkpoint proposal exceeds maxBlocksPerCheckpoint`, {
|
package/src/validator.ts
CHANGED
|
@@ -11,7 +11,7 @@ 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,
|
|
@@ -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(),
|