@aztec/validator-client 5.0.0-nightly.20260505 → 5.0.0-nightly.20260514
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/factory.js +1 -1
- package/dest/proposal_handler.d.ts +4 -2
- package/dest/proposal_handler.d.ts.map +1 -1
- package/dest/proposal_handler.js +54 -21
- package/dest/validator.d.ts +9 -1
- package/dest/validator.d.ts.map +1 -1
- package/dest/validator.js +93 -25
- package/package.json +19 -19
- package/src/factory.ts +1 -1
- package/src/proposal_handler.ts +47 -30
- package/src/validator.ts +113 -28
package/dest/factory.js
CHANGED
|
@@ -10,7 +10,7 @@ export function createProposalHandler(config, deps) {
|
|
|
10
10
|
maxBlocksPerCheckpoint: config.maxBlocksPerCheckpoint,
|
|
11
11
|
signatureContext: {
|
|
12
12
|
chainId: config.l1ChainId,
|
|
13
|
-
rollupAddress: config.
|
|
13
|
+
rollupAddress: config.rollupAddress
|
|
14
14
|
}
|
|
15
15
|
});
|
|
16
16
|
return new ProposalHandler(deps.checkpointsBuilder, deps.worldState, deps.blockSource, deps.l1ToL2MessageSource, deps.p2pClient.getTxProvider(), blockProposalValidator, deps.epochCache, config, deps.blobClient, metrics, deps.dateProvider, deps.telemetry, undefined);
|
|
@@ -56,7 +56,9 @@ export declare class ProposalHandler {
|
|
|
56
56
|
private dateProvider;
|
|
57
57
|
private log;
|
|
58
58
|
readonly tracer: Tracer;
|
|
59
|
-
/** Cached last checkpoint validation result to avoid double-validation on validator nodes.
|
|
59
|
+
/** Cached last checkpoint validation result to avoid double-validation on validator nodes.
|
|
60
|
+
* Keyed by signed-payload hash so two proposals at the same (slot, archive) but with a
|
|
61
|
+
* different `feeAssetPriceModifier` (or any other signed field) are validated independently. */
|
|
60
62
|
private lastCheckpointValidationResult?;
|
|
61
63
|
/** Archiver reference for setting proposed checkpoints (pipelining). Set via register(). */
|
|
62
64
|
private archiver?;
|
|
@@ -105,4 +107,4 @@ export declare class ProposalHandler {
|
|
|
105
107
|
private setProposedCheckpointFromBlocks;
|
|
106
108
|
}
|
|
107
109
|
export {};
|
|
108
|
-
//# sourceMappingURL=data:application/json;base64,
|
|
110
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicHJvcG9zYWxfaGFuZGxlci5kLnRzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vc3JjL3Byb3Bvc2FsX2hhbmRsZXIudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsT0FBTyxLQUFLLEVBQUUsUUFBUSxFQUFFLE1BQU0saUJBQWlCLENBQUM7QUFDaEQsT0FBTyxLQUFLLEVBQUUsbUJBQW1CLEVBQUUsTUFBTSwyQkFBMkIsQ0FBQztBQUdyRSxPQUFPLEtBQUssRUFBRSxVQUFVLEVBQUUsTUFBTSxvQkFBb0IsQ0FBQztBQUVyRCxPQUFPLEVBQ0wsV0FBVyxFQUNYLGdCQUFnQixFQUdqQixNQUFNLGlDQUFpQyxDQUFDO0FBRXpDLE9BQU8sRUFBRSxFQUFFLEVBQUUsTUFBTSxnQ0FBZ0MsQ0FBQztBQUVwRCxPQUFPLEtBQUssRUFBRSxPQUFPLEVBQUUsTUFBTSx1QkFBdUIsQ0FBQztBQUdyRCxPQUFPLEVBQUUsWUFBWSxFQUFTLE1BQU0seUJBQXlCLENBQUM7QUFDOUQsT0FBTyxLQUFLLEVBQUUsR0FBRyxFQUFFLE1BQU0sRUFBRSxNQUFNLFlBQVksQ0FBQztBQUM5QyxPQUFPLEVBQUUsc0JBQXNCLEVBQUUsTUFBTSwyQkFBMkIsQ0FBQztBQUNuRSxPQUFPLEtBQUssRUFBYSxPQUFPLEVBQUUsV0FBVyxFQUFFLGFBQWEsRUFBRSxNQUFNLHFCQUFxQixDQUFDO0FBSTFGLE9BQU8sS0FBSyxFQUFFLFdBQVcsRUFBRSx5QkFBeUIsRUFBRSxzQkFBc0IsRUFBRSxNQUFNLGlDQUFpQyxDQUFDO0FBQ3RILE9BQU8sRUFDTCxLQUFLLG1CQUFtQixFQUd6QixNQUFNLHlCQUF5QixDQUFDO0FBQ2pDLE9BQU8sS0FBSyxFQUFFLGFBQWEsRUFBeUIsc0JBQXNCLEVBQUUsTUFBTSxtQkFBbUIsQ0FBQztBQUV0RyxPQUFPLEtBQUssRUFBNkIsUUFBUSxFQUFFLEVBQUUsRUFBRSxNQUFNLGtCQUFrQixDQUFDO0FBUWhGLE9BQU8sRUFBRSxLQUFLLGVBQWUsRUFB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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,
|
|
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
|
@@ -73,7 +73,7 @@ import { TimeoutError } from '@aztec/foundation/error';
|
|
|
73
73
|
import { createLogger } from '@aztec/foundation/log';
|
|
74
74
|
import { retryUntil } from '@aztec/foundation/retry';
|
|
75
75
|
import { DateProvider, Timer } from '@aztec/foundation/timer';
|
|
76
|
-
import { validateCheckpoint } from '@aztec/stdlib/checkpoint';
|
|
76
|
+
import { getPreviousCheckpointOutHashes, validateCheckpoint } from '@aztec/stdlib/checkpoint';
|
|
77
77
|
import { getEpochAtSlot, getTimestampForSlot } from '@aztec/stdlib/epoch-helpers';
|
|
78
78
|
import { Gas } from '@aztec/stdlib/gas';
|
|
79
79
|
import { accumulateCheckpointOutHashes, computeInHashFromL1ToL2Messages } from '@aztec/stdlib/messaging';
|
|
@@ -94,7 +94,9 @@ import { getTelemetryClient } from '@aztec/telemetry-client';
|
|
|
94
94
|
dateProvider;
|
|
95
95
|
log;
|
|
96
96
|
tracer;
|
|
97
|
-
/** Cached last checkpoint validation result to avoid double-validation on validator nodes.
|
|
97
|
+
/** Cached last checkpoint validation result to avoid double-validation on validator nodes.
|
|
98
|
+
* Keyed by signed-payload hash so two proposals at the same (slot, archive) but with a
|
|
99
|
+
* different `feeAssetPriceModifier` (or any other signed field) are validated independently. */ lastCheckpointValidationResult;
|
|
98
100
|
/** Archiver reference for setting proposed checkpoints (pipelining). Set via register(). */ archiver;
|
|
99
101
|
/** Returns current validator addresses for own-proposal detection. Set via register(). */ getOwnValidatorAddresses;
|
|
100
102
|
constructor(checkpointsBuilder, worldState, blockSource, l1ToL2MessageSource, txProvider, blockProposalValidator, epochCache, config, blobClient, metrics, dateProvider = new DateProvider(), telemetry = getTelemetryClient(), log = createLogger('validator:proposal-handler')){
|
|
@@ -269,7 +271,9 @@ import { getTelemetryClient } from '@aztec/telemetry-client';
|
|
|
269
271
|
const blockNumber = parentBlock === 'genesis' ? BlockNumber(INITIAL_L2_BLOCK_NUM) : BlockNumber(parentBlock.header.getBlockNumber() + 1);
|
|
270
272
|
proposalInfo.blockNumber = blockNumber;
|
|
271
273
|
// Check that this block number does not exist already
|
|
272
|
-
const existingBlock = await this.blockSource.
|
|
274
|
+
const existingBlock = await this.blockSource.getBlockData({
|
|
275
|
+
number: blockNumber
|
|
276
|
+
});
|
|
273
277
|
if (existingBlock) {
|
|
274
278
|
this.log.warn(`Block number ${blockNumber} already exists, skipping processing`, proposalInfo);
|
|
275
279
|
return {
|
|
@@ -331,9 +335,18 @@ import { getTelemetryClient } from '@aztec/telemetry-client';
|
|
|
331
335
|
reason: 'txs_not_available'
|
|
332
336
|
};
|
|
333
337
|
}
|
|
334
|
-
// Collect the out hashes of all the checkpoints before this one in the same epoch
|
|
338
|
+
// Collect the out hashes of all the checkpoints before this one in the same epoch.
|
|
339
|
+
// Mirror the proposer-side fallback: under pipelining the immediately-preceding cp may not
|
|
340
|
+
// yet be on L1, in which case the helper grafts the locally-known proposed cp's outHash.
|
|
335
341
|
const epoch = getEpochAtSlot(slotNumber, this.epochCache.getL1Constants());
|
|
336
|
-
const previousCheckpointOutHashes =
|
|
342
|
+
const previousCheckpointOutHashes = await getPreviousCheckpointOutHashes({
|
|
343
|
+
blockSource: this.blockSource,
|
|
344
|
+
epoch,
|
|
345
|
+
checkpointNumber,
|
|
346
|
+
l1Constants: this.epochCache.getL1Constants(),
|
|
347
|
+
pipeliningEnabled: this.epochCache.isProposerPipeliningEnabled(),
|
|
348
|
+
log: this.log
|
|
349
|
+
});
|
|
337
350
|
// Try re-executing the transactions in the proposal if needed
|
|
338
351
|
let reexecutionResult;
|
|
339
352
|
try {
|
|
@@ -374,7 +387,11 @@ import { getTelemetryClient } from '@aztec/telemetry-client';
|
|
|
374
387
|
const currentTime = this.dateProvider.now();
|
|
375
388
|
const timeoutDurationMs = deadline.getTime() - currentTime;
|
|
376
389
|
try {
|
|
377
|
-
return await this.blockSource.
|
|
390
|
+
return await this.blockSource.getBlockData({
|
|
391
|
+
archive: parentArchive
|
|
392
|
+
}) ?? (timeoutDurationMs <= 0 ? undefined : await retryUntil(()=>this.blockSource.syncImmediate().then(()=>this.blockSource.getBlockData({
|
|
393
|
+
archive: parentArchive
|
|
394
|
+
})), 'force archiver sync', timeoutDurationMs / 1000, 0.5));
|
|
378
395
|
} catch (err) {
|
|
379
396
|
if (err instanceof TimeoutError) {
|
|
380
397
|
this.log.debug(`Timed out getting parent block by archive root`, {
|
|
@@ -673,8 +690,9 @@ import { getTelemetryClient } from '@aztec/telemetry-client';
|
|
|
673
690
|
* Used by both the all-nodes callback (via register) and the validator client (via delegation).
|
|
674
691
|
*/ async handleCheckpointProposal(proposal, proposalInfo) {
|
|
675
692
|
const slot = proposal.slotNumber;
|
|
676
|
-
|
|
677
|
-
|
|
693
|
+
const payloadHash = proposal.getPayloadHash();
|
|
694
|
+
// Check cache: same signed-payload hash means we already validated this exact proposal.
|
|
695
|
+
if (this.lastCheckpointValidationResult && this.lastCheckpointValidationResult.payloadHash === payloadHash) {
|
|
678
696
|
this.log.debug(`Returning cached validation result for checkpoint proposal at slot ${slot}`, proposalInfo);
|
|
679
697
|
return this.lastCheckpointValidationResult.result;
|
|
680
698
|
}
|
|
@@ -686,8 +704,7 @@ import { getTelemetryClient } from '@aztec/telemetry-client';
|
|
|
686
704
|
reason: 'invalid_signature'
|
|
687
705
|
};
|
|
688
706
|
this.lastCheckpointValidationResult = {
|
|
689
|
-
|
|
690
|
-
slotNumber: slot,
|
|
707
|
+
payloadHash,
|
|
691
708
|
result
|
|
692
709
|
};
|
|
693
710
|
return result;
|
|
@@ -699,16 +716,14 @@ import { getTelemetryClient } from '@aztec/telemetry-client';
|
|
|
699
716
|
reason: 'invalid_fee_asset_price_modifier'
|
|
700
717
|
};
|
|
701
718
|
this.lastCheckpointValidationResult = {
|
|
702
|
-
|
|
703
|
-
slotNumber: slot,
|
|
719
|
+
payloadHash,
|
|
704
720
|
result
|
|
705
721
|
};
|
|
706
722
|
return result;
|
|
707
723
|
}
|
|
708
724
|
const result = await this.validateCheckpointProposal(proposal, proposalInfo);
|
|
709
725
|
this.lastCheckpointValidationResult = {
|
|
710
|
-
|
|
711
|
-
slotNumber: slot,
|
|
726
|
+
payloadHash,
|
|
712
727
|
result
|
|
713
728
|
};
|
|
714
729
|
// Upload blobs to filestore if validation passed (fire and forget)
|
|
@@ -737,7 +752,9 @@ import { getTelemetryClient } from '@aztec/telemetry-client';
|
|
|
737
752
|
try {
|
|
738
753
|
lastBlockHeader = await retryUntil(async ()=>{
|
|
739
754
|
await this.blockSource.syncImmediate();
|
|
740
|
-
return this.blockSource.
|
|
755
|
+
return (await this.blockSource.getBlockData({
|
|
756
|
+
archive: proposal.archive
|
|
757
|
+
}))?.header;
|
|
741
758
|
}, `waiting for block with archive ${proposal.archive.toString()} for slot ${slot}`, timeoutSeconds, 0.5);
|
|
742
759
|
} catch (err) {
|
|
743
760
|
if (err instanceof TimeoutError) {
|
|
@@ -799,9 +816,17 @@ import { getTelemetryClient } from '@aztec/telemetry-client';
|
|
|
799
816
|
const checkpointNumber = firstBlock.checkpointNumber;
|
|
800
817
|
// Get L1-to-L2 messages for this checkpoint
|
|
801
818
|
const l1ToL2Messages = await this.l1ToL2MessageSource.getL1ToL2Messages(checkpointNumber);
|
|
802
|
-
// Collect the out hashes of all the checkpoints before this one in the same epoch
|
|
819
|
+
// Collect the out hashes of all the checkpoints before this one in the same epoch.
|
|
820
|
+
// See note on the analogous block-proposal site: the helper handles pipelining lag.
|
|
803
821
|
const epoch = getEpochAtSlot(slot, this.epochCache.getL1Constants());
|
|
804
|
-
const previousCheckpointOutHashes =
|
|
822
|
+
const previousCheckpointOutHashes = await getPreviousCheckpointOutHashes({
|
|
823
|
+
blockSource: this.blockSource,
|
|
824
|
+
epoch,
|
|
825
|
+
checkpointNumber,
|
|
826
|
+
l1Constants: this.epochCache.getL1Constants(),
|
|
827
|
+
pipeliningEnabled: this.epochCache.isProposerPipeliningEnabled(),
|
|
828
|
+
log: this.log
|
|
829
|
+
});
|
|
805
830
|
// Fork world state at the block before the first block
|
|
806
831
|
const parentBlockNumber = BlockNumber(firstBlock.number - 1);
|
|
807
832
|
const fork = _ts_add_disposable_resource(env, await this.checkpointsBuilder.getFork(parentBlockNumber), true);
|
|
@@ -902,7 +927,9 @@ import { getTelemetryClient } from '@aztec/telemetry-client';
|
|
|
902
927
|
}
|
|
903
928
|
/** Uploads blobs for a checkpoint to the filestore. */ async uploadBlobsForCheckpoint(proposal, proposalInfo) {
|
|
904
929
|
try {
|
|
905
|
-
const lastBlockHeader = await this.blockSource.
|
|
930
|
+
const lastBlockHeader = (await this.blockSource.getBlockData({
|
|
931
|
+
archive: proposal.archive
|
|
932
|
+
}))?.header;
|
|
906
933
|
if (!lastBlockHeader) {
|
|
907
934
|
this.log.warn(`Failed to get last block header for blob upload`, proposalInfo);
|
|
908
935
|
return;
|
|
@@ -932,7 +959,9 @@ import { getTelemetryClient } from '@aztec/telemetry-client';
|
|
|
932
959
|
if (!this.archiver) {
|
|
933
960
|
return false;
|
|
934
961
|
}
|
|
935
|
-
const blockData = await this.blockSource.
|
|
962
|
+
const blockData = await this.blockSource.getBlockData({
|
|
963
|
+
archive: proposal.archive
|
|
964
|
+
});
|
|
936
965
|
if (!blockData) {
|
|
937
966
|
this.log.debug(`Block data not found for checkpoint proposal archive, cannot set proposed checkpoint`, {
|
|
938
967
|
archive: proposal.archive.toString()
|
|
@@ -957,14 +986,18 @@ import { getTelemetryClient } from '@aztec/telemetry-client';
|
|
|
957
986
|
if (!this.archiver) {
|
|
958
987
|
return false;
|
|
959
988
|
}
|
|
960
|
-
let blockData = await this.blockSource.
|
|
989
|
+
let blockData = await this.blockSource.getBlockData({
|
|
990
|
+
archive: proposal.archive
|
|
991
|
+
});
|
|
961
992
|
if (!blockData) {
|
|
962
993
|
// The checkpoint proposal often arrives before the last block finishes re-execution.
|
|
963
994
|
// Retry until we find the data or give up at the end of the slot.
|
|
964
995
|
const nextSlot = this.epochCache.getSlotNow() + 1;
|
|
965
996
|
const timeOfNextSlot = getTimestampForSlot(SlotNumber(nextSlot), await this.archiver.getL1Constants());
|
|
966
997
|
const timeoutSeconds = Math.max(1, Number(timeOfNextSlot) - Math.floor(this.dateProvider.now() / 1000));
|
|
967
|
-
blockData = await retryUntil(()=>this.blockSource.
|
|
998
|
+
blockData = await retryUntil(()=>this.blockSource.getBlockData({
|
|
999
|
+
archive: proposal.archive
|
|
1000
|
+
}), 'block data for own checkpoint proposal', timeoutSeconds, 0.25).catch(()=>undefined);
|
|
968
1001
|
}
|
|
969
1002
|
if (blockData) {
|
|
970
1003
|
await this.archiver.addProposedCheckpoint({
|
package/dest/validator.d.ts
CHANGED
|
@@ -54,6 +54,9 @@ export declare class ValidatorClient extends ValidatorClient_base implements Val
|
|
|
54
54
|
/** Tracks the last epoch in which each attester successfully submitted at least one attestation. */
|
|
55
55
|
private lastAttestedEpochByAttester;
|
|
56
56
|
private proposersOfInvalidBlocks;
|
|
57
|
+
private slotsWithInvalidBlockProposals;
|
|
58
|
+
private slotsWithProposalEquivocation;
|
|
59
|
+
private badAttestationOffenseKeys;
|
|
57
60
|
/** Tracks the last checkpoint proposal we attested to, to prevent equivocation. */
|
|
58
61
|
private lastAttestedProposal?;
|
|
59
62
|
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);
|
|
@@ -97,6 +100,9 @@ export declare class ValidatorClient extends ValidatorClient_base implements Val
|
|
|
97
100
|
*/
|
|
98
101
|
protected uploadBlobsForCheckpoint(proposal: CheckpointProposalCore, proposalInfo: LogData): Promise<void>;
|
|
99
102
|
private slashInvalidBlock;
|
|
103
|
+
private markInvalidProposalSlot;
|
|
104
|
+
private handleCheckpointAttestation;
|
|
105
|
+
private slashAttestedToInvalidCheckpointProposal;
|
|
100
106
|
/**
|
|
101
107
|
* Handle detection of a duplicate proposal (equivocation).
|
|
102
108
|
* Emits a slash event when a proposer sends multiple proposals for the same position.
|
|
@@ -107,6 +113,8 @@ export declare class ValidatorClient extends ValidatorClient_base implements Val
|
|
|
107
113
|
* Emits a slash event when an attester signs attestations for different proposals at the same slot.
|
|
108
114
|
*/
|
|
109
115
|
private handleDuplicateAttestation;
|
|
116
|
+
private getSlotKey;
|
|
117
|
+
private addToBoundedSet;
|
|
110
118
|
createBlockProposal(blockHeader: BlockHeader, checkpointNumber: CheckpointNumber, indexWithinCheckpoint: IndexWithinCheckpoint, inHash: Fr, archive: Fr, txs: Tx[], proposerAddress: EthAddress | undefined, options?: BlockProposalOptions): Promise<BlockProposal>;
|
|
111
119
|
createCheckpointProposal(checkpointHeader: CheckpointHeader, archive: Fr, checkpointNumber: CheckpointNumber, feeAssetPriceModifier: bigint, lastBlockProposal: BlockProposal | undefined, proposerAddress: EthAddress | undefined, options?: CheckpointProposalOptions): Promise<CheckpointProposal>;
|
|
112
120
|
broadcastBlockProposal(proposal: BlockProposal): Promise<void>;
|
|
@@ -116,4 +124,4 @@ export declare class ValidatorClient extends ValidatorClient_base implements Val
|
|
|
116
124
|
private handleAuthRequest;
|
|
117
125
|
}
|
|
118
126
|
export {};
|
|
119
|
-
//# sourceMappingURL=data:application/json;base64,
|
|
127
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoidmFsaWRhdG9yLmQudHMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi9zcmMvdmFsaWRhdG9yLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBLE9BQU8sS0FBSyxFQUFFLG1CQUFtQixFQUFFLE1BQU0sMkJBQTJCLENBQUM7QUFFckUsT0FBTyxLQUFLLEVBQUUsVUFBVSxFQUFFLE1BQU0sb0JBQW9CLENBQUM7QUFDckQsT0FBTyxFQUFFLGdCQUFnQixFQUFlLHFCQUFxQixFQUFFLFVBQVUsRUFBRSxNQUFNLGlDQUFpQyxDQUFDO0FBQ25ILE9BQU8sRUFBRSxFQUFFLEVBQUUsTUFBTSxnQ0FBZ0MsQ0FBQztBQUNwRCxPQUFPLEtBQUssRUFBRSxVQUFVLEVBQUUsTUFBTSwrQkFBK0IsQ0FBQztBQUNoRSxPQUFPLEtBQUssRUFBRSxTQUFTLEVBQUUsTUFBTSxpQ0FBaUMsQ0FBQztBQUNqRSxPQUFPLEVBQUUsS0FBSyxPQUFPLEVBQUUsS0FBSyxNQUFNLEVBQWdCLE1BQU0sdUJBQXVCLENBQUM7QUFHaEYsT0FBTyxFQUFFLFlBQVksRUFBRSxNQUFNLHlCQUF5QixDQUFDO0FBQ3ZELE9BQU8sS0FBSyxFQUFFLGVBQWUsRUFBRSxNQUFNLHNCQUFzQixDQUFDO0FBQzVELE9BQU8sS0FBSyxFQUFtRCxHQUFHLEVBQUUsTUFBTSxFQUFFLE1BQU0sWUFBWSxDQUFDO0FBRS9GLE9BQU8sRUFJTCxLQUFLLE9BQU8sRUFDWixLQUFLLGNBQWMsRUFDcEIsTUFBTSxnQkFBZ0IsQ0FBQztBQUN4QixPQUFPLEtBQUssRUFBRSxZQUFZLEVBQUUsTUFBTSw2QkFBNkIsQ0FBQztBQUNoRSxPQUFPLEtBQUssRUFBRSwrQkFBK0IsRUFBRSxXQUFXLEVBQUUsYUFBYSxFQUFFLE1BQU0scUJBQXFCLENBQUM7QUFFdkcsT0FBTyxLQUFLLEVBQ1YsV0FBVyxFQUNYLFNBQVMsRUFDVCx5QkFBeUIsRUFDekIsc0JBQXNCLEVBQ3ZCLE1BQU0saUNBQWlDLENBQUM7QUFDekMsT0FB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
|
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;AACjE,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,EAAmD,GAAG,EAAE,MAAM,EAAE,MAAM,YAAY,CAAC;AAE/F,OAAO,
|
|
1
|
+
{"version":3,"file":"validator.d.ts","sourceRoot":"","sources":["../src/validator.ts"],"names":[],"mappings":"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"}
|
package/dest/validator.js
CHANGED
|
@@ -5,7 +5,7 @@ import { RunningPromise } from '@aztec/foundation/running-promise';
|
|
|
5
5
|
import { sleep } from '@aztec/foundation/sleep';
|
|
6
6
|
import { DateProvider } from '@aztec/foundation/timer';
|
|
7
7
|
import { AuthRequest, AuthResponse, BlockProposalValidator, ReqRespSubProtocol } from '@aztec/p2p';
|
|
8
|
-
import { OffenseType, WANT_TO_SLASH_EVENT } from '@aztec/slasher';
|
|
8
|
+
import { OffenseType, WANT_TO_CLEAR_SLASH_EVENT, WANT_TO_SLASH_EVENT } from '@aztec/slasher';
|
|
9
9
|
import { getEpochAtSlot } from '@aztec/stdlib/epoch-helpers';
|
|
10
10
|
import { AttestationTimeoutError } from '@aztec/stdlib/validators';
|
|
11
11
|
import { getTelemetryClient } from '@aztec/telemetry-client';
|
|
@@ -20,6 +20,8 @@ import { ProposalHandler } from './proposal_handler.js';
|
|
|
20
20
|
// We maintain a set of proposers who have proposed invalid blocks.
|
|
21
21
|
// Just cap the set to avoid unbounded growth.
|
|
22
22
|
const MAX_PROPOSERS_OF_INVALID_BLOCKS = 1000;
|
|
23
|
+
const MAX_TRACKED_INVALID_PROPOSAL_SLOTS = 1000;
|
|
24
|
+
const MAX_TRACKED_BAD_ATTESTATIONS = 10_000;
|
|
23
25
|
// What errors from the block proposal handler result in slashing
|
|
24
26
|
const SLASHABLE_BLOCK_PROPOSAL_VALIDATION_RESULT = [
|
|
25
27
|
'state_mismatch',
|
|
@@ -52,9 +54,12 @@ const SLASHABLE_BLOCK_PROPOSAL_VALIDATION_RESULT = [
|
|
|
52
54
|
epochCacheUpdateLoop;
|
|
53
55
|
/** Tracks the last epoch in which each attester successfully submitted at least one attestation. */ lastAttestedEpochByAttester;
|
|
54
56
|
proposersOfInvalidBlocks;
|
|
57
|
+
slotsWithInvalidBlockProposals;
|
|
58
|
+
slotsWithProposalEquivocation;
|
|
59
|
+
badAttestationOffenseKeys;
|
|
55
60
|
/** Tracks the last checkpoint proposal we attested to, to prevent equivocation. */ lastAttestedProposal;
|
|
56
61
|
constructor(keyStore, epochCache, p2pClient, proposalHandler, blockSource, checkpointsBuilder, worldState, l1ToL2MessageSource, config, blobClient, slashingProtectionSigner, dateProvider = new DateProvider(), telemetry = getTelemetryClient(), log = createLogger('validator')){
|
|
57
|
-
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 = new Set();
|
|
62
|
+
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 = new Set(), this.slotsWithInvalidBlockProposals = new Set(), this.slotsWithProposalEquivocation = new Set(), this.badAttestationOffenseKeys = new Set();
|
|
58
63
|
// Create child logger with fisherman prefix if in fisherman mode
|
|
59
64
|
this.log = config.fishermanMode ? log.createChild('[FISHERMAN]') : log;
|
|
60
65
|
this.tracer = telemetry.getTracer('Validator');
|
|
@@ -117,7 +122,7 @@ const SLASHABLE_BLOCK_PROPOSAL_VALIDATION_RESULT = [
|
|
|
117
122
|
maxBlocksPerCheckpoint: config.maxBlocksPerCheckpoint,
|
|
118
123
|
signatureContext: {
|
|
119
124
|
chainId: config.l1ChainId,
|
|
120
|
-
rollupAddress: config.
|
|
125
|
+
rollupAddress: config.rollupAddress
|
|
121
126
|
}
|
|
122
127
|
});
|
|
123
128
|
const proposalHandler = new ProposalHandler(checkpointsBuilder, worldState, blockSource, l1ToL2MessageSource, txProvider, blockProposalValidator, epochCache, config, blobClient, metrics, dateProvider, telemetry, undefined);
|
|
@@ -164,7 +169,7 @@ const SLASHABLE_BLOCK_PROPOSAL_VALIDATION_RESULT = [
|
|
|
164
169
|
getSignatureContext() {
|
|
165
170
|
return {
|
|
166
171
|
chainId: this.config.l1ChainId,
|
|
167
|
-
rollupAddress: this.config.
|
|
172
|
+
rollupAddress: this.config.rollupAddress
|
|
168
173
|
};
|
|
169
174
|
}
|
|
170
175
|
getCoinbaseForAttestor(attestor) {
|
|
@@ -227,6 +232,9 @@ const SLASHABLE_BLOCK_PROPOSAL_VALIDATION_RESULT = [
|
|
|
227
232
|
this.p2pClient.registerDuplicateAttestationCallback((info)=>{
|
|
228
233
|
this.handleDuplicateAttestation(info);
|
|
229
234
|
});
|
|
235
|
+
this.p2pClient.registerCheckpointAttestationCallback((attestation)=>{
|
|
236
|
+
this.handleCheckpointAttestation(attestation);
|
|
237
|
+
});
|
|
230
238
|
const myAddresses = this.getValidatorAddresses();
|
|
231
239
|
this.p2pClient.registerThisValidatorAddresses(myAddresses);
|
|
232
240
|
await this.p2pClient.addReqRespSubProtocol(ReqRespSubProtocol.AUTH, this.handleAuthRequest.bind(this));
|
|
@@ -268,8 +276,8 @@ const SLASHABLE_BLOCK_PROPOSAL_VALIDATION_RESULT = [
|
|
|
268
276
|
});
|
|
269
277
|
// Reexecute txs if we are part of the committee, or if slashing is enabled, or if we are configured to always reexecute.
|
|
270
278
|
// In fisherman mode, we always reexecute to validate proposals.
|
|
271
|
-
const { slashBroadcastedInvalidBlockPenalty, alwaysReexecuteBlockProposals, fishermanMode } = this.config;
|
|
272
|
-
const shouldReexecute = fishermanMode || slashBroadcastedInvalidBlockPenalty > 0n || partOfCommittee || alwaysReexecuteBlockProposals || this.blobClient.canUpload();
|
|
279
|
+
const { slashBroadcastedInvalidBlockPenalty, slashAttestInvalidCheckpointProposalPenalty, alwaysReexecuteBlockProposals, fishermanMode } = this.config;
|
|
280
|
+
const shouldReexecute = fishermanMode || slashBroadcastedInvalidBlockPenalty > 0n || slashAttestInvalidCheckpointProposalPenalty > 0n || partOfCommittee || alwaysReexecuteBlockProposals || this.blobClient.canUpload();
|
|
273
281
|
const validationResult = await this.proposalHandler.handleBlockProposal(proposal, proposalSender, !!shouldReexecute && !escapeHatchOpen);
|
|
274
282
|
if (!validationResult.isValid) {
|
|
275
283
|
const reason = validationResult.reason || 'unknown';
|
|
@@ -288,10 +296,14 @@ const SLASHABLE_BLOCK_PROPOSAL_VALIDATION_RESULT = [
|
|
|
288
296
|
// Node issues so we can't validate
|
|
289
297
|
this.metrics.incFailedAttestationsNodeIssue(1, reason, partOfCommittee);
|
|
290
298
|
}
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
299
|
+
if (!escapeHatchOpen && validationResult.reason && SLASHABLE_BLOCK_PROPOSAL_VALIDATION_RESULT.includes(validationResult.reason)) {
|
|
300
|
+
if (slashBroadcastedInvalidBlockPenalty > 0n) {
|
|
301
|
+
this.log.warn(`Slashing proposer for invalid block proposal`, proposalInfo);
|
|
302
|
+
this.slashInvalidBlock(proposal);
|
|
303
|
+
}
|
|
304
|
+
if (slashAttestInvalidCheckpointProposalPenalty > 0n) {
|
|
305
|
+
this.markInvalidProposalSlot(proposal.slotNumber);
|
|
306
|
+
}
|
|
295
307
|
}
|
|
296
308
|
return false;
|
|
297
309
|
}
|
|
@@ -431,7 +443,9 @@ const SLASHABLE_BLOCK_PROPOSAL_VALIDATION_RESULT = [
|
|
|
431
443
|
* Uploads blobs for a checkpoint to the filestore (fire and forget).
|
|
432
444
|
*/ async uploadBlobsForCheckpoint(proposal, proposalInfo) {
|
|
433
445
|
try {
|
|
434
|
-
const lastBlockHeader = await this.blockSource.
|
|
446
|
+
const lastBlockHeader = (await this.blockSource.getBlockData({
|
|
447
|
+
archive: proposal.archive
|
|
448
|
+
}))?.header;
|
|
435
449
|
if (!lastBlockHeader) {
|
|
436
450
|
this.log.warn(`Failed to get last block header for blob upload`, proposalInfo);
|
|
437
451
|
return;
|
|
@@ -474,11 +488,54 @@ const SLASHABLE_BLOCK_PROPOSAL_VALIDATION_RESULT = [
|
|
|
474
488
|
}
|
|
475
489
|
]);
|
|
476
490
|
}
|
|
491
|
+
markInvalidProposalSlot(slotNumber) {
|
|
492
|
+
const slotKey = this.getSlotKey(slotNumber);
|
|
493
|
+
this.addToBoundedSet(this.slotsWithInvalidBlockProposals, slotKey, MAX_TRACKED_INVALID_PROPOSAL_SLOTS);
|
|
494
|
+
}
|
|
495
|
+
handleCheckpointAttestation(attestation) {
|
|
496
|
+
const slotNumber = attestation.slotNumber;
|
|
497
|
+
const slotKey = this.getSlotKey(slotNumber);
|
|
498
|
+
if (!this.slotsWithInvalidBlockProposals.has(slotKey) || this.slotsWithProposalEquivocation.has(slotKey)) {
|
|
499
|
+
return;
|
|
500
|
+
}
|
|
501
|
+
const attester = attestation.getSender();
|
|
502
|
+
if (!attester) {
|
|
503
|
+
this.log.warn(`Cannot slash checkpoint attestation with invalid signature`, {
|
|
504
|
+
slotNumber,
|
|
505
|
+
archive: attestation.archive.toString()
|
|
506
|
+
});
|
|
507
|
+
return;
|
|
508
|
+
}
|
|
509
|
+
this.slashAttestedToInvalidCheckpointProposal(slotNumber, attester);
|
|
510
|
+
}
|
|
511
|
+
slashAttestedToInvalidCheckpointProposal(slotNumber, attester) {
|
|
512
|
+
if (this.config.slashAttestInvalidCheckpointProposalPenalty <= 0n) {
|
|
513
|
+
return;
|
|
514
|
+
}
|
|
515
|
+
const offenseKey = `${this.getSlotKey(slotNumber)}:${attester.toString()}`;
|
|
516
|
+
if (!this.addToBoundedSet(this.badAttestationOffenseKeys, offenseKey, MAX_TRACKED_BAD_ATTESTATIONS)) {
|
|
517
|
+
return;
|
|
518
|
+
}
|
|
519
|
+
this.log.warn(`Slashing attester for attesting to invalid checkpoint proposal`, {
|
|
520
|
+
attester: attester.toString(),
|
|
521
|
+
slotNumber
|
|
522
|
+
});
|
|
523
|
+
this.emit(WANT_TO_SLASH_EVENT, [
|
|
524
|
+
{
|
|
525
|
+
validator: attester,
|
|
526
|
+
amount: this.config.slashAttestInvalidCheckpointProposalPenalty,
|
|
527
|
+
offenseType: OffenseType.ATTESTED_TO_INVALID_CHECKPOINT_PROPOSAL,
|
|
528
|
+
epochOrSlot: BigInt(slotNumber)
|
|
529
|
+
}
|
|
530
|
+
]);
|
|
531
|
+
}
|
|
477
532
|
/**
|
|
478
533
|
* Handle detection of a duplicate proposal (equivocation).
|
|
479
534
|
* Emits a slash event when a proposer sends multiple proposals for the same position.
|
|
480
535
|
*/ handleDuplicateProposal(info) {
|
|
481
536
|
const { slot, proposer, type } = info;
|
|
537
|
+
const slotKey = this.getSlotKey(slot);
|
|
538
|
+
this.addToBoundedSet(this.slotsWithProposalEquivocation, slotKey, MAX_TRACKED_INVALID_PROPOSAL_SLOTS);
|
|
482
539
|
this.log.warn(`Triggering slash event for duplicate ${type} proposal from ${proposer.toString()} at slot ${slot}`, {
|
|
483
540
|
proposer: proposer.toString(),
|
|
484
541
|
slot,
|
|
@@ -493,6 +550,12 @@ const SLASHABLE_BLOCK_PROPOSAL_VALIDATION_RESULT = [
|
|
|
493
550
|
epochOrSlot: BigInt(slot)
|
|
494
551
|
}
|
|
495
552
|
]);
|
|
553
|
+
this.emit(WANT_TO_CLEAR_SLASH_EVENT, [
|
|
554
|
+
{
|
|
555
|
+
offenseType: OffenseType.ATTESTED_TO_INVALID_CHECKPOINT_PROPOSAL,
|
|
556
|
+
epochOrSlot: BigInt(slot)
|
|
557
|
+
}
|
|
558
|
+
]);
|
|
496
559
|
}
|
|
497
560
|
/**
|
|
498
561
|
* Handle detection of a duplicate attestation (equivocation).
|
|
@@ -512,6 +575,19 @@ const SLASHABLE_BLOCK_PROPOSAL_VALIDATION_RESULT = [
|
|
|
512
575
|
}
|
|
513
576
|
]);
|
|
514
577
|
}
|
|
578
|
+
getSlotKey(slot) {
|
|
579
|
+
return slot.toString();
|
|
580
|
+
}
|
|
581
|
+
addToBoundedSet(set, value, maxSize) {
|
|
582
|
+
if (set.has(value)) {
|
|
583
|
+
return false;
|
|
584
|
+
}
|
|
585
|
+
if (set.size >= maxSize) {
|
|
586
|
+
set.delete(set.values().next().value);
|
|
587
|
+
}
|
|
588
|
+
set.add(value);
|
|
589
|
+
return true;
|
|
590
|
+
}
|
|
515
591
|
async createBlockProposal(blockHeader, checkpointNumber, indexWithinCheckpoint, inHash, archive, txs, proposerAddress, options = {}) {
|
|
516
592
|
// Validate that we're not creating a proposal for an older or equal position
|
|
517
593
|
if (this.lastProposedBlock) {
|
|
@@ -577,27 +653,19 @@ const SLASHABLE_BLOCK_PROPOSAL_VALIDATION_RESULT = [
|
|
|
577
653
|
throw new AttestationTimeoutError(0, required, slot);
|
|
578
654
|
}
|
|
579
655
|
await this.collectOwnAttestations(proposal, checkpointNumber);
|
|
580
|
-
const
|
|
656
|
+
const proposalPayloadHash = proposal.getPayloadHash();
|
|
581
657
|
const myAddresses = this.getValidatorAddresses();
|
|
582
658
|
let attestations = [];
|
|
583
659
|
while(true){
|
|
584
|
-
//
|
|
585
|
-
//
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
this.log.warn(`Received attestation for slot ${slot} with mismatched archive from ${attestation.getSender()?.toString()}`, {
|
|
589
|
-
attestationArchive: attestation.archive.toString(),
|
|
590
|
-
proposalArchive: proposal.archive.toString()
|
|
591
|
-
});
|
|
592
|
-
return false;
|
|
593
|
-
}
|
|
594
|
-
return true;
|
|
595
|
-
});
|
|
660
|
+
// The pool already filters by proposal payload hash; if any attestation slips through with a
|
|
661
|
+
// mismatched payload hash, drop it defensively. Equivocations are emitted as separate slash
|
|
662
|
+
// events from libp2p_service.
|
|
663
|
+
const collectedAttestations = await this.p2pClient.getCheckpointAttestationsForSlot(slot, proposalPayloadHash);
|
|
596
664
|
// Log new attestations we collected
|
|
597
665
|
const oldSenders = attestations.map((attestation)=>attestation.getSender());
|
|
598
666
|
for (const collected of collectedAttestations){
|
|
599
667
|
const collectedSender = collected.getSender();
|
|
600
|
-
// Skip attestations with invalid signatures
|
|
668
|
+
// Skip attestations with invalid signatures. Should not happen as we don't add invalid attestations to our pool.
|
|
601
669
|
if (!collectedSender) {
|
|
602
670
|
this.log.warn(`Skipping attestation with invalid signature for slot ${slot}`);
|
|
603
671
|
continue;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aztec/validator-client",
|
|
3
|
-
"version": "5.0.0-nightly.
|
|
3
|
+
"version": "5.0.0-nightly.20260514",
|
|
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-nightly.
|
|
68
|
-
"@aztec/blob-lib": "5.0.0-nightly.
|
|
69
|
-
"@aztec/constants": "5.0.0-nightly.
|
|
70
|
-
"@aztec/epoch-cache": "5.0.0-nightly.
|
|
71
|
-
"@aztec/ethereum": "5.0.0-nightly.
|
|
72
|
-
"@aztec/foundation": "5.0.0-nightly.
|
|
73
|
-
"@aztec/node-keystore": "5.0.0-nightly.
|
|
74
|
-
"@aztec/noir-protocol-circuits-types": "5.0.0-nightly.
|
|
75
|
-
"@aztec/p2p": "5.0.0-nightly.
|
|
76
|
-
"@aztec/protocol-contracts": "5.0.0-nightly.
|
|
77
|
-
"@aztec/prover-client": "5.0.0-nightly.
|
|
78
|
-
"@aztec/simulator": "5.0.0-nightly.
|
|
79
|
-
"@aztec/slasher": "5.0.0-nightly.
|
|
80
|
-
"@aztec/stdlib": "5.0.0-nightly.
|
|
81
|
-
"@aztec/telemetry-client": "5.0.0-nightly.
|
|
82
|
-
"@aztec/validator-ha-signer": "5.0.0-nightly.
|
|
67
|
+
"@aztec/blob-client": "5.0.0-nightly.20260514",
|
|
68
|
+
"@aztec/blob-lib": "5.0.0-nightly.20260514",
|
|
69
|
+
"@aztec/constants": "5.0.0-nightly.20260514",
|
|
70
|
+
"@aztec/epoch-cache": "5.0.0-nightly.20260514",
|
|
71
|
+
"@aztec/ethereum": "5.0.0-nightly.20260514",
|
|
72
|
+
"@aztec/foundation": "5.0.0-nightly.20260514",
|
|
73
|
+
"@aztec/node-keystore": "5.0.0-nightly.20260514",
|
|
74
|
+
"@aztec/noir-protocol-circuits-types": "5.0.0-nightly.20260514",
|
|
75
|
+
"@aztec/p2p": "5.0.0-nightly.20260514",
|
|
76
|
+
"@aztec/protocol-contracts": "5.0.0-nightly.20260514",
|
|
77
|
+
"@aztec/prover-client": "5.0.0-nightly.20260514",
|
|
78
|
+
"@aztec/simulator": "5.0.0-nightly.20260514",
|
|
79
|
+
"@aztec/slasher": "5.0.0-nightly.20260514",
|
|
80
|
+
"@aztec/stdlib": "5.0.0-nightly.20260514",
|
|
81
|
+
"@aztec/telemetry-client": "5.0.0-nightly.20260514",
|
|
82
|
+
"@aztec/validator-ha-signer": "5.0.0-nightly.20260514",
|
|
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-nightly.
|
|
90
|
-
"@aztec/world-state": "5.0.0-nightly.
|
|
89
|
+
"@aztec/archiver": "5.0.0-nightly.20260514",
|
|
90
|
+
"@aztec/world-state": "5.0.0-nightly.20260514",
|
|
91
91
|
"@electric-sql/pglite": "^0.3.14",
|
|
92
92
|
"@jest/globals": "^30.0.0",
|
|
93
93
|
"@types/jest": "^30.0.0",
|
package/src/factory.ts
CHANGED
|
@@ -35,7 +35,7 @@ export function createProposalHandler(
|
|
|
35
35
|
maxBlocksPerCheckpoint: config.maxBlocksPerCheckpoint,
|
|
36
36
|
signatureContext: {
|
|
37
37
|
chainId: config.l1ChainId,
|
|
38
|
-
rollupAddress: config.
|
|
38
|
+
rollupAddress: config.rollupAddress,
|
|
39
39
|
},
|
|
40
40
|
});
|
|
41
41
|
return new ProposalHandler(
|
package/src/proposal_handler.ts
CHANGED
|
@@ -4,7 +4,12 @@ import { type Blob, encodeCheckpointBlobDataFromBlocks, getBlobsPerL1Block } fro
|
|
|
4
4
|
import { INITIAL_L2_BLOCK_NUM } from '@aztec/constants';
|
|
5
5
|
import type { EpochCache } from '@aztec/epoch-cache';
|
|
6
6
|
import { validateFeeAssetPriceModifier } from '@aztec/ethereum/contracts';
|
|
7
|
-
import {
|
|
7
|
+
import {
|
|
8
|
+
BlockNumber,
|
|
9
|
+
CheckpointNumber,
|
|
10
|
+
type CheckpointProposalHash,
|
|
11
|
+
SlotNumber,
|
|
12
|
+
} from '@aztec/foundation/branded-types';
|
|
8
13
|
import { pick } from '@aztec/foundation/collection';
|
|
9
14
|
import { Fr } from '@aztec/foundation/curves/bn254';
|
|
10
15
|
import { TimeoutError } from '@aztec/foundation/error';
|
|
@@ -15,7 +20,7 @@ import { DateProvider, Timer } from '@aztec/foundation/timer';
|
|
|
15
20
|
import type { P2P, PeerId } from '@aztec/p2p';
|
|
16
21
|
import { BlockProposalValidator } from '@aztec/p2p/msg_validators';
|
|
17
22
|
import type { BlockData, L2Block, L2BlockSink, L2BlockSource } from '@aztec/stdlib/block';
|
|
18
|
-
import { validateCheckpoint } from '@aztec/stdlib/checkpoint';
|
|
23
|
+
import { getPreviousCheckpointOutHashes, validateCheckpoint } from '@aztec/stdlib/checkpoint';
|
|
19
24
|
import { getEpochAtSlot, getTimestampForSlot } from '@aztec/stdlib/epoch-helpers';
|
|
20
25
|
import { Gas } from '@aztec/stdlib/gas';
|
|
21
26
|
import type { ITxProvider, ValidatorClientFullConfig, WorldStateSynchronizer } from '@aztec/stdlib/interfaces/server';
|
|
@@ -88,10 +93,11 @@ type CheckpointComputationResult =
|
|
|
88
93
|
export class ProposalHandler {
|
|
89
94
|
public readonly tracer: Tracer;
|
|
90
95
|
|
|
91
|
-
/** Cached last checkpoint validation result to avoid double-validation on validator nodes.
|
|
96
|
+
/** Cached last checkpoint validation result to avoid double-validation on validator nodes.
|
|
97
|
+
* Keyed by signed-payload hash so two proposals at the same (slot, archive) but with a
|
|
98
|
+
* different `feeAssetPriceModifier` (or any other signed field) are validated independently. */
|
|
92
99
|
private lastCheckpointValidationResult?: {
|
|
93
|
-
|
|
94
|
-
slotNumber: SlotNumber;
|
|
100
|
+
payloadHash: CheckpointProposalHash;
|
|
95
101
|
result: CheckpointProposalValidationResult;
|
|
96
102
|
};
|
|
97
103
|
|
|
@@ -292,7 +298,7 @@ export class ProposalHandler {
|
|
|
292
298
|
proposalInfo.blockNumber = blockNumber;
|
|
293
299
|
|
|
294
300
|
// Check that this block number does not exist already
|
|
295
|
-
const existingBlock = await this.blockSource.
|
|
301
|
+
const existingBlock = await this.blockSource.getBlockData({ number: blockNumber });
|
|
296
302
|
if (existingBlock) {
|
|
297
303
|
this.log.warn(`Block number ${blockNumber} already exists, skipping processing`, proposalInfo);
|
|
298
304
|
return { isValid: false, blockNumber, reason: 'block_number_already_exists' };
|
|
@@ -341,11 +347,18 @@ export class ProposalHandler {
|
|
|
341
347
|
return { isValid: false, blockNumber, reason: 'txs_not_available' };
|
|
342
348
|
}
|
|
343
349
|
|
|
344
|
-
// Collect the out hashes of all the checkpoints before this one in the same epoch
|
|
350
|
+
// Collect the out hashes of all the checkpoints before this one in the same epoch.
|
|
351
|
+
// Mirror the proposer-side fallback: under pipelining the immediately-preceding cp may not
|
|
352
|
+
// yet be on L1, in which case the helper grafts the locally-known proposed cp's outHash.
|
|
345
353
|
const epoch = getEpochAtSlot(slotNumber, this.epochCache.getL1Constants());
|
|
346
|
-
const previousCheckpointOutHashes =
|
|
347
|
-
|
|
348
|
-
|
|
354
|
+
const previousCheckpointOutHashes = await getPreviousCheckpointOutHashes({
|
|
355
|
+
blockSource: this.blockSource,
|
|
356
|
+
epoch,
|
|
357
|
+
checkpointNumber,
|
|
358
|
+
l1Constants: this.epochCache.getL1Constants(),
|
|
359
|
+
pipeliningEnabled: this.epochCache.isProposerPipeliningEnabled(),
|
|
360
|
+
log: this.log,
|
|
361
|
+
});
|
|
349
362
|
|
|
350
363
|
// Try re-executing the transactions in the proposal if needed
|
|
351
364
|
let reexecutionResult;
|
|
@@ -393,11 +406,12 @@ export class ProposalHandler {
|
|
|
393
406
|
|
|
394
407
|
try {
|
|
395
408
|
return (
|
|
396
|
-
(await this.blockSource.
|
|
409
|
+
(await this.blockSource.getBlockData({ archive: parentArchive })) ??
|
|
397
410
|
(timeoutDurationMs <= 0
|
|
398
411
|
? undefined
|
|
399
412
|
: await retryUntil(
|
|
400
|
-
() =>
|
|
413
|
+
() =>
|
|
414
|
+
this.blockSource.syncImmediate().then(() => this.blockSource.getBlockData({ archive: parentArchive })),
|
|
401
415
|
'force archiver sync',
|
|
402
416
|
timeoutDurationMs / 1000,
|
|
403
417
|
0.5,
|
|
@@ -734,13 +748,10 @@ export class ProposalHandler {
|
|
|
734
748
|
proposalInfo: LogData,
|
|
735
749
|
): Promise<CheckpointProposalValidationResult> {
|
|
736
750
|
const slot = proposal.slotNumber;
|
|
751
|
+
const payloadHash = proposal.getPayloadHash();
|
|
737
752
|
|
|
738
|
-
// Check cache: same
|
|
739
|
-
if (
|
|
740
|
-
this.lastCheckpointValidationResult &&
|
|
741
|
-
this.lastCheckpointValidationResult.archive.equals(proposal.archive) &&
|
|
742
|
-
this.lastCheckpointValidationResult.slotNumber === slot
|
|
743
|
-
) {
|
|
753
|
+
// Check cache: same signed-payload hash means we already validated this exact proposal.
|
|
754
|
+
if (this.lastCheckpointValidationResult && this.lastCheckpointValidationResult.payloadHash === payloadHash) {
|
|
744
755
|
this.log.debug(`Returning cached validation result for checkpoint proposal at slot ${slot}`, proposalInfo);
|
|
745
756
|
return this.lastCheckpointValidationResult.result;
|
|
746
757
|
}
|
|
@@ -749,7 +760,7 @@ export class ProposalHandler {
|
|
|
749
760
|
if (!proposer) {
|
|
750
761
|
this.log.warn(`Received checkpoint proposal with invalid signature for slot ${proposal.slotNumber}`);
|
|
751
762
|
const result: CheckpointProposalValidationResult = { isValid: false, reason: 'invalid_signature' };
|
|
752
|
-
this.lastCheckpointValidationResult = {
|
|
763
|
+
this.lastCheckpointValidationResult = { payloadHash, result };
|
|
753
764
|
return result;
|
|
754
765
|
}
|
|
755
766
|
|
|
@@ -758,12 +769,12 @@ export class ProposalHandler {
|
|
|
758
769
|
`Received checkpoint proposal with invalid feeAssetPriceModifier ${proposal.feeAssetPriceModifier} for slot ${proposal.slotNumber}`,
|
|
759
770
|
);
|
|
760
771
|
const result: CheckpointProposalValidationResult = { isValid: false, reason: 'invalid_fee_asset_price_modifier' };
|
|
761
|
-
this.lastCheckpointValidationResult = {
|
|
772
|
+
this.lastCheckpointValidationResult = { payloadHash, result };
|
|
762
773
|
return result;
|
|
763
774
|
}
|
|
764
775
|
|
|
765
776
|
const result = await this.validateCheckpointProposal(proposal, proposalInfo);
|
|
766
|
-
this.lastCheckpointValidationResult = {
|
|
777
|
+
this.lastCheckpointValidationResult = { payloadHash, result };
|
|
767
778
|
|
|
768
779
|
// Upload blobs to filestore if validation passed (fire and forget)
|
|
769
780
|
if (result.isValid) {
|
|
@@ -794,7 +805,7 @@ export class ProposalHandler {
|
|
|
794
805
|
lastBlockHeader = await retryUntil(
|
|
795
806
|
async () => {
|
|
796
807
|
await this.blockSource.syncImmediate();
|
|
797
|
-
return this.blockSource.
|
|
808
|
+
return (await this.blockSource.getBlockData({ archive: proposal.archive }))?.header;
|
|
798
809
|
},
|
|
799
810
|
`waiting for block with archive ${proposal.archive.toString()} for slot ${slot}`,
|
|
800
811
|
timeoutSeconds,
|
|
@@ -850,11 +861,17 @@ export class ProposalHandler {
|
|
|
850
861
|
// Get L1-to-L2 messages for this checkpoint
|
|
851
862
|
const l1ToL2Messages = await this.l1ToL2MessageSource.getL1ToL2Messages(checkpointNumber);
|
|
852
863
|
|
|
853
|
-
// Collect the out hashes of all the checkpoints before this one in the same epoch
|
|
864
|
+
// Collect the out hashes of all the checkpoints before this one in the same epoch.
|
|
865
|
+
// See note on the analogous block-proposal site: the helper handles pipelining lag.
|
|
854
866
|
const epoch = getEpochAtSlot(slot, this.epochCache.getL1Constants());
|
|
855
|
-
const previousCheckpointOutHashes =
|
|
856
|
-
|
|
857
|
-
|
|
867
|
+
const previousCheckpointOutHashes = await getPreviousCheckpointOutHashes({
|
|
868
|
+
blockSource: this.blockSource,
|
|
869
|
+
epoch,
|
|
870
|
+
checkpointNumber,
|
|
871
|
+
l1Constants: this.epochCache.getL1Constants(),
|
|
872
|
+
pipeliningEnabled: this.epochCache.isProposerPipeliningEnabled(),
|
|
873
|
+
log: this.log,
|
|
874
|
+
});
|
|
858
875
|
|
|
859
876
|
// Fork world state at the block before the first block
|
|
860
877
|
const parentBlockNumber = BlockNumber(firstBlock.number - 1);
|
|
@@ -953,7 +970,7 @@ export class ProposalHandler {
|
|
|
953
970
|
/** Uploads blobs for a checkpoint to the filestore. */
|
|
954
971
|
protected async uploadBlobsForCheckpoint(proposal: CheckpointProposalCore, proposalInfo: LogData): Promise<void> {
|
|
955
972
|
try {
|
|
956
|
-
const lastBlockHeader = await this.blockSource.
|
|
973
|
+
const lastBlockHeader = (await this.blockSource.getBlockData({ archive: proposal.archive }))?.header;
|
|
957
974
|
if (!lastBlockHeader) {
|
|
958
975
|
this.log.warn(`Failed to get last block header for blob upload`, proposalInfo);
|
|
959
976
|
return;
|
|
@@ -987,7 +1004,7 @@ export class ProposalHandler {
|
|
|
987
1004
|
if (!this.archiver) {
|
|
988
1005
|
return false;
|
|
989
1006
|
}
|
|
990
|
-
const blockData = await this.blockSource.
|
|
1007
|
+
const blockData = await this.blockSource.getBlockData({ archive: proposal.archive });
|
|
991
1008
|
if (!blockData) {
|
|
992
1009
|
this.log.debug(`Block data not found for checkpoint proposal archive, cannot set proposed checkpoint`, {
|
|
993
1010
|
archive: proposal.archive.toString(),
|
|
@@ -1015,7 +1032,7 @@ export class ProposalHandler {
|
|
|
1015
1032
|
if (!this.archiver) {
|
|
1016
1033
|
return false;
|
|
1017
1034
|
}
|
|
1018
|
-
let blockData = await this.blockSource.
|
|
1035
|
+
let blockData = await this.blockSource.getBlockData({ archive: proposal.archive });
|
|
1019
1036
|
|
|
1020
1037
|
if (!blockData) {
|
|
1021
1038
|
// The checkpoint proposal often arrives before the last block finishes re-execution.
|
|
@@ -1025,7 +1042,7 @@ export class ProposalHandler {
|
|
|
1025
1042
|
const timeoutSeconds = Math.max(1, Number(timeOfNextSlot) - Math.floor(this.dateProvider.now() / 1000));
|
|
1026
1043
|
|
|
1027
1044
|
blockData = await retryUntil(
|
|
1028
|
-
() => this.blockSource.
|
|
1045
|
+
() => this.blockSource.getBlockData({ archive: proposal.archive }),
|
|
1029
1046
|
'block data for own checkpoint proposal',
|
|
1030
1047
|
timeoutSeconds,
|
|
1031
1048
|
0.25,
|
package/src/validator.ts
CHANGED
|
@@ -12,7 +12,13 @@ import { DateProvider } from '@aztec/foundation/timer';
|
|
|
12
12
|
import type { KeystoreManager } from '@aztec/node-keystore';
|
|
13
13
|
import type { DuplicateAttestationInfo, DuplicateProposalInfo, P2P, PeerId } from '@aztec/p2p';
|
|
14
14
|
import { AuthRequest, AuthResponse, BlockProposalValidator, ReqRespSubProtocol } from '@aztec/p2p';
|
|
15
|
-
import {
|
|
15
|
+
import {
|
|
16
|
+
OffenseType,
|
|
17
|
+
WANT_TO_CLEAR_SLASH_EVENT,
|
|
18
|
+
WANT_TO_SLASH_EVENT,
|
|
19
|
+
type Watcher,
|
|
20
|
+
type WatcherEmitter,
|
|
21
|
+
} from '@aztec/slasher';
|
|
16
22
|
import type { AztecAddress } from '@aztec/stdlib/aztec-address';
|
|
17
23
|
import type { CommitteeAttestationsAndSigners, L2BlockSink, L2BlockSource } from '@aztec/stdlib/block';
|
|
18
24
|
import { getEpochAtSlot } from '@aztec/stdlib/epoch-helpers';
|
|
@@ -58,6 +64,8 @@ import { type BlockProposalValidationFailureReason, ProposalHandler } from './pr
|
|
|
58
64
|
// We maintain a set of proposers who have proposed invalid blocks.
|
|
59
65
|
// Just cap the set to avoid unbounded growth.
|
|
60
66
|
const MAX_PROPOSERS_OF_INVALID_BLOCKS = 1000;
|
|
67
|
+
const MAX_TRACKED_INVALID_PROPOSAL_SLOTS = 1000;
|
|
68
|
+
const MAX_TRACKED_BAD_ATTESTATIONS = 10_000;
|
|
61
69
|
|
|
62
70
|
// What errors from the block proposal handler result in slashing
|
|
63
71
|
const SLASHABLE_BLOCK_PROPOSAL_VALIDATION_RESULT: BlockProposalValidationFailureReason[] = [
|
|
@@ -88,6 +96,9 @@ export class ValidatorClient extends (EventEmitter as new () => WatcherEmitter)
|
|
|
88
96
|
private lastAttestedEpochByAttester: Map<string, EpochNumber> = new Map();
|
|
89
97
|
|
|
90
98
|
private proposersOfInvalidBlocks: Set<string> = new Set();
|
|
99
|
+
private slotsWithInvalidBlockProposals: Set<string> = new Set();
|
|
100
|
+
private slotsWithProposalEquivocation: Set<string> = new Set();
|
|
101
|
+
private badAttestationOffenseKeys: Set<string> = new Set();
|
|
91
102
|
|
|
92
103
|
/** Tracks the last checkpoint proposal we attested to, to prevent equivocation. */
|
|
93
104
|
private lastAttestedProposal?: CheckpointProposalCore;
|
|
@@ -204,7 +215,7 @@ export class ValidatorClient extends (EventEmitter as new () => WatcherEmitter)
|
|
|
204
215
|
maxBlocksPerCheckpoint: config.maxBlocksPerCheckpoint,
|
|
205
216
|
signatureContext: {
|
|
206
217
|
chainId: config.l1ChainId,
|
|
207
|
-
rollupAddress: config.
|
|
218
|
+
rollupAddress: config.rollupAddress,
|
|
208
219
|
},
|
|
209
220
|
});
|
|
210
221
|
const proposalHandler = new ProposalHandler(
|
|
@@ -288,7 +299,7 @@ export class ValidatorClient extends (EventEmitter as new () => WatcherEmitter)
|
|
|
288
299
|
private getSignatureContext(): CoordinationSignatureContext {
|
|
289
300
|
return {
|
|
290
301
|
chainId: this.config.l1ChainId,
|
|
291
|
-
rollupAddress: this.config.
|
|
302
|
+
rollupAddress: this.config.rollupAddress,
|
|
292
303
|
};
|
|
293
304
|
}
|
|
294
305
|
|
|
@@ -374,6 +385,10 @@ export class ValidatorClient extends (EventEmitter as new () => WatcherEmitter)
|
|
|
374
385
|
this.handleDuplicateAttestation(info);
|
|
375
386
|
});
|
|
376
387
|
|
|
388
|
+
this.p2pClient.registerCheckpointAttestationCallback((attestation: CheckpointAttestation) => {
|
|
389
|
+
this.handleCheckpointAttestation(attestation);
|
|
390
|
+
});
|
|
391
|
+
|
|
377
392
|
const myAddresses = this.getValidatorAddresses();
|
|
378
393
|
this.p2pClient.registerThisValidatorAddresses(myAddresses);
|
|
379
394
|
|
|
@@ -422,10 +437,16 @@ export class ValidatorClient extends (EventEmitter as new () => WatcherEmitter)
|
|
|
422
437
|
|
|
423
438
|
// Reexecute txs if we are part of the committee, or if slashing is enabled, or if we are configured to always reexecute.
|
|
424
439
|
// In fisherman mode, we always reexecute to validate proposals.
|
|
425
|
-
const {
|
|
440
|
+
const {
|
|
441
|
+
slashBroadcastedInvalidBlockPenalty,
|
|
442
|
+
slashAttestInvalidCheckpointProposalPenalty,
|
|
443
|
+
alwaysReexecuteBlockProposals,
|
|
444
|
+
fishermanMode,
|
|
445
|
+
} = this.config;
|
|
426
446
|
const shouldReexecute =
|
|
427
447
|
fishermanMode ||
|
|
428
448
|
slashBroadcastedInvalidBlockPenalty > 0n ||
|
|
449
|
+
slashAttestInvalidCheckpointProposalPenalty > 0n ||
|
|
429
450
|
partOfCommittee ||
|
|
430
451
|
alwaysReexecuteBlockProposals ||
|
|
431
452
|
this.blobClient.canUpload();
|
|
@@ -457,15 +478,18 @@ export class ValidatorClient extends (EventEmitter as new () => WatcherEmitter)
|
|
|
457
478
|
this.metrics.incFailedAttestationsNodeIssue(1, reason, partOfCommittee);
|
|
458
479
|
}
|
|
459
480
|
|
|
460
|
-
// Slash invalid block proposals (can happen even when not in committee)
|
|
461
481
|
if (
|
|
462
482
|
!escapeHatchOpen &&
|
|
463
483
|
validationResult.reason &&
|
|
464
|
-
SLASHABLE_BLOCK_PROPOSAL_VALIDATION_RESULT.includes(validationResult.reason)
|
|
465
|
-
slashBroadcastedInvalidBlockPenalty > 0n
|
|
484
|
+
SLASHABLE_BLOCK_PROPOSAL_VALIDATION_RESULT.includes(validationResult.reason)
|
|
466
485
|
) {
|
|
467
|
-
|
|
468
|
-
|
|
486
|
+
if (slashBroadcastedInvalidBlockPenalty > 0n) {
|
|
487
|
+
this.log.warn(`Slashing proposer for invalid block proposal`, proposalInfo);
|
|
488
|
+
this.slashInvalidBlock(proposal);
|
|
489
|
+
}
|
|
490
|
+
if (slashAttestInvalidCheckpointProposalPenalty > 0n) {
|
|
491
|
+
this.markInvalidProposalSlot(proposal.slotNumber);
|
|
492
|
+
}
|
|
469
493
|
}
|
|
470
494
|
return false;
|
|
471
495
|
}
|
|
@@ -645,7 +669,7 @@ export class ValidatorClient extends (EventEmitter as new () => WatcherEmitter)
|
|
|
645
669
|
*/
|
|
646
670
|
protected async uploadBlobsForCheckpoint(proposal: CheckpointProposalCore, proposalInfo: LogData): Promise<void> {
|
|
647
671
|
try {
|
|
648
|
-
const lastBlockHeader = await this.blockSource.
|
|
672
|
+
const lastBlockHeader = (await this.blockSource.getBlockData({ archive: proposal.archive }))?.header;
|
|
649
673
|
if (!lastBlockHeader) {
|
|
650
674
|
this.log.warn(`Failed to get last block header for blob upload`, proposalInfo);
|
|
651
675
|
return;
|
|
@@ -696,12 +720,63 @@ export class ValidatorClient extends (EventEmitter as new () => WatcherEmitter)
|
|
|
696
720
|
]);
|
|
697
721
|
}
|
|
698
722
|
|
|
723
|
+
private markInvalidProposalSlot(slotNumber: SlotNumber): void {
|
|
724
|
+
const slotKey = this.getSlotKey(slotNumber);
|
|
725
|
+
this.addToBoundedSet(this.slotsWithInvalidBlockProposals, slotKey, MAX_TRACKED_INVALID_PROPOSAL_SLOTS);
|
|
726
|
+
}
|
|
727
|
+
|
|
728
|
+
private handleCheckpointAttestation(attestation: CheckpointAttestation): void {
|
|
729
|
+
const slotNumber = attestation.slotNumber;
|
|
730
|
+
const slotKey = this.getSlotKey(slotNumber);
|
|
731
|
+
if (!this.slotsWithInvalidBlockProposals.has(slotKey) || this.slotsWithProposalEquivocation.has(slotKey)) {
|
|
732
|
+
return;
|
|
733
|
+
}
|
|
734
|
+
|
|
735
|
+
const attester = attestation.getSender();
|
|
736
|
+
if (!attester) {
|
|
737
|
+
this.log.warn(`Cannot slash checkpoint attestation with invalid signature`, {
|
|
738
|
+
slotNumber,
|
|
739
|
+
archive: attestation.archive.toString(),
|
|
740
|
+
});
|
|
741
|
+
return;
|
|
742
|
+
}
|
|
743
|
+
|
|
744
|
+
this.slashAttestedToInvalidCheckpointProposal(slotNumber, attester);
|
|
745
|
+
}
|
|
746
|
+
|
|
747
|
+
private slashAttestedToInvalidCheckpointProposal(slotNumber: SlotNumber, attester: EthAddress): void {
|
|
748
|
+
if (this.config.slashAttestInvalidCheckpointProposalPenalty <= 0n) {
|
|
749
|
+
return;
|
|
750
|
+
}
|
|
751
|
+
|
|
752
|
+
const offenseKey = `${this.getSlotKey(slotNumber)}:${attester.toString()}`;
|
|
753
|
+
if (!this.addToBoundedSet(this.badAttestationOffenseKeys, offenseKey, MAX_TRACKED_BAD_ATTESTATIONS)) {
|
|
754
|
+
return;
|
|
755
|
+
}
|
|
756
|
+
|
|
757
|
+
this.log.warn(`Slashing attester for attesting to invalid checkpoint proposal`, {
|
|
758
|
+
attester: attester.toString(),
|
|
759
|
+
slotNumber,
|
|
760
|
+
});
|
|
761
|
+
|
|
762
|
+
this.emit(WANT_TO_SLASH_EVENT, [
|
|
763
|
+
{
|
|
764
|
+
validator: attester,
|
|
765
|
+
amount: this.config.slashAttestInvalidCheckpointProposalPenalty,
|
|
766
|
+
offenseType: OffenseType.ATTESTED_TO_INVALID_CHECKPOINT_PROPOSAL,
|
|
767
|
+
epochOrSlot: BigInt(slotNumber),
|
|
768
|
+
},
|
|
769
|
+
]);
|
|
770
|
+
}
|
|
771
|
+
|
|
699
772
|
/**
|
|
700
773
|
* Handle detection of a duplicate proposal (equivocation).
|
|
701
774
|
* Emits a slash event when a proposer sends multiple proposals for the same position.
|
|
702
775
|
*/
|
|
703
776
|
private handleDuplicateProposal(info: DuplicateProposalInfo): void {
|
|
704
777
|
const { slot, proposer, type } = info;
|
|
778
|
+
const slotKey = this.getSlotKey(slot);
|
|
779
|
+
this.addToBoundedSet(this.slotsWithProposalEquivocation, slotKey, MAX_TRACKED_INVALID_PROPOSAL_SLOTS);
|
|
705
780
|
|
|
706
781
|
this.log.warn(`Triggering slash event for duplicate ${type} proposal from ${proposer.toString()} at slot ${slot}`, {
|
|
707
782
|
proposer: proposer.toString(),
|
|
@@ -718,6 +793,13 @@ export class ValidatorClient extends (EventEmitter as new () => WatcherEmitter)
|
|
|
718
793
|
epochOrSlot: BigInt(slot),
|
|
719
794
|
},
|
|
720
795
|
]);
|
|
796
|
+
|
|
797
|
+
this.emit(WANT_TO_CLEAR_SLASH_EVENT, [
|
|
798
|
+
{
|
|
799
|
+
offenseType: OffenseType.ATTESTED_TO_INVALID_CHECKPOINT_PROPOSAL,
|
|
800
|
+
epochOrSlot: BigInt(slot),
|
|
801
|
+
},
|
|
802
|
+
]);
|
|
721
803
|
}
|
|
722
804
|
|
|
723
805
|
/**
|
|
@@ -742,6 +824,21 @@ export class ValidatorClient extends (EventEmitter as new () => WatcherEmitter)
|
|
|
742
824
|
]);
|
|
743
825
|
}
|
|
744
826
|
|
|
827
|
+
private getSlotKey(slot: SlotNumber): string {
|
|
828
|
+
return slot.toString();
|
|
829
|
+
}
|
|
830
|
+
|
|
831
|
+
private addToBoundedSet(set: Set<string>, value: string, maxSize: number): boolean {
|
|
832
|
+
if (set.has(value)) {
|
|
833
|
+
return false;
|
|
834
|
+
}
|
|
835
|
+
if (set.size >= maxSize) {
|
|
836
|
+
set.delete(set.values().next().value!);
|
|
837
|
+
}
|
|
838
|
+
set.add(value);
|
|
839
|
+
return true;
|
|
840
|
+
}
|
|
841
|
+
|
|
745
842
|
async createBlockProposal(
|
|
746
843
|
blockHeader: BlockHeader,
|
|
747
844
|
checkpointNumber: CheckpointNumber,
|
|
@@ -881,33 +978,21 @@ export class ValidatorClient extends (EventEmitter as new () => WatcherEmitter)
|
|
|
881
978
|
|
|
882
979
|
await this.collectOwnAttestations(proposal, checkpointNumber);
|
|
883
980
|
|
|
884
|
-
const
|
|
981
|
+
const proposalPayloadHash = proposal.getPayloadHash();
|
|
885
982
|
const myAddresses = this.getValidatorAddresses();
|
|
886
983
|
|
|
887
984
|
let attestations: CheckpointAttestation[] = [];
|
|
888
985
|
while (true) {
|
|
889
|
-
//
|
|
890
|
-
//
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
if (!attestation.archive.equals(proposal.archive)) {
|
|
894
|
-
this.log.warn(
|
|
895
|
-
`Received attestation for slot ${slot} with mismatched archive from ${attestation
|
|
896
|
-
.getSender()
|
|
897
|
-
?.toString()}`,
|
|
898
|
-
{ attestationArchive: attestation.archive.toString(), proposalArchive: proposal.archive.toString() },
|
|
899
|
-
);
|
|
900
|
-
return false;
|
|
901
|
-
}
|
|
902
|
-
return true;
|
|
903
|
-
},
|
|
904
|
-
);
|
|
986
|
+
// The pool already filters by proposal payload hash; if any attestation slips through with a
|
|
987
|
+
// mismatched payload hash, drop it defensively. Equivocations are emitted as separate slash
|
|
988
|
+
// events from libp2p_service.
|
|
989
|
+
const collectedAttestations = await this.p2pClient.getCheckpointAttestationsForSlot(slot, proposalPayloadHash);
|
|
905
990
|
|
|
906
991
|
// Log new attestations we collected
|
|
907
992
|
const oldSenders = attestations.map(attestation => attestation.getSender());
|
|
908
993
|
for (const collected of collectedAttestations) {
|
|
909
994
|
const collectedSender = collected.getSender();
|
|
910
|
-
// Skip attestations with invalid signatures
|
|
995
|
+
// Skip attestations with invalid signatures. Should not happen as we don't add invalid attestations to our pool.
|
|
911
996
|
if (!collectedSender) {
|
|
912
997
|
this.log.warn(`Skipping attestation with invalid signature for slot ${slot}`);
|
|
913
998
|
continue;
|