@aztec/slasher 0.0.1-commit.6c91f13 → 0.0.1-commit.96bb3f7
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/watchers/attestations_block_watcher.d.ts +5 -5
- package/dest/watchers/attestations_block_watcher.d.ts.map +1 -1
- package/dest/watchers/attestations_block_watcher.js +39 -34
- package/dest/watchers/epoch_prune_watcher.d.ts +4 -4
- package/dest/watchers/epoch_prune_watcher.d.ts.map +1 -1
- package/dest/watchers/epoch_prune_watcher.js +1 -1
- package/package.json +9 -9
- package/src/watchers/attestations_block_watcher.ts +51 -43
- package/src/watchers/epoch_prune_watcher.ts +5 -5
|
@@ -15,19 +15,19 @@ export declare class AttestationsBlockWatcher extends AttestationsBlockWatcher_b
|
|
|
15
15
|
private l2BlockSource;
|
|
16
16
|
private epochCache;
|
|
17
17
|
private log;
|
|
18
|
-
private
|
|
18
|
+
private maxInvalidCheckpoints;
|
|
19
19
|
private invalidArchiveRoots;
|
|
20
20
|
private config;
|
|
21
|
-
private
|
|
21
|
+
private boundHandleInvalidCheckpoint;
|
|
22
22
|
constructor(l2BlockSource: L2BlockSourceEventEmitter, epochCache: EpochCache, config: AttestationsBlockWatcherConfig);
|
|
23
23
|
updateConfig(newConfig: Partial<AttestationsBlockWatcherConfig>): void;
|
|
24
24
|
start(): Promise<void>;
|
|
25
25
|
stop(): Promise<void>;
|
|
26
|
-
private
|
|
26
|
+
private handleInvalidCheckpoint;
|
|
27
27
|
private slashAttestorsOnAncestorInvalid;
|
|
28
28
|
private slashProposer;
|
|
29
29
|
private getOffenseFromInvalidationReason;
|
|
30
|
-
private
|
|
30
|
+
private addInvalidCheckpoint;
|
|
31
31
|
}
|
|
32
32
|
export {};
|
|
33
|
-
//# sourceMappingURL=data:application/json;base64,
|
|
33
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiYXR0ZXN0YXRpb25zX2Jsb2NrX3dhdGNoZXIuZC50cyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uL3NyYy93YXRjaGVycy9hdHRlc3RhdGlvbnNfYmxvY2tfd2F0Y2hlci50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxPQUFPLEVBQUUsVUFBVSxFQUFFLE1BQU0sb0JBQW9CLENBQUM7QUFJaEQsT0FBTyxFQUVMLEtBQUsseUJBQXlCLEVBRy9CLE1BQU0scUJBQXFCLENBQUM7QUFNN0IsT0FBTyxLQUFLLEVBQUUsYUFBYSxFQUFFLE1BQU0sY0FBYyxDQUFDO0FBQ2xELE9BQU8sRUFBNkMsS0FBSyxPQUFPLEVBQUUsS0FBSyxjQUFjLEVBQUUsTUFBTSxlQUFlLENBQUM7QUFFN0csUUFBQSxNQUFNLGtDQUFrQyw4RkFHOUIsQ0FBQztBQUVYLEtBQUssOEJBQThCLEdBQUcsSUFBSSxDQUFDLGFBQWEsRUFBRSxDQUFDLE9BQU8sa0NBQWtDLENBQUMsQ0FBQyxNQUFNLENBQUMsQ0FBQyxDQUFDOztBQUUvRzs7Ozs7R0FLRztBQUNILHFCQUFhLHdCQUF5QixTQUFRLDZCQUEyQyxZQUFXLE9BQU87SUF1QnZHLE9BQU8sQ0FBQyxhQUFhO0lBQ3JCLE9BQU8sQ0FBQyxVQUFVO0lBdkJwQixPQUFPLENBQUMsR0FBRyxDQUFzRDtJQUdqRSxPQUFPLENBQUMscUJBQXFCLENBQU87SUFHcEMsT0FBTyxDQUFDLG1CQUFtQixDQUEwQjtJQUVyRCxPQUFPLENBQUMsTUFBTSxDQUFpQztJQUUvQyxPQUFPLENBQUMsNEJBQTRCLENBU2xDO0lBRUYsWUFDVSxhQUFhLEVBQUUseUJBQXlCLEVBQ3hDLFVBQVUsRUFBRSxVQUFVLEVBQzlCLE1BQU0sRUFBRSw4QkFBOEIsRUFLdkM7SUFFTSxZQUFZLENBQUMsU0FBUyxFQUFFLE9BQU8sQ0FBQyw4QkFBOEIsQ0FBQyxRQUdyRTtJQUVNLEtBQUssa0JBR1g7SUFFTSxJQUFJLGtCQU1WO0lBRUQsT0FBTyxDQUFDLHVCQUF1QjtJQXlCL0IsT0FBTyxDQUFDLCtCQUErQjtJQTJCdkMsT0FBTyxDQUFDLGFBQWE7SUFpQ3JCLE9BQU8sQ0FBQyxnQ0FBZ0M7SUFheEMsT0FBTyxDQUFDLG9CQUFvQjtDQVM3QiJ9
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"attestations_block_watcher.d.ts","sourceRoot":"","sources":["../../src/watchers/attestations_block_watcher.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAC;AAIhD,OAAO,
|
|
1
|
+
{"version":3,"file":"attestations_block_watcher.d.ts","sourceRoot":"","sources":["../../src/watchers/attestations_block_watcher.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAC;AAIhD,OAAO,EAEL,KAAK,yBAAyB,EAG/B,MAAM,qBAAqB,CAAC;AAM7B,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAClD,OAAO,EAA6C,KAAK,OAAO,EAAE,KAAK,cAAc,EAAE,MAAM,eAAe,CAAC;AAE7G,QAAA,MAAM,kCAAkC,8FAG9B,CAAC;AAEX,KAAK,8BAA8B,GAAG,IAAI,CAAC,aAAa,EAAE,CAAC,OAAO,kCAAkC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC;;AAE/G;;;;;GAKG;AACH,qBAAa,wBAAyB,SAAQ,6BAA2C,YAAW,OAAO;IAuBvG,OAAO,CAAC,aAAa;IACrB,OAAO,CAAC,UAAU;IAvBpB,OAAO,CAAC,GAAG,CAAsD;IAGjE,OAAO,CAAC,qBAAqB,CAAO;IAGpC,OAAO,CAAC,mBAAmB,CAA0B;IAErD,OAAO,CAAC,MAAM,CAAiC;IAE/C,OAAO,CAAC,4BAA4B,CASlC;IAEF,YACU,aAAa,EAAE,yBAAyB,EACxC,UAAU,EAAE,UAAU,EAC9B,MAAM,EAAE,8BAA8B,EAKvC;IAEM,YAAY,CAAC,SAAS,EAAE,OAAO,CAAC,8BAA8B,CAAC,QAGrE;IAEM,KAAK,kBAGX;IAEM,IAAI,kBAMV;IAED,OAAO,CAAC,uBAAuB;IAyB/B,OAAO,CAAC,+BAA+B;IA2BvC,OAAO,CAAC,aAAa;IAiCrB,OAAO,CAAC,gCAAgC;IAaxC,OAAO,CAAC,oBAAoB;CAS7B"}
|
|
@@ -18,18 +18,18 @@ const AttestationsBlockWatcherConfigKeys = [
|
|
|
18
18
|
l2BlockSource;
|
|
19
19
|
epochCache;
|
|
20
20
|
log;
|
|
21
|
-
// Only keep track of the last N invalid
|
|
22
|
-
|
|
21
|
+
// Only keep track of the last N invalid checkpoints
|
|
22
|
+
maxInvalidCheckpoints;
|
|
23
23
|
// All invalid archive roots seen
|
|
24
24
|
invalidArchiveRoots;
|
|
25
25
|
config;
|
|
26
|
-
|
|
26
|
+
boundHandleInvalidCheckpoint;
|
|
27
27
|
constructor(l2BlockSource, epochCache, config){
|
|
28
|
-
super(), this.l2BlockSource = l2BlockSource, this.epochCache = epochCache, this.log = createLogger('attestations-block-watcher'), this.
|
|
28
|
+
super(), this.l2BlockSource = l2BlockSource, this.epochCache = epochCache, this.log = createLogger('attestations-block-watcher'), this.maxInvalidCheckpoints = 100, this.invalidArchiveRoots = new Set(), this.boundHandleInvalidCheckpoint = (event)=>{
|
|
29
29
|
try {
|
|
30
|
-
this.
|
|
30
|
+
this.handleInvalidCheckpoint(event);
|
|
31
31
|
} catch (err) {
|
|
32
|
-
this.log.error('Error handling invalid
|
|
32
|
+
this.log.error('Error handling invalid checkpoint', err, {
|
|
33
33
|
...event.validationResult,
|
|
34
34
|
reason: event.validationResult.reason
|
|
35
35
|
});
|
|
@@ -43,39 +43,39 @@ const AttestationsBlockWatcherConfigKeys = [
|
|
|
43
43
|
this.log.verbose('AttestationsBlockWatcher config updated', this.config);
|
|
44
44
|
}
|
|
45
45
|
start() {
|
|
46
|
-
this.l2BlockSource.on(L2BlockSourceEvents.
|
|
46
|
+
this.l2BlockSource.on(L2BlockSourceEvents.InvalidAttestationsCheckpointDetected, this.boundHandleInvalidCheckpoint);
|
|
47
47
|
return Promise.resolve();
|
|
48
48
|
}
|
|
49
49
|
stop() {
|
|
50
|
-
this.l2BlockSource.removeListener(L2BlockSourceEvents.
|
|
50
|
+
this.l2BlockSource.removeListener(L2BlockSourceEvents.InvalidAttestationsCheckpointDetected, this.boundHandleInvalidCheckpoint);
|
|
51
51
|
return Promise.resolve();
|
|
52
52
|
}
|
|
53
|
-
|
|
53
|
+
handleInvalidCheckpoint(event) {
|
|
54
54
|
const { validationResult } = event;
|
|
55
|
-
const
|
|
56
|
-
// Check if we already have processed this
|
|
57
|
-
if (this.invalidArchiveRoots.has(
|
|
58
|
-
this.log.trace(`Already processed invalid
|
|
55
|
+
const checkpoint = validationResult.checkpoint;
|
|
56
|
+
// Check if we already have processed this checkpoint, archiver may emit the same event multiple times
|
|
57
|
+
if (this.invalidArchiveRoots.has(checkpoint.archive.toString())) {
|
|
58
|
+
this.log.trace(`Already processed invalid checkpoint ${checkpoint.checkpointNumber}`);
|
|
59
59
|
return;
|
|
60
60
|
}
|
|
61
|
-
this.log.verbose(`Detected invalid
|
|
62
|
-
...
|
|
61
|
+
this.log.verbose(`Detected invalid checkpoint ${checkpoint.checkpointNumber}`, {
|
|
62
|
+
...checkpoint,
|
|
63
63
|
reason: validationResult.valid === false ? validationResult.reason : 'unknown'
|
|
64
64
|
});
|
|
65
|
-
// Store the invalid
|
|
66
|
-
this.
|
|
67
|
-
// Slash the proposer of the invalid
|
|
65
|
+
// Store the invalid checkpoint
|
|
66
|
+
this.addInvalidCheckpoint(event.validationResult.checkpoint);
|
|
67
|
+
// Slash the proposer of the invalid checkpoint
|
|
68
68
|
this.slashProposer(event.validationResult);
|
|
69
|
-
// Check if the parent of this
|
|
69
|
+
// Check if the parent of this checkpoint is invalid as well, if so, we will slash its attestors as well
|
|
70
70
|
this.slashAttestorsOnAncestorInvalid(event.validationResult);
|
|
71
71
|
}
|
|
72
72
|
slashAttestorsOnAncestorInvalid(validationResult) {
|
|
73
|
-
const
|
|
74
|
-
const parentArchive =
|
|
73
|
+
const checkpoint = validationResult.checkpoint;
|
|
74
|
+
const parentArchive = checkpoint.lastArchive.toString();
|
|
75
75
|
if (this.invalidArchiveRoots.has(parentArchive)) {
|
|
76
76
|
const attestors = validationResult.attestors;
|
|
77
|
-
this.log.info(`Want to slash attestors of
|
|
78
|
-
...
|
|
77
|
+
this.log.info(`Want to slash attestors of checkpoint ${checkpoint.checkpointNumber} built on invalid checkpoint`, {
|
|
78
|
+
...checkpoint,
|
|
79
79
|
...attestors,
|
|
80
80
|
parentArchive
|
|
81
81
|
});
|
|
@@ -83,17 +83,22 @@ const AttestationsBlockWatcherConfigKeys = [
|
|
|
83
83
|
validator: attestor,
|
|
84
84
|
amount: this.config.slashAttestDescendantOfInvalidPenalty,
|
|
85
85
|
offenseType: OffenseType.ATTESTED_DESCENDANT_OF_INVALID,
|
|
86
|
-
epochOrSlot: BigInt(SlotNumber(
|
|
86
|
+
epochOrSlot: BigInt(SlotNumber(checkpoint.slotNumber))
|
|
87
87
|
})));
|
|
88
88
|
}
|
|
89
89
|
}
|
|
90
90
|
slashProposer(validationResult) {
|
|
91
|
-
const { reason,
|
|
92
|
-
const
|
|
93
|
-
const slot =
|
|
94
|
-
const
|
|
91
|
+
const { reason, checkpoint } = validationResult;
|
|
92
|
+
const checkpointNumber = checkpoint.checkpointNumber;
|
|
93
|
+
const slot = checkpoint.slotNumber;
|
|
94
|
+
const epochCommitteeInfo = {
|
|
95
|
+
committee: validationResult.committee,
|
|
96
|
+
seed: validationResult.seed,
|
|
97
|
+
epoch: validationResult.epoch
|
|
98
|
+
};
|
|
99
|
+
const proposer = this.epochCache.getProposerFromEpochCommittee(epochCommitteeInfo, slot);
|
|
95
100
|
if (!proposer) {
|
|
96
|
-
this.log.warn(`No proposer found for
|
|
101
|
+
this.log.warn(`No proposer found for checkpoint ${checkpointNumber} at slot ${slot}`);
|
|
97
102
|
return;
|
|
98
103
|
}
|
|
99
104
|
const offense = this.getOffenseFromInvalidationReason(reason);
|
|
@@ -104,8 +109,8 @@ const AttestationsBlockWatcherConfigKeys = [
|
|
|
104
109
|
offenseType: offense,
|
|
105
110
|
epochOrSlot: BigInt(slot)
|
|
106
111
|
};
|
|
107
|
-
this.log.info(`Want to slash proposer of
|
|
108
|
-
...
|
|
112
|
+
this.log.info(`Want to slash proposer of checkpoint ${checkpointNumber} due to ${reason}`, {
|
|
113
|
+
...checkpoint,
|
|
109
114
|
...args
|
|
110
115
|
});
|
|
111
116
|
this.emit(WANT_TO_SLASH_EVENT, [
|
|
@@ -125,10 +130,10 @@ const AttestationsBlockWatcherConfigKeys = [
|
|
|
125
130
|
}
|
|
126
131
|
}
|
|
127
132
|
}
|
|
128
|
-
|
|
129
|
-
this.invalidArchiveRoots.add(
|
|
133
|
+
addInvalidCheckpoint(checkpoint) {
|
|
134
|
+
this.invalidArchiveRoots.add(checkpoint.archive.toString());
|
|
130
135
|
// Prune old entries if we exceed the maximum
|
|
131
|
-
if (this.invalidArchiveRoots.size > this.
|
|
136
|
+
if (this.invalidArchiveRoots.size > this.maxInvalidCheckpoints) {
|
|
132
137
|
const oldestKey = this.invalidArchiveRoots.keys().next().value;
|
|
133
138
|
this.invalidArchiveRoots.delete(oldestKey);
|
|
134
139
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { EpochCache } from '@aztec/epoch-cache';
|
|
2
|
-
import {
|
|
2
|
+
import { L2BlockNew, type L2BlockSourceEventEmitter } from '@aztec/stdlib/block';
|
|
3
3
|
import type { IFullNodeBlockBuilder, ITxProvider, MerkleTreeWriteOperations, SlasherConfig } from '@aztec/stdlib/interfaces/server';
|
|
4
4
|
import type { L1ToL2MessageSource } from '@aztec/stdlib/messaging';
|
|
5
5
|
import { type Watcher, type WatcherEmitter } from '../watcher.js';
|
|
@@ -28,10 +28,10 @@ export declare class EpochPruneWatcher extends EpochPruneWatcher_base implements
|
|
|
28
28
|
private handlePruneL2Blocks;
|
|
29
29
|
private emitSlashForEpoch;
|
|
30
30
|
private processPruneL2Blocks;
|
|
31
|
-
validateBlocks(blocks:
|
|
32
|
-
validateBlock(blockFromL1:
|
|
31
|
+
validateBlocks(blocks: L2BlockNew[]): Promise<void>;
|
|
32
|
+
validateBlock(blockFromL1: L2BlockNew, fork: MerkleTreeWriteOperations): Promise<void>;
|
|
33
33
|
private getValidatorsForEpoch;
|
|
34
34
|
private validatorsToSlashingArgs;
|
|
35
35
|
}
|
|
36
36
|
export {};
|
|
37
|
-
//# sourceMappingURL=data:application/json;base64,
|
|
37
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiZXBvY2hfcHJ1bmVfd2F0Y2hlci5kLnRzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vc3JjL3dhdGNoZXJzL2Vwb2NoX3BydW5lX3dhdGNoZXIudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsT0FBTyxFQUFFLFVBQVUsRUFBRSxNQUFNLG9CQUFvQixDQUFDO0FBSWhELE9BQU8sRUFFTCxVQUFVLEVBRVYsS0FBSyx5QkFBeUIsRUFFL0IsTUFBTSxxQkFBcUIsQ0FBQztBQUU3QixPQUFPLEtBQUssRUFDVixxQkFBcUIsRUFDckIsV0FBVyxFQUNYLHlCQUF5QixFQUN6QixhQUFhLEVBQ2QsTUFBTSxpQ0FBaUMsQ0FBQztBQUN6QyxPQUFPLEtBQUssRUFBRSxtQkFBbUIsRUFBRSxNQUFNLHlCQUF5QixDQUFDO0FBV25FLE9BQU8sRUFBNkMsS0FBSyxPQUFPLEVBQUUsS0FBSyxjQUFjLEVBQUUsTUFBTSxlQUFlLENBQUM7QUFFN0csUUFBQSxNQUFNLG9DQUFvQywrREFBZ0UsQ0FBQztBQUUzRyxLQUFLLDBCQUEwQixHQUFHLElBQUksQ0FBQyxhQUFhLEVBQUUsQ0FBQyxPQUFPLG9DQUFvQyxDQUFDLENBQUMsTUFBTSxDQUFDLENBQUMsQ0FBQzs7QUFFN0c7Ozs7O0dBS0c7QUFDSCxxQkFBYSxpQkFBa0IsU0FBUSxzQkFBMkMsWUFBVyxPQUFPO0lBU2hHLE9BQU8sQ0FBQyxhQUFhO0lBQ3JCLE9BQU8sQ0FBQyxtQkFBbUI7SUFDM0IsT0FBTyxDQUFDLFVBQVU7SUFDbEIsT0FBTyxDQUFDLFVBQVU7SUFDbEIsT0FBTyxDQUFDLFlBQVk7SUFadEIsT0FBTyxDQUFDLEdBQUcsQ0FBK0M7SUFHMUQsT0FBTyxDQUFDLHdCQUF3QixDQUF1QztJQUV2RSxPQUFPLENBQUMsU0FBUyxDQUE2QjtJQUU5QyxZQUNVLGFBQWEsRUFBRSx5QkFBeUIsRUFDeEMsbUJBQW1CLEVBQUUsbUJBQW1CLEVBQ3hDLFVBQVUsRUFBRSxVQUFVLEVBQ3RCLFVBQVUsRUFBRSxJQUFJLENBQUMsV0FBVyxFQUFFLGlCQUFpQixDQUFDLEVBQ2hELFlBQVksRUFBRSxxQkFBcUIsRUFDM0MsU0FBUyxFQUFFLDBCQUEwQixFQU90QztJQUVNLEtBQUssa0JBR1g7SUFFTSxJQUFJLGtCQUdWO0lBRU0sWUFBWSxDQUFDLE1BQU0sRUFBRSxPQUFPLENBQUMsYUFBYSxDQUFDLEdBQUcsSUFBSSxDQUd4RDtJQUVELE9BQU8sQ0FBQyxtQkFBbUI7WUFPYixpQkFBaUI7WUFXakIsb0JBQW9CO0lBd0JyQixjQUFjLENBQUMsTUFBTSxFQUFFLFVBQVUsRUFBRSxHQUFHLE9BQU8sQ0FBQyxJQUFJLENBQUMsQ0FZL0Q7SUFFWSxhQUFhLENBQUMsV0FBVyxFQUFFLFVBQVUsRUFBRSxJQUFJLEVBQUUseUJBQXlCLEdBQUcsT0FBTyxDQUFDLElBQUksQ0FBQyxDQStCbEc7WUFFYSxxQkFBcUI7SUFTbkMsT0FBTyxDQUFDLHdCQUF3QjtDQWdCakMifQ==
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"epoch_prune_watcher.d.ts","sourceRoot":"","sources":["../../src/watchers/epoch_prune_watcher.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAC;AAIhD,OAAO,EAEL,
|
|
1
|
+
{"version":3,"file":"epoch_prune_watcher.d.ts","sourceRoot":"","sources":["../../src/watchers/epoch_prune_watcher.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAC;AAIhD,OAAO,EAEL,UAAU,EAEV,KAAK,yBAAyB,EAE/B,MAAM,qBAAqB,CAAC;AAE7B,OAAO,KAAK,EACV,qBAAqB,EACrB,WAAW,EACX,yBAAyB,EACzB,aAAa,EACd,MAAM,iCAAiC,CAAC;AACzC,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,yBAAyB,CAAC;AAWnE,OAAO,EAA6C,KAAK,OAAO,EAAE,KAAK,cAAc,EAAE,MAAM,eAAe,CAAC;AAE7G,QAAA,MAAM,oCAAoC,+DAAgE,CAAC;AAE3G,KAAK,0BAA0B,GAAG,IAAI,CAAC,aAAa,EAAE,CAAC,OAAO,oCAAoC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC;;AAE7G;;;;;GAKG;AACH,qBAAa,iBAAkB,SAAQ,sBAA2C,YAAW,OAAO;IAShG,OAAO,CAAC,aAAa;IACrB,OAAO,CAAC,mBAAmB;IAC3B,OAAO,CAAC,UAAU;IAClB,OAAO,CAAC,UAAU;IAClB,OAAO,CAAC,YAAY;IAZtB,OAAO,CAAC,GAAG,CAA+C;IAG1D,OAAO,CAAC,wBAAwB,CAAuC;IAEvE,OAAO,CAAC,SAAS,CAA6B;IAE9C,YACU,aAAa,EAAE,yBAAyB,EACxC,mBAAmB,EAAE,mBAAmB,EACxC,UAAU,EAAE,UAAU,EACtB,UAAU,EAAE,IAAI,CAAC,WAAW,EAAE,iBAAiB,CAAC,EAChD,YAAY,EAAE,qBAAqB,EAC3C,SAAS,EAAE,0BAA0B,EAOtC;IAEM,KAAK,kBAGX;IAEM,IAAI,kBAGV;IAEM,YAAY,CAAC,MAAM,EAAE,OAAO,CAAC,aAAa,CAAC,GAAG,IAAI,CAGxD;IAED,OAAO,CAAC,mBAAmB;YAOb,iBAAiB;YAWjB,oBAAoB;IAwBrB,cAAc,CAAC,MAAM,EAAE,UAAU,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAY/D;IAEY,aAAa,CAAC,WAAW,EAAE,UAAU,EAAE,IAAI,EAAE,yBAAyB,GAAG,OAAO,CAAC,IAAI,CAAC,CA+BlG;YAEa,qBAAqB;IASnC,OAAO,CAAC,wBAAwB;CAgBjC"}
|
|
@@ -62,7 +62,7 @@ const EpochPruneWatcherPenaltiesConfigKeys = [
|
|
|
62
62
|
async processPruneL2Blocks(blocks, epochNumber) {
|
|
63
63
|
try {
|
|
64
64
|
const l1Constants = this.epochCache.getL1Constants();
|
|
65
|
-
const epochBlocks = blocks.filter((b)=>getEpochAtSlot(b.
|
|
65
|
+
const epochBlocks = blocks.filter((b)=>getEpochAtSlot(b.header.getSlot(), l1Constants) === epochNumber);
|
|
66
66
|
this.log.info(`Detected chain prune. Validating epoch ${epochNumber} with blocks ${epochBlocks[0]?.number} to ${epochBlocks[epochBlocks.length - 1]?.number}.`, {
|
|
67
67
|
blocks: epochBlocks.map((b)=>b.toBlockInfo())
|
|
68
68
|
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aztec/slasher",
|
|
3
|
-
"version": "0.0.1-commit.
|
|
3
|
+
"version": "0.0.1-commit.96bb3f7",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"exports": {
|
|
6
6
|
".": "./dest/index.js",
|
|
@@ -54,20 +54,20 @@
|
|
|
54
54
|
]
|
|
55
55
|
},
|
|
56
56
|
"dependencies": {
|
|
57
|
-
"@aztec/epoch-cache": "0.0.1-commit.
|
|
58
|
-
"@aztec/ethereum": "0.0.1-commit.
|
|
59
|
-
"@aztec/foundation": "0.0.1-commit.
|
|
60
|
-
"@aztec/kv-store": "0.0.1-commit.
|
|
61
|
-
"@aztec/l1-artifacts": "0.0.1-commit.
|
|
62
|
-
"@aztec/stdlib": "0.0.1-commit.
|
|
63
|
-
"@aztec/telemetry-client": "0.0.1-commit.
|
|
57
|
+
"@aztec/epoch-cache": "0.0.1-commit.96bb3f7",
|
|
58
|
+
"@aztec/ethereum": "0.0.1-commit.96bb3f7",
|
|
59
|
+
"@aztec/foundation": "0.0.1-commit.96bb3f7",
|
|
60
|
+
"@aztec/kv-store": "0.0.1-commit.96bb3f7",
|
|
61
|
+
"@aztec/l1-artifacts": "0.0.1-commit.96bb3f7",
|
|
62
|
+
"@aztec/stdlib": "0.0.1-commit.96bb3f7",
|
|
63
|
+
"@aztec/telemetry-client": "0.0.1-commit.96bb3f7",
|
|
64
64
|
"source-map-support": "^0.5.21",
|
|
65
65
|
"tslib": "^2.4.0",
|
|
66
66
|
"viem": "npm:@aztec/viem@2.38.2",
|
|
67
67
|
"zod": "^3.23.8"
|
|
68
68
|
},
|
|
69
69
|
"devDependencies": {
|
|
70
|
-
"@aztec/aztec.js": "0.0.1-commit.
|
|
70
|
+
"@aztec/aztec.js": "0.0.1-commit.96bb3f7",
|
|
71
71
|
"@jest/globals": "^30.0.0",
|
|
72
72
|
"@types/jest": "^30.0.0",
|
|
73
73
|
"@types/node": "^22.15.17",
|
|
@@ -3,12 +3,12 @@ import { SlotNumber } from '@aztec/foundation/branded-types';
|
|
|
3
3
|
import { merge, pick } from '@aztec/foundation/collection';
|
|
4
4
|
import { type Logger, createLogger } from '@aztec/foundation/log';
|
|
5
5
|
import {
|
|
6
|
-
type
|
|
7
|
-
type L2BlockInfo,
|
|
6
|
+
type InvalidCheckpointDetectedEvent,
|
|
8
7
|
type L2BlockSourceEventEmitter,
|
|
9
8
|
L2BlockSourceEvents,
|
|
10
|
-
type
|
|
9
|
+
type ValidateCheckpointNegativeResult,
|
|
11
10
|
} from '@aztec/stdlib/block';
|
|
11
|
+
import type { CheckpointInfo } from '@aztec/stdlib/checkpoint';
|
|
12
12
|
import { OffenseType } from '@aztec/stdlib/slashing';
|
|
13
13
|
|
|
14
14
|
import EventEmitter from 'node:events';
|
|
@@ -32,19 +32,19 @@ type AttestationsBlockWatcherConfig = Pick<SlasherConfig, (typeof AttestationsBl
|
|
|
32
32
|
export class AttestationsBlockWatcher extends (EventEmitter as new () => WatcherEmitter) implements Watcher {
|
|
33
33
|
private log: Logger = createLogger('attestations-block-watcher');
|
|
34
34
|
|
|
35
|
-
// Only keep track of the last N invalid
|
|
36
|
-
private
|
|
35
|
+
// Only keep track of the last N invalid checkpoints
|
|
36
|
+
private maxInvalidCheckpoints = 100;
|
|
37
37
|
|
|
38
38
|
// All invalid archive roots seen
|
|
39
39
|
private invalidArchiveRoots: Set<string> = new Set();
|
|
40
40
|
|
|
41
41
|
private config: AttestationsBlockWatcherConfig;
|
|
42
42
|
|
|
43
|
-
private
|
|
43
|
+
private boundHandleInvalidCheckpoint = (event: InvalidCheckpointDetectedEvent) => {
|
|
44
44
|
try {
|
|
45
|
-
this.
|
|
45
|
+
this.handleInvalidCheckpoint(event);
|
|
46
46
|
} catch (err) {
|
|
47
|
-
this.log.error('Error handling invalid
|
|
47
|
+
this.log.error('Error handling invalid checkpoint', err, {
|
|
48
48
|
...event.validationResult,
|
|
49
49
|
reason: event.validationResult.reason,
|
|
50
50
|
});
|
|
@@ -67,54 +67,57 @@ export class AttestationsBlockWatcher extends (EventEmitter as new () => Watcher
|
|
|
67
67
|
}
|
|
68
68
|
|
|
69
69
|
public start() {
|
|
70
|
-
this.l2BlockSource.on(L2BlockSourceEvents.
|
|
70
|
+
this.l2BlockSource.on(L2BlockSourceEvents.InvalidAttestationsCheckpointDetected, this.boundHandleInvalidCheckpoint);
|
|
71
71
|
return Promise.resolve();
|
|
72
72
|
}
|
|
73
73
|
|
|
74
74
|
public stop() {
|
|
75
75
|
this.l2BlockSource.removeListener(
|
|
76
|
-
L2BlockSourceEvents.
|
|
77
|
-
this.
|
|
76
|
+
L2BlockSourceEvents.InvalidAttestationsCheckpointDetected,
|
|
77
|
+
this.boundHandleInvalidCheckpoint,
|
|
78
78
|
);
|
|
79
79
|
return Promise.resolve();
|
|
80
80
|
}
|
|
81
81
|
|
|
82
|
-
private
|
|
82
|
+
private handleInvalidCheckpoint(event: InvalidCheckpointDetectedEvent): void {
|
|
83
83
|
const { validationResult } = event;
|
|
84
|
-
const
|
|
84
|
+
const checkpoint = validationResult.checkpoint;
|
|
85
85
|
|
|
86
|
-
// Check if we already have processed this
|
|
87
|
-
if (this.invalidArchiveRoots.has(
|
|
88
|
-
this.log.trace(`Already processed invalid
|
|
86
|
+
// Check if we already have processed this checkpoint, archiver may emit the same event multiple times
|
|
87
|
+
if (this.invalidArchiveRoots.has(checkpoint.archive.toString())) {
|
|
88
|
+
this.log.trace(`Already processed invalid checkpoint ${checkpoint.checkpointNumber}`);
|
|
89
89
|
return;
|
|
90
90
|
}
|
|
91
91
|
|
|
92
|
-
this.log.verbose(`Detected invalid
|
|
93
|
-
...
|
|
92
|
+
this.log.verbose(`Detected invalid checkpoint ${checkpoint.checkpointNumber}`, {
|
|
93
|
+
...checkpoint,
|
|
94
94
|
reason: validationResult.valid === false ? validationResult.reason : 'unknown',
|
|
95
95
|
});
|
|
96
96
|
|
|
97
|
-
// Store the invalid
|
|
98
|
-
this.
|
|
97
|
+
// Store the invalid checkpoint
|
|
98
|
+
this.addInvalidCheckpoint(event.validationResult.checkpoint);
|
|
99
99
|
|
|
100
|
-
// Slash the proposer of the invalid
|
|
100
|
+
// Slash the proposer of the invalid checkpoint
|
|
101
101
|
this.slashProposer(event.validationResult);
|
|
102
102
|
|
|
103
|
-
// Check if the parent of this
|
|
103
|
+
// Check if the parent of this checkpoint is invalid as well, if so, we will slash its attestors as well
|
|
104
104
|
this.slashAttestorsOnAncestorInvalid(event.validationResult);
|
|
105
105
|
}
|
|
106
106
|
|
|
107
|
-
private slashAttestorsOnAncestorInvalid(validationResult:
|
|
108
|
-
const
|
|
107
|
+
private slashAttestorsOnAncestorInvalid(validationResult: ValidateCheckpointNegativeResult) {
|
|
108
|
+
const checkpoint = validationResult.checkpoint;
|
|
109
109
|
|
|
110
|
-
const parentArchive =
|
|
110
|
+
const parentArchive = checkpoint.lastArchive.toString();
|
|
111
111
|
if (this.invalidArchiveRoots.has(parentArchive)) {
|
|
112
112
|
const attestors = validationResult.attestors;
|
|
113
|
-
this.log.info(
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
113
|
+
this.log.info(
|
|
114
|
+
`Want to slash attestors of checkpoint ${checkpoint.checkpointNumber} built on invalid checkpoint`,
|
|
115
|
+
{
|
|
116
|
+
...checkpoint,
|
|
117
|
+
...attestors,
|
|
118
|
+
parentArchive,
|
|
119
|
+
},
|
|
120
|
+
);
|
|
118
121
|
|
|
119
122
|
this.emit(
|
|
120
123
|
WANT_TO_SLASH_EVENT,
|
|
@@ -122,20 +125,25 @@ export class AttestationsBlockWatcher extends (EventEmitter as new () => Watcher
|
|
|
122
125
|
validator: attestor,
|
|
123
126
|
amount: this.config.slashAttestDescendantOfInvalidPenalty,
|
|
124
127
|
offenseType: OffenseType.ATTESTED_DESCENDANT_OF_INVALID,
|
|
125
|
-
epochOrSlot: BigInt(SlotNumber(
|
|
128
|
+
epochOrSlot: BigInt(SlotNumber(checkpoint.slotNumber)),
|
|
126
129
|
})),
|
|
127
130
|
);
|
|
128
131
|
}
|
|
129
132
|
}
|
|
130
133
|
|
|
131
|
-
private slashProposer(validationResult:
|
|
132
|
-
const { reason,
|
|
133
|
-
const
|
|
134
|
-
const slot =
|
|
135
|
-
const
|
|
134
|
+
private slashProposer(validationResult: ValidateCheckpointNegativeResult) {
|
|
135
|
+
const { reason, checkpoint } = validationResult;
|
|
136
|
+
const checkpointNumber = checkpoint.checkpointNumber;
|
|
137
|
+
const slot = checkpoint.slotNumber;
|
|
138
|
+
const epochCommitteeInfo = {
|
|
139
|
+
committee: validationResult.committee,
|
|
140
|
+
seed: validationResult.seed,
|
|
141
|
+
epoch: validationResult.epoch,
|
|
142
|
+
};
|
|
143
|
+
const proposer = this.epochCache.getProposerFromEpochCommittee(epochCommitteeInfo, slot);
|
|
136
144
|
|
|
137
145
|
if (!proposer) {
|
|
138
|
-
this.log.warn(`No proposer found for
|
|
146
|
+
this.log.warn(`No proposer found for checkpoint ${checkpointNumber} at slot ${slot}`);
|
|
139
147
|
return;
|
|
140
148
|
}
|
|
141
149
|
|
|
@@ -148,15 +156,15 @@ export class AttestationsBlockWatcher extends (EventEmitter as new () => Watcher
|
|
|
148
156
|
epochOrSlot: BigInt(slot),
|
|
149
157
|
};
|
|
150
158
|
|
|
151
|
-
this.log.info(`Want to slash proposer of
|
|
152
|
-
...
|
|
159
|
+
this.log.info(`Want to slash proposer of checkpoint ${checkpointNumber} due to ${reason}`, {
|
|
160
|
+
...checkpoint,
|
|
153
161
|
...args,
|
|
154
162
|
});
|
|
155
163
|
|
|
156
164
|
this.emit(WANT_TO_SLASH_EVENT, [args]);
|
|
157
165
|
}
|
|
158
166
|
|
|
159
|
-
private getOffenseFromInvalidationReason(reason:
|
|
167
|
+
private getOffenseFromInvalidationReason(reason: ValidateCheckpointNegativeResult['reason']): OffenseType {
|
|
160
168
|
switch (reason) {
|
|
161
169
|
case 'invalid-attestation':
|
|
162
170
|
return OffenseType.PROPOSED_INCORRECT_ATTESTATIONS;
|
|
@@ -169,11 +177,11 @@ export class AttestationsBlockWatcher extends (EventEmitter as new () => Watcher
|
|
|
169
177
|
}
|
|
170
178
|
}
|
|
171
179
|
|
|
172
|
-
private
|
|
173
|
-
this.invalidArchiveRoots.add(
|
|
180
|
+
private addInvalidCheckpoint(checkpoint: CheckpointInfo) {
|
|
181
|
+
this.invalidArchiveRoots.add(checkpoint.archive.toString());
|
|
174
182
|
|
|
175
183
|
// Prune old entries if we exceed the maximum
|
|
176
|
-
if (this.invalidArchiveRoots.size > this.
|
|
184
|
+
if (this.invalidArchiveRoots.size > this.maxInvalidCheckpoints) {
|
|
177
185
|
const oldestKey = this.invalidArchiveRoots.keys().next().value!;
|
|
178
186
|
this.invalidArchiveRoots.delete(oldestKey);
|
|
179
187
|
}
|
|
@@ -4,7 +4,7 @@ import { merge, pick } from '@aztec/foundation/collection';
|
|
|
4
4
|
import { type Logger, createLogger } from '@aztec/foundation/log';
|
|
5
5
|
import {
|
|
6
6
|
EthAddress,
|
|
7
|
-
|
|
7
|
+
L2BlockNew,
|
|
8
8
|
type L2BlockPruneEvent,
|
|
9
9
|
type L2BlockSourceEventEmitter,
|
|
10
10
|
L2BlockSourceEvents,
|
|
@@ -95,10 +95,10 @@ export class EpochPruneWatcher extends (EventEmitter as new () => WatcherEmitter
|
|
|
95
95
|
this.emit(WANT_TO_SLASH_EVENT, args);
|
|
96
96
|
}
|
|
97
97
|
|
|
98
|
-
private async processPruneL2Blocks(blocks:
|
|
98
|
+
private async processPruneL2Blocks(blocks: L2BlockNew[], epochNumber: EpochNumber): Promise<void> {
|
|
99
99
|
try {
|
|
100
100
|
const l1Constants = this.epochCache.getL1Constants();
|
|
101
|
-
const epochBlocks = blocks.filter(b => getEpochAtSlot(b.
|
|
101
|
+
const epochBlocks = blocks.filter(b => getEpochAtSlot(b.header.getSlot(), l1Constants) === epochNumber);
|
|
102
102
|
this.log.info(
|
|
103
103
|
`Detected chain prune. Validating epoch ${epochNumber} with blocks ${epochBlocks[0]?.number} to ${epochBlocks[epochBlocks.length - 1]?.number}.`,
|
|
104
104
|
{ blocks: epochBlocks.map(b => b.toBlockInfo()) },
|
|
@@ -119,7 +119,7 @@ export class EpochPruneWatcher extends (EventEmitter as new () => WatcherEmitter
|
|
|
119
119
|
}
|
|
120
120
|
}
|
|
121
121
|
|
|
122
|
-
public async validateBlocks(blocks:
|
|
122
|
+
public async validateBlocks(blocks: L2BlockNew[]): Promise<void> {
|
|
123
123
|
if (blocks.length === 0) {
|
|
124
124
|
return;
|
|
125
125
|
}
|
|
@@ -133,7 +133,7 @@ export class EpochPruneWatcher extends (EventEmitter as new () => WatcherEmitter
|
|
|
133
133
|
}
|
|
134
134
|
}
|
|
135
135
|
|
|
136
|
-
public async validateBlock(blockFromL1:
|
|
136
|
+
public async validateBlock(blockFromL1: L2BlockNew, fork: MerkleTreeWriteOperations): Promise<void> {
|
|
137
137
|
this.log.debug(`Validating pruned block ${blockFromL1.header.globalVariables.blockNumber}`);
|
|
138
138
|
const txHashes = blockFromL1.body.txEffects.map(txEffect => txEffect.txHash);
|
|
139
139
|
// We load txs from the mempool directly, since the TxCollector running in the background has already been
|