@aztec/slasher 0.0.1-commit.9b94fc1 → 0.0.1-commit.9d2bcf6d
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/config.d.ts +1 -1
- package/dest/config.d.ts.map +1 -1
- package/dest/config.js +16 -16
- package/dest/empire_slasher_client.d.ts +2 -2
- package/dest/empire_slasher_client.d.ts.map +1 -1
- package/dest/empire_slasher_client.js +0 -8
- package/dest/factory/create_facade.d.ts +3 -2
- package/dest/factory/create_facade.d.ts.map +1 -1
- package/dest/factory/create_facade.js +1 -1
- package/dest/factory/create_implementation.d.ts +2 -2
- package/dest/factory/create_implementation.d.ts.map +1 -1
- package/dest/factory/get_settings.d.ts +2 -2
- package/dest/factory/get_settings.d.ts.map +1 -1
- package/dest/generated/slasher-defaults.d.ts +19 -0
- package/dest/generated/slasher-defaults.d.ts.map +1 -0
- package/dest/generated/slasher-defaults.js +19 -0
- package/dest/slash_offenses_collector.d.ts +1 -1
- package/dest/slash_offenses_collector.d.ts.map +1 -1
- package/dest/slash_offenses_collector.js +5 -1
- package/dest/slasher_client_facade.d.ts +2 -2
- package/dest/slasher_client_facade.d.ts.map +1 -1
- package/dest/stores/offenses_store.d.ts +1 -1
- package/dest/stores/offenses_store.d.ts.map +1 -1
- package/dest/stores/offenses_store.js +4 -2
- package/dest/stores/payloads_store.d.ts +1 -1
- package/dest/stores/payloads_store.d.ts.map +1 -1
- package/dest/stores/payloads_store.js +6 -3
- package/dest/tally_slasher_client.d.ts +1 -1
- package/dest/tally_slasher_client.d.ts.map +1 -1
- package/dest/tally_slasher_client.js +10 -5
- package/dest/watchers/attestations_block_watcher.d.ts +7 -6
- package/dest/watchers/attestations_block_watcher.d.ts.map +1 -1
- package/dest/watchers/attestations_block_watcher.js +40 -34
- package/dest/watchers/epoch_prune_watcher.d.ts +7 -6
- package/dest/watchers/epoch_prune_watcher.d.ts.map +1 -1
- package/dest/watchers/epoch_prune_watcher.js +34 -11
- package/package.json +16 -14
- package/src/config.ts +17 -16
- package/src/empire_slasher_client.ts +1 -9
- package/src/factory/create_facade.ts +3 -2
- package/src/factory/create_implementation.ts +1 -1
- package/src/factory/get_settings.ts +1 -1
- package/src/generated/slasher-defaults.ts +21 -0
- package/src/slash_offenses_collector.ts +5 -1
- package/src/slasher_client_facade.ts +1 -1
- package/src/stores/offenses_store.ts +4 -2
- package/src/stores/payloads_store.ts +7 -4
- package/src/tally_slasher_client.ts +10 -5
- package/src/watcher.ts +1 -1
- package/src/watchers/attestations_block_watcher.ts +57 -44
- package/src/watchers/epoch_prune_watcher.ts +45 -17
|
@@ -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.events.on(L2BlockSourceEvents.InvalidAttestationsCheckpointDetected, this.boundHandleInvalidCheckpoint);
|
|
47
47
|
return Promise.resolve();
|
|
48
48
|
}
|
|
49
49
|
stop() {
|
|
50
|
-
this.l2BlockSource.removeListener(L2BlockSourceEvents.
|
|
50
|
+
this.l2BlockSource.events.removeListener(L2BlockSourceEvents.InvalidAttestationsCheckpointDetected, this.boundHandleInvalidCheckpoint);
|
|
51
51
|
return Promise.resolve();
|
|
52
52
|
}
|
|
53
|
-
|
|
53
|
+
/** Event handler for invalid checkpoints as reported by the archiver. Public for testing purposes. */ 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,23 @@ 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
|
+
isEscapeHatchOpen: false
|
|
99
|
+
};
|
|
100
|
+
const proposer = this.epochCache.getProposerFromEpochCommittee(epochCommitteeInfo, slot);
|
|
95
101
|
if (!proposer) {
|
|
96
|
-
this.log.warn(`No proposer found for
|
|
102
|
+
this.log.warn(`No proposer found for checkpoint ${checkpointNumber} at slot ${slot}`);
|
|
97
103
|
return;
|
|
98
104
|
}
|
|
99
105
|
const offense = this.getOffenseFromInvalidationReason(reason);
|
|
@@ -104,8 +110,8 @@ const AttestationsBlockWatcherConfigKeys = [
|
|
|
104
110
|
offenseType: offense,
|
|
105
111
|
epochOrSlot: BigInt(slot)
|
|
106
112
|
};
|
|
107
|
-
this.log.info(`Want to slash proposer of
|
|
108
|
-
...
|
|
113
|
+
this.log.info(`Want to slash proposer of checkpoint ${checkpointNumber} due to ${reason}`, {
|
|
114
|
+
...checkpoint,
|
|
109
115
|
...args
|
|
110
116
|
});
|
|
111
117
|
this.emit(WANT_TO_SLASH_EVENT, [
|
|
@@ -125,10 +131,10 @@ const AttestationsBlockWatcherConfigKeys = [
|
|
|
125
131
|
}
|
|
126
132
|
}
|
|
127
133
|
}
|
|
128
|
-
|
|
129
|
-
this.invalidArchiveRoots.add(
|
|
134
|
+
addInvalidCheckpoint(checkpoint) {
|
|
135
|
+
this.invalidArchiveRoots.add(checkpoint.archive.toString());
|
|
130
136
|
// Prune old entries if we exceed the maximum
|
|
131
|
-
if (this.invalidArchiveRoots.size > this.
|
|
137
|
+
if (this.invalidArchiveRoots.size > this.maxInvalidCheckpoints) {
|
|
132
138
|
const oldestKey = this.invalidArchiveRoots.keys().next().value;
|
|
133
139
|
this.invalidArchiveRoots.delete(oldestKey);
|
|
134
140
|
}
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { EpochCache } from '@aztec/epoch-cache';
|
|
2
|
+
import type { Fr } from '@aztec/foundation/curves/bn254';
|
|
2
3
|
import { L2Block, type L2BlockSourceEventEmitter } from '@aztec/stdlib/block';
|
|
3
|
-
import type {
|
|
4
|
-
import type
|
|
4
|
+
import type { ICheckpointsBuilder, ITxProvider, MerkleTreeWriteOperations, SlasherConfig } from '@aztec/stdlib/interfaces/server';
|
|
5
|
+
import { type L1ToL2MessageSource } from '@aztec/stdlib/messaging';
|
|
5
6
|
import { type Watcher, type WatcherEmitter } from '../watcher.js';
|
|
6
7
|
declare const EpochPruneWatcherPenaltiesConfigKeys: readonly ["slashPrunePenalty", "slashDataWithholdingPenalty"];
|
|
7
8
|
type EpochPruneWatcherPenalties = Pick<SlasherConfig, (typeof EpochPruneWatcherPenaltiesConfigKeys)[number]>;
|
|
@@ -17,11 +18,11 @@ export declare class EpochPruneWatcher extends EpochPruneWatcher_base implements
|
|
|
17
18
|
private l1ToL2MessageSource;
|
|
18
19
|
private epochCache;
|
|
19
20
|
private txProvider;
|
|
20
|
-
private
|
|
21
|
+
private checkpointsBuilder;
|
|
21
22
|
private log;
|
|
22
23
|
private boundHandlePruneL2Blocks;
|
|
23
24
|
private penalties;
|
|
24
|
-
constructor(l2BlockSource: L2BlockSourceEventEmitter, l1ToL2MessageSource: L1ToL2MessageSource, epochCache: EpochCache, txProvider: Pick<ITxProvider, 'getAvailableTxs'>,
|
|
25
|
+
constructor(l2BlockSource: L2BlockSourceEventEmitter, l1ToL2MessageSource: L1ToL2MessageSource, epochCache: EpochCache, txProvider: Pick<ITxProvider, 'getAvailableTxs'>, checkpointsBuilder: ICheckpointsBuilder, penalties: EpochPruneWatcherPenalties);
|
|
25
26
|
start(): Promise<void>;
|
|
26
27
|
stop(): Promise<void>;
|
|
27
28
|
updateConfig(config: Partial<SlasherConfig>): void;
|
|
@@ -29,9 +30,9 @@ export declare class EpochPruneWatcher extends EpochPruneWatcher_base implements
|
|
|
29
30
|
private emitSlashForEpoch;
|
|
30
31
|
private processPruneL2Blocks;
|
|
31
32
|
validateBlocks(blocks: L2Block[]): Promise<void>;
|
|
32
|
-
validateBlock(blockFromL1: L2Block, fork: MerkleTreeWriteOperations): Promise<void>;
|
|
33
|
+
validateBlock(blockFromL1: L2Block, previousCheckpointOutHashes: Fr[], fork: MerkleTreeWriteOperations): Promise<void>;
|
|
33
34
|
private getValidatorsForEpoch;
|
|
34
35
|
private validatorsToSlashingArgs;
|
|
35
36
|
}
|
|
36
37
|
export {};
|
|
37
|
-
//# sourceMappingURL=data:application/json;base64,
|
|
38
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiZXBvY2hfcHJ1bmVfd2F0Y2hlci5kLnRzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vc3JjL3dhdGNoZXJzL2Vwb2NoX3BydW5lX3dhdGNoZXIudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsT0FBTyxFQUFFLFVBQVUsRUFBRSxNQUFNLG9CQUFvQixDQUFDO0FBR2hELE9BQU8sS0FBSyxFQUFFLEVBQUUsRUFBRSxNQUFNLGdDQUFnQyxDQUFDO0FBRXpELE9BQU8sRUFFTCxPQUFPLEVBQ1AsS0FBSyx5QkFBeUIsRUFHL0IsTUFBTSxxQkFBcUIsQ0FBQztBQUU3QixPQUFPLEtBQUssRUFDVixtQkFBbUIsRUFDbkIsV0FBVyxFQUNYLHlCQUF5QixFQUN6QixhQUFhLEVBQ2QsTUFBTSxpQ0FBaUMsQ0FBQztBQUN6QyxPQUFPLEVBQUUsS0FBSyxtQkFBbUIsRUFBNEIsTUFBTSx5QkFBeUIsQ0FBQztBQVk3RixPQUFPLEVBQTZDLEtBQUssT0FBTyxFQUFFLEtBQUssY0FBYyxFQUFFLE1BQU0sZUFBZSxDQUFDO0FBRTdHLFFBQUEsTUFBTSxvQ0FBb0MsK0RBQWdFLENBQUM7QUFFM0csS0FBSywwQkFBMEIsR0FBRyxJQUFJLENBQUMsYUFBYSxFQUFFLENBQUMsT0FBTyxvQ0FBb0MsQ0FBQyxDQUFDLE1BQU0sQ0FBQyxDQUFDLENBQUM7O0FBRTdHOzs7OztHQUtHO0FBQ0gscUJBQWEsaUJBQWtCLFNBQVEsc0JBQTJDLFlBQVcsT0FBTztJQVNoRyxPQUFPLENBQUMsYUFBYTtJQUNyQixPQUFPLENBQUMsbUJBQW1CO0lBQzNCLE9BQU8sQ0FBQyxVQUFVO0lBQ2xCLE9BQU8sQ0FBQyxVQUFVO0lBQ2xCLE9BQU8sQ0FBQyxrQkFBa0I7SUFaNUIsT0FBTyxDQUFDLEdBQUcsQ0FBK0M7SUFHMUQsT0FBTyxDQUFDLHdCQUF3QixDQUF1QztJQUV2RSxPQUFPLENBQUMsU0FBUyxDQUE2QjtJQUU5QyxZQUNVLGFBQWEsRUFBRSx5QkFBeUIsRUFDeEMsbUJBQW1CLEVBQUUsbUJBQW1CLEVBQ3hDLFVBQVUsRUFBRSxVQUFVLEVBQ3RCLFVBQVUsRUFBRSxJQUFJLENBQUMsV0FBVyxFQUFFLGlCQUFpQixDQUFDLEVBQ2hELGtCQUFrQixFQUFFLG1CQUFtQixFQUMvQyxTQUFTLEVBQUUsMEJBQTBCLEVBT3RDO0lBRU0sS0FBSyxrQkFHWDtJQUVNLElBQUksa0JBR1Y7SUFFTSxZQUFZLENBQUMsTUFBTSxFQUFFLE9BQU8sQ0FBQyxhQUFhLENBQUMsR0FBRyxJQUFJLENBR3hEO0lBRUQsT0FBTyxDQUFDLG1CQUFtQjtZQU9iLGlCQUFpQjtZQVdqQixvQkFBb0I7SUF3QnJCLGNBQWMsQ0FBQyxNQUFNLEVBQUUsT0FBTyxFQUFFLEdBQUcsT0FBTyxDQUFDLElBQUksQ0FBQyxDQWtCNUQ7SUFFWSxhQUFhLENBQ3hCLFdBQVcsRUFBRSxPQUFPLEVBQ3BCLDJCQUEyQixFQUFFLEVBQUUsRUFBRSxFQUNqQyxJQUFJLEVBQUUseUJBQXlCLEdBQzlCLE9BQU8sQ0FBQyxJQUFJLENBQUMsQ0E4Q2Y7WUFFYSxxQkFBcUI7SUFTbkMsT0FBTyxDQUFDLHdCQUF3QjtDQWdCakMifQ==
|
|
@@ -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;
|
|
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;AAGhD,OAAO,KAAK,EAAE,EAAE,EAAE,MAAM,gCAAgC,CAAC;AAEzD,OAAO,EAEL,OAAO,EACP,KAAK,yBAAyB,EAG/B,MAAM,qBAAqB,CAAC;AAE7B,OAAO,KAAK,EACV,mBAAmB,EACnB,WAAW,EACX,yBAAyB,EACzB,aAAa,EACd,MAAM,iCAAiC,CAAC;AACzC,OAAO,EAAE,KAAK,mBAAmB,EAA4B,MAAM,yBAAyB,CAAC;AAY7F,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,kBAAkB;IAZ5B,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,kBAAkB,EAAE,mBAAmB,EAC/C,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,OAAO,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAkB5D;IAEY,aAAa,CACxB,WAAW,EAAE,OAAO,EACpB,2BAA2B,EAAE,EAAE,EAAE,EACjC,IAAI,EAAE,yBAAyB,GAC9B,OAAO,CAAC,IAAI,CAAC,CA8Cf;YAEa,qBAAqB;IASnC,OAAO,CAAC,wBAAwB;CAgBjC"}
|
|
@@ -1,7 +1,9 @@
|
|
|
1
|
+
import { BlockNumber, CheckpointNumber } from '@aztec/foundation/branded-types';
|
|
1
2
|
import { merge, pick } from '@aztec/foundation/collection';
|
|
2
3
|
import { createLogger } from '@aztec/foundation/log';
|
|
3
4
|
import { L2BlockSourceEvents } from '@aztec/stdlib/block';
|
|
4
5
|
import { getEpochAtSlot } from '@aztec/stdlib/epoch-helpers';
|
|
6
|
+
import { computeCheckpointOutHash } from '@aztec/stdlib/messaging';
|
|
5
7
|
import { OffenseType, getOffenseTypeName } from '@aztec/stdlib/slashing';
|
|
6
8
|
import { ReExFailedTxsError, ReExStateMismatchError, TransactionsNotAvailableError, ValidatorError } from '@aztec/stdlib/validators';
|
|
7
9
|
import EventEmitter from 'node:events';
|
|
@@ -20,22 +22,22 @@ const EpochPruneWatcherPenaltiesConfigKeys = [
|
|
|
20
22
|
l1ToL2MessageSource;
|
|
21
23
|
epochCache;
|
|
22
24
|
txProvider;
|
|
23
|
-
|
|
25
|
+
checkpointsBuilder;
|
|
24
26
|
log;
|
|
25
27
|
// Store bound function reference for proper listener removal
|
|
26
28
|
boundHandlePruneL2Blocks;
|
|
27
29
|
penalties;
|
|
28
|
-
constructor(l2BlockSource, l1ToL2MessageSource, epochCache, txProvider,
|
|
29
|
-
super(), this.l2BlockSource = l2BlockSource, this.l1ToL2MessageSource = l1ToL2MessageSource, this.epochCache = epochCache, this.txProvider = txProvider, this.
|
|
30
|
+
constructor(l2BlockSource, l1ToL2MessageSource, epochCache, txProvider, checkpointsBuilder, penalties){
|
|
31
|
+
super(), this.l2BlockSource = l2BlockSource, this.l1ToL2MessageSource = l1ToL2MessageSource, this.epochCache = epochCache, this.txProvider = txProvider, this.checkpointsBuilder = checkpointsBuilder, this.log = createLogger('epoch-prune-watcher'), this.boundHandlePruneL2Blocks = this.handlePruneL2Blocks.bind(this);
|
|
30
32
|
this.penalties = pick(penalties, ...EpochPruneWatcherPenaltiesConfigKeys);
|
|
31
33
|
this.log.verbose(`EpochPruneWatcher initialized with penalties: valid epoch pruned=${penalties.slashPrunePenalty} data withholding=${penalties.slashDataWithholdingPenalty}`);
|
|
32
34
|
}
|
|
33
35
|
start() {
|
|
34
|
-
this.l2BlockSource.on(L2BlockSourceEvents.
|
|
36
|
+
this.l2BlockSource.events.on(L2BlockSourceEvents.L2PruneUnproven, this.boundHandlePruneL2Blocks);
|
|
35
37
|
return Promise.resolve();
|
|
36
38
|
}
|
|
37
39
|
stop() {
|
|
38
|
-
this.l2BlockSource.removeListener(L2BlockSourceEvents.
|
|
40
|
+
this.l2BlockSource.events.removeListener(L2BlockSourceEvents.L2PruneUnproven, this.boundHandlePruneL2Blocks);
|
|
39
41
|
return Promise.resolve();
|
|
40
42
|
}
|
|
41
43
|
updateConfig(config) {
|
|
@@ -61,7 +63,7 @@ const EpochPruneWatcherPenaltiesConfigKeys = [
|
|
|
61
63
|
async processPruneL2Blocks(blocks, epochNumber) {
|
|
62
64
|
try {
|
|
63
65
|
const l1Constants = this.epochCache.getL1Constants();
|
|
64
|
-
const epochBlocks = blocks.filter((b)=>getEpochAtSlot(b.
|
|
66
|
+
const epochBlocks = blocks.filter((b)=>getEpochAtSlot(b.header.getSlot(), l1Constants) === epochNumber);
|
|
65
67
|
this.log.info(`Detected chain prune. Validating epoch ${epochNumber} with blocks ${epochBlocks[0]?.number} to ${epochBlocks[epochBlocks.length - 1]?.number}.`, {
|
|
66
68
|
blocks: epochBlocks.map((b)=>b.toBlockInfo())
|
|
67
69
|
});
|
|
@@ -83,16 +85,25 @@ const EpochPruneWatcherPenaltiesConfigKeys = [
|
|
|
83
85
|
if (blocks.length === 0) {
|
|
84
86
|
return;
|
|
85
87
|
}
|
|
86
|
-
|
|
88
|
+
let previousCheckpointOutHashes = [];
|
|
89
|
+
const fork = await this.checkpointsBuilder.getFork(BlockNumber(blocks[0].header.globalVariables.blockNumber - 1));
|
|
87
90
|
try {
|
|
88
91
|
for (const block of blocks){
|
|
89
|
-
await this.validateBlock(block, fork);
|
|
92
|
+
await this.validateBlock(block, previousCheckpointOutHashes, fork);
|
|
93
|
+
// TODO(mbps): This assumes one block per checkpoint, which is only true for now.
|
|
94
|
+
const checkpointOutHash = computeCheckpointOutHash([
|
|
95
|
+
block.body.txEffects.map((tx)=>tx.l2ToL1Msgs)
|
|
96
|
+
]);
|
|
97
|
+
previousCheckpointOutHashes = [
|
|
98
|
+
...previousCheckpointOutHashes,
|
|
99
|
+
checkpointOutHash
|
|
100
|
+
];
|
|
90
101
|
}
|
|
91
102
|
} finally{
|
|
92
103
|
await fork.close();
|
|
93
104
|
}
|
|
94
105
|
}
|
|
95
|
-
async validateBlock(blockFromL1, fork) {
|
|
106
|
+
async validateBlock(blockFromL1, previousCheckpointOutHashes, fork) {
|
|
96
107
|
this.log.debug(`Validating pruned block ${blockFromL1.header.globalVariables.blockNumber}`);
|
|
97
108
|
const txHashes = blockFromL1.body.txEffects.map((txEffect)=>txEffect.txHash);
|
|
98
109
|
// We load txs from the mempool directly, since the TxCollector running in the background has already been
|
|
@@ -102,8 +113,20 @@ const EpochPruneWatcherPenaltiesConfigKeys = [
|
|
|
102
113
|
if (missingTxs && missingTxs.length > 0) {
|
|
103
114
|
throw new TransactionsNotAvailableError(missingTxs);
|
|
104
115
|
}
|
|
105
|
-
const
|
|
106
|
-
const
|
|
116
|
+
const checkpointNumber = CheckpointNumber.fromBlockNumber(blockFromL1.number);
|
|
117
|
+
const l1ToL2Messages = await this.l1ToL2MessageSource.getL1ToL2Messages(checkpointNumber);
|
|
118
|
+
const gv = blockFromL1.header.globalVariables;
|
|
119
|
+
const constants = {
|
|
120
|
+
chainId: gv.chainId,
|
|
121
|
+
version: gv.version,
|
|
122
|
+
slotNumber: gv.slotNumber,
|
|
123
|
+
coinbase: gv.coinbase,
|
|
124
|
+
feeRecipient: gv.feeRecipient,
|
|
125
|
+
gasFees: gv.gasFees
|
|
126
|
+
};
|
|
127
|
+
// Use checkpoint builder to validate the block
|
|
128
|
+
const checkpointBuilder = await this.checkpointsBuilder.startCheckpoint(checkpointNumber, constants, l1ToL2Messages, previousCheckpointOutHashes, fork, this.log.getBindings());
|
|
129
|
+
const { block, failedTxs, numTxs } = await checkpointBuilder.buildBlock(txs, gv.blockNumber, gv.timestamp, {});
|
|
107
130
|
if (numTxs !== txs.length) {
|
|
108
131
|
// This should be detected by state mismatch, but this makes it easier to debug.
|
|
109
132
|
throw new ValidatorError(`Built block with ${numTxs} txs, expected ${txs.length}`);
|
package/package.json
CHANGED
|
@@ -1,20 +1,22 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aztec/slasher",
|
|
3
|
-
"version": "0.0.1-commit.
|
|
3
|
+
"version": "0.0.1-commit.9d2bcf6d",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"exports": {
|
|
6
6
|
".": "./dest/index.js",
|
|
7
7
|
"./config": "./dest/config.js"
|
|
8
8
|
},
|
|
9
9
|
"inherits": [
|
|
10
|
-
"../package.common.json"
|
|
10
|
+
"../package.common.json",
|
|
11
|
+
"./package.local.json"
|
|
11
12
|
],
|
|
12
13
|
"scripts": {
|
|
13
|
-
"build": "yarn clean &&
|
|
14
|
-
"build:dev": "
|
|
14
|
+
"build": "yarn clean && ../scripts/tsc.sh",
|
|
15
|
+
"build:dev": "../scripts/tsc.sh --watch",
|
|
15
16
|
"clean": "rm -rf ./dest .tsbuildinfo",
|
|
16
17
|
"bb": "node --no-warnings ./dest/bb/index.js",
|
|
17
|
-
"test": "NODE_NO_WARNINGS=1 node --experimental-vm-modules ../node_modules/.bin/jest --passWithNoTests --maxWorkers=${JEST_MAX_WORKERS:-8}"
|
|
18
|
+
"test": "NODE_NO_WARNINGS=1 node --experimental-vm-modules ../node_modules/.bin/jest --passWithNoTests --maxWorkers=${JEST_MAX_WORKERS:-8}",
|
|
19
|
+
"generate": "./scripts/generate.sh"
|
|
18
20
|
},
|
|
19
21
|
"jest": {
|
|
20
22
|
"moduleNameMapper": {
|
|
@@ -54,25 +56,25 @@
|
|
|
54
56
|
]
|
|
55
57
|
},
|
|
56
58
|
"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.
|
|
59
|
+
"@aztec/epoch-cache": "0.0.1-commit.9d2bcf6d",
|
|
60
|
+
"@aztec/ethereum": "0.0.1-commit.9d2bcf6d",
|
|
61
|
+
"@aztec/foundation": "0.0.1-commit.9d2bcf6d",
|
|
62
|
+
"@aztec/kv-store": "0.0.1-commit.9d2bcf6d",
|
|
63
|
+
"@aztec/l1-artifacts": "0.0.1-commit.9d2bcf6d",
|
|
64
|
+
"@aztec/stdlib": "0.0.1-commit.9d2bcf6d",
|
|
65
|
+
"@aztec/telemetry-client": "0.0.1-commit.9d2bcf6d",
|
|
64
66
|
"source-map-support": "^0.5.21",
|
|
65
67
|
"tslib": "^2.4.0",
|
|
66
68
|
"viem": "npm:@aztec/viem@2.38.2",
|
|
67
69
|
"zod": "^3.23.8"
|
|
68
70
|
},
|
|
69
71
|
"devDependencies": {
|
|
70
|
-
"@aztec/aztec.js": "0.0.1-commit.
|
|
72
|
+
"@aztec/aztec.js": "0.0.1-commit.9d2bcf6d",
|
|
71
73
|
"@jest/globals": "^30.0.0",
|
|
72
74
|
"@types/jest": "^30.0.0",
|
|
73
75
|
"@types/node": "^22.15.17",
|
|
74
76
|
"@types/source-map-support": "^0.5.10",
|
|
75
|
-
"@typescript/native-preview": "7.0.0-dev.
|
|
77
|
+
"@typescript/native-preview": "7.0.0-dev.20260113.1",
|
|
76
78
|
"jest": "^30.0.0",
|
|
77
79
|
"jest-mock-extended": "^4.0.0",
|
|
78
80
|
"ts-node": "^10.9.1",
|
package/src/config.ts
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { DefaultL1ContractsConfig } from '@aztec/ethereum';
|
|
2
1
|
import type { ConfigMappingsType } from '@aztec/foundation/config';
|
|
3
2
|
import {
|
|
4
3
|
bigintConfigHelper,
|
|
@@ -9,27 +8,29 @@ import {
|
|
|
9
8
|
import { EthAddress } from '@aztec/foundation/eth-address';
|
|
10
9
|
import type { SlasherConfig } from '@aztec/stdlib/interfaces/server';
|
|
11
10
|
|
|
11
|
+
import { slasherDefaultEnv } from './generated/slasher-defaults.js';
|
|
12
|
+
|
|
12
13
|
export type { SlasherConfig };
|
|
13
14
|
|
|
14
15
|
export const DefaultSlasherConfig: SlasherConfig = {
|
|
15
16
|
slashOverridePayload: undefined,
|
|
16
|
-
slashMinPenaltyPercentage:
|
|
17
|
-
slashMaxPenaltyPercentage:
|
|
17
|
+
slashMinPenaltyPercentage: slasherDefaultEnv.SLASH_MIN_PENALTY_PERCENTAGE,
|
|
18
|
+
slashMaxPenaltyPercentage: slasherDefaultEnv.SLASH_MAX_PENALTY_PERCENTAGE,
|
|
18
19
|
slashValidatorsAlways: [], // Empty by default
|
|
19
20
|
slashValidatorsNever: [], // Empty by default
|
|
20
|
-
slashPrunePenalty:
|
|
21
|
-
slashDataWithholdingPenalty:
|
|
22
|
-
slashInactivityTargetPercentage:
|
|
23
|
-
slashInactivityConsecutiveEpochThreshold:
|
|
24
|
-
slashBroadcastedInvalidBlockPenalty:
|
|
25
|
-
slashInactivityPenalty:
|
|
26
|
-
slashProposeInvalidAttestationsPenalty:
|
|
27
|
-
slashAttestDescendantOfInvalidPenalty:
|
|
28
|
-
slashUnknownPenalty:
|
|
29
|
-
slashOffenseExpirationRounds:
|
|
30
|
-
slashMaxPayloadSize:
|
|
31
|
-
slashGracePeriodL2Slots:
|
|
32
|
-
slashExecuteRoundsLookBack:
|
|
21
|
+
slashPrunePenalty: BigInt(slasherDefaultEnv.SLASH_PRUNE_PENALTY),
|
|
22
|
+
slashDataWithholdingPenalty: BigInt(slasherDefaultEnv.SLASH_DATA_WITHHOLDING_PENALTY),
|
|
23
|
+
slashInactivityTargetPercentage: slasherDefaultEnv.SLASH_INACTIVITY_TARGET_PERCENTAGE,
|
|
24
|
+
slashInactivityConsecutiveEpochThreshold: slasherDefaultEnv.SLASH_INACTIVITY_CONSECUTIVE_EPOCH_THRESHOLD,
|
|
25
|
+
slashBroadcastedInvalidBlockPenalty: BigInt(slasherDefaultEnv.SLASH_INVALID_BLOCK_PENALTY),
|
|
26
|
+
slashInactivityPenalty: BigInt(slasherDefaultEnv.SLASH_INACTIVITY_PENALTY),
|
|
27
|
+
slashProposeInvalidAttestationsPenalty: BigInt(slasherDefaultEnv.SLASH_PROPOSE_INVALID_ATTESTATIONS_PENALTY),
|
|
28
|
+
slashAttestDescendantOfInvalidPenalty: BigInt(slasherDefaultEnv.SLASH_ATTEST_DESCENDANT_OF_INVALID_PENALTY),
|
|
29
|
+
slashUnknownPenalty: BigInt(slasherDefaultEnv.SLASH_UNKNOWN_PENALTY),
|
|
30
|
+
slashOffenseExpirationRounds: slasherDefaultEnv.SLASH_OFFENSE_EXPIRATION_ROUNDS,
|
|
31
|
+
slashMaxPayloadSize: slasherDefaultEnv.SLASH_MAX_PAYLOAD_SIZE,
|
|
32
|
+
slashGracePeriodL2Slots: slasherDefaultEnv.SLASH_GRACE_PERIOD_L2_SLOTS,
|
|
33
|
+
slashExecuteRoundsLookBack: slasherDefaultEnv.SLASH_EXECUTE_ROUNDS_LOOK_BACK,
|
|
33
34
|
slashSelfAllowed: false,
|
|
34
35
|
};
|
|
35
36
|
|
|
@@ -1,10 +1,9 @@
|
|
|
1
|
-
import { EmpireSlashingProposerContract, RollupContract, SlasherContract } from '@aztec/ethereum';
|
|
1
|
+
import { EmpireSlashingProposerContract, RollupContract, SlasherContract } from '@aztec/ethereum/contracts';
|
|
2
2
|
import { sumBigint } from '@aztec/foundation/bigint';
|
|
3
3
|
import { SlotNumber } from '@aztec/foundation/branded-types';
|
|
4
4
|
import { compactArray, filterAsync, maxBy, pick } from '@aztec/foundation/collection';
|
|
5
5
|
import { EthAddress } from '@aztec/foundation/eth-address';
|
|
6
6
|
import { createLogger } from '@aztec/foundation/log';
|
|
7
|
-
import { sleep } from '@aztec/foundation/sleep';
|
|
8
7
|
import type { DateProvider } from '@aztec/foundation/timer';
|
|
9
8
|
import type { L1RollupConstants } from '@aztec/stdlib/epoch-helpers';
|
|
10
9
|
import type { SlasherConfig } from '@aztec/stdlib/interfaces/server';
|
|
@@ -196,13 +195,6 @@ export class EmpireSlasherClient implements ProposerSlashActionProvider, Slasher
|
|
|
196
195
|
this.roundMonitor.stop();
|
|
197
196
|
await this.offensesCollector.stop();
|
|
198
197
|
|
|
199
|
-
// Viem calls eth_uninstallFilter under the hood when uninstalling event watchers, but these calls are not awaited,
|
|
200
|
-
// meaning that any error that happens during the uninstallation will not be caught. This causes errors during jest teardowns,
|
|
201
|
-
// where we stop anvil after all other processes are stopped, so sometimes the eth_uninstallFilter call fails because anvil
|
|
202
|
-
// is already stopped. We add a sleep here to give the uninstallation some time to complete, but the proper fix is for
|
|
203
|
-
// viem to await the eth_uninstallFilter calls, or to catch any errors that happen during the uninstallation.
|
|
204
|
-
// See https://github.com/wevm/viem/issues/3714.
|
|
205
|
-
await sleep(2000);
|
|
206
198
|
this.log.info('Empire Slasher client stopped');
|
|
207
199
|
}
|
|
208
200
|
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { EpochCache } from '@aztec/epoch-cache';
|
|
2
|
-
import type { L1ReaderConfig, ViemClient } from '@aztec/ethereum';
|
|
3
2
|
import { RollupContract } from '@aztec/ethereum/contracts';
|
|
3
|
+
import type { L1ReaderConfig } from '@aztec/ethereum/l1-reader';
|
|
4
|
+
import type { ViemClient } from '@aztec/ethereum/types';
|
|
4
5
|
import { unique } from '@aztec/foundation/collection';
|
|
5
6
|
import { EthAddress } from '@aztec/foundation/eth-address';
|
|
6
7
|
import { createLogger } from '@aztec/foundation/log';
|
|
@@ -30,7 +31,7 @@ export async function createSlasherFacade(
|
|
|
30
31
|
throw new Error('Cannot initialize SlasherClient without a Rollup address');
|
|
31
32
|
}
|
|
32
33
|
|
|
33
|
-
const kvStore = await createStore('slasher', SCHEMA_VERSION, config,
|
|
34
|
+
const kvStore = await createStore('slasher', SCHEMA_VERSION, config, logger.getBindings());
|
|
34
35
|
const rollup = new RollupContract(l1Client, l1Contracts.rollupAddress);
|
|
35
36
|
|
|
36
37
|
const slashValidatorsNever = config.slashSelfAllowed
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { EpochCache } from '@aztec/epoch-cache';
|
|
2
|
-
import type { ViemClient } from '@aztec/ethereum';
|
|
3
2
|
import {
|
|
4
3
|
EmpireSlashingProposerContract,
|
|
5
4
|
RollupContract,
|
|
6
5
|
TallySlashingProposerContract,
|
|
7
6
|
} from '@aztec/ethereum/contracts';
|
|
7
|
+
import type { ViemClient } from '@aztec/ethereum/types';
|
|
8
8
|
import { EthAddress } from '@aztec/foundation/eth-address';
|
|
9
9
|
import { createLogger } from '@aztec/foundation/log';
|
|
10
10
|
import { DateProvider } from '@aztec/foundation/timer';
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
// Auto-generated from spartan/environments/network-defaults.yml
|
|
2
|
+
// Do not edit manually - run yarn generate to regenerate
|
|
3
|
+
|
|
4
|
+
/** Default slasher configuration values from network-defaults.yml */
|
|
5
|
+
export const slasherDefaultEnv = {
|
|
6
|
+
SLASH_MIN_PENALTY_PERCENTAGE: 0.5,
|
|
7
|
+
SLASH_MAX_PENALTY_PERCENTAGE: 2,
|
|
8
|
+
SLASH_OFFENSE_EXPIRATION_ROUNDS: 4,
|
|
9
|
+
SLASH_MAX_PAYLOAD_SIZE: 50,
|
|
10
|
+
SLASH_EXECUTE_ROUNDS_LOOK_BACK: 4,
|
|
11
|
+
SLASH_PRUNE_PENALTY: 10000000000000000000,
|
|
12
|
+
SLASH_DATA_WITHHOLDING_PENALTY: 10000000000000000000,
|
|
13
|
+
SLASH_INACTIVITY_TARGET_PERCENTAGE: 0.9,
|
|
14
|
+
SLASH_INACTIVITY_CONSECUTIVE_EPOCH_THRESHOLD: 1,
|
|
15
|
+
SLASH_INACTIVITY_PENALTY: 10000000000000000000,
|
|
16
|
+
SLASH_PROPOSE_INVALID_ATTESTATIONS_PENALTY: 10000000000000000000,
|
|
17
|
+
SLASH_ATTEST_DESCENDANT_OF_INVALID_PENALTY: 10000000000000000000,
|
|
18
|
+
SLASH_UNKNOWN_PENALTY: 10000000000000000000,
|
|
19
|
+
SLASH_INVALID_BLOCK_PENALTY: 10000000000000000000,
|
|
20
|
+
SLASH_GRACE_PERIOD_L2_SLOTS: 0,
|
|
21
|
+
} as const;
|
|
@@ -85,7 +85,11 @@ export class SlashOffensesCollector {
|
|
|
85
85
|
}
|
|
86
86
|
}
|
|
87
87
|
|
|
88
|
-
this.log.info(`Adding pending offense for validator ${arg.validator}`,
|
|
88
|
+
this.log.info(`Adding pending offense for validator ${arg.validator}`, {
|
|
89
|
+
...pendingOffense,
|
|
90
|
+
epochOrSlot: pendingOffense.epochOrSlot.toString(),
|
|
91
|
+
amount: pendingOffense.amount.toString(),
|
|
92
|
+
});
|
|
89
93
|
await this.offensesStore.addPendingOffense(pendingOffense);
|
|
90
94
|
}
|
|
91
95
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { EpochCache } from '@aztec/epoch-cache';
|
|
2
|
-
import type { ViemClient } from '@aztec/ethereum';
|
|
3
2
|
import { RollupContract } from '@aztec/ethereum/contracts';
|
|
3
|
+
import type { ViemClient } from '@aztec/ethereum/types';
|
|
4
4
|
import type { SlotNumber } from '@aztec/foundation/branded-types';
|
|
5
5
|
import { EthAddress } from '@aztec/foundation/eth-address';
|
|
6
6
|
import { createLogger } from '@aztec/foundation/log';
|
|
@@ -76,9 +76,11 @@ export class SlasherOffensesStore {
|
|
|
76
76
|
/** Adds a new offense (defaults to pending, but will be slashed if markAsSlashed had been called for it) */
|
|
77
77
|
public async addPendingOffense(offense: Offense): Promise<void> {
|
|
78
78
|
const key = this.getOffenseKey(offense);
|
|
79
|
-
await this.offenses.set(key, serializeOffense(offense));
|
|
80
79
|
const round = getRoundForOffense(offense, this.settings);
|
|
81
|
-
await this.
|
|
80
|
+
await this.kvStore.transactionAsync(async () => {
|
|
81
|
+
await this.offenses.set(key, serializeOffense(offense));
|
|
82
|
+
await this.roundsOffenses.set(this.getRoundKey(round), key);
|
|
83
|
+
});
|
|
82
84
|
this.log.trace(`Adding pending offense ${key} for round ${round}`);
|
|
83
85
|
}
|
|
84
86
|
|
|
@@ -118,10 +118,13 @@ export class SlasherPayloadsStore {
|
|
|
118
118
|
|
|
119
119
|
public async incrementPayloadVotes(payloadAddress: EthAddress, round: bigint): Promise<bigint> {
|
|
120
120
|
const key = this.getPayloadVotesKey(round, payloadAddress);
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
121
|
+
let newVotes: bigint;
|
|
122
|
+
await this.kvStore.transactionAsync(async () => {
|
|
123
|
+
const currentVotes = (await this.roundPayloadVotes.getAsync(key)) || 0n;
|
|
124
|
+
newVotes = currentVotes + 1n;
|
|
125
|
+
await this.roundPayloadVotes.set(key, newVotes);
|
|
126
|
+
});
|
|
127
|
+
return newVotes!;
|
|
125
128
|
}
|
|
126
129
|
|
|
127
130
|
public async addPayload(payload: SlashPayloadRound): Promise<void> {
|
|
@@ -5,7 +5,6 @@ import { maxBigint } from '@aztec/foundation/bigint';
|
|
|
5
5
|
import { SlotNumber } from '@aztec/foundation/branded-types';
|
|
6
6
|
import { compactArray, partition, times } from '@aztec/foundation/collection';
|
|
7
7
|
import { createLogger } from '@aztec/foundation/log';
|
|
8
|
-
import { sleep } from '@aztec/foundation/sleep';
|
|
9
8
|
import type { DateProvider } from '@aztec/foundation/timer';
|
|
10
9
|
import type { Prettify } from '@aztec/foundation/types';
|
|
11
10
|
import type { SlasherConfig } from '@aztec/stdlib/interfaces/server';
|
|
@@ -138,8 +137,6 @@ export class TallySlasherClient implements ProposerSlashActionProvider, SlasherC
|
|
|
138
137
|
this.roundMonitor.stop();
|
|
139
138
|
await this.offensesCollector.stop();
|
|
140
139
|
|
|
141
|
-
// Sleeping to sidestep viem issue with unwatching events
|
|
142
|
-
await sleep(2000);
|
|
143
140
|
this.log.info('Tally Slasher client stopped');
|
|
144
141
|
}
|
|
145
142
|
|
|
@@ -281,8 +278,12 @@ export class TallySlasherClient implements ProposerSlashActionProvider, SlasherC
|
|
|
281
278
|
return undefined;
|
|
282
279
|
}
|
|
283
280
|
|
|
281
|
+
const slashActionsWithAmounts = slashActions.map(action => ({
|
|
282
|
+
validator: action.validator.toString(),
|
|
283
|
+
slashAmount: action.slashAmount.toString(),
|
|
284
|
+
}));
|
|
284
285
|
this.log.info(`Round ${executableRound} is ready to execute with ${slashActions.length} slashes`, {
|
|
285
|
-
slashActions,
|
|
286
|
+
slashActions: slashActionsWithAmounts,
|
|
286
287
|
payloadAddress: payload.address.toString(),
|
|
287
288
|
...logData,
|
|
288
289
|
});
|
|
@@ -348,11 +349,15 @@ export class TallySlasherClient implements ProposerSlashActionProvider, SlasherC
|
|
|
348
349
|
return undefined;
|
|
349
350
|
}
|
|
350
351
|
|
|
352
|
+
const offensesToSlashLog = offensesToSlash.map(offense => ({
|
|
353
|
+
...offense,
|
|
354
|
+
amount: offense.amount.toString(),
|
|
355
|
+
}));
|
|
351
356
|
this.log.info(`Voting to slash ${offensesToSlash.length} offenses`, {
|
|
352
357
|
slotNumber,
|
|
353
358
|
currentRound,
|
|
354
359
|
slashedRound,
|
|
355
|
-
offensesToSlash,
|
|
360
|
+
offensesToSlash: offensesToSlashLog,
|
|
356
361
|
});
|
|
357
362
|
|
|
358
363
|
const committees = await this.collectCommitteesActiveDuringRound(slashedRound);
|
package/src/watcher.ts
CHANGED
|
@@ -10,7 +10,7 @@ export interface WantToSlashArgs {
|
|
|
10
10
|
validator: EthAddress;
|
|
11
11
|
amount: bigint;
|
|
12
12
|
offenseType: OffenseType;
|
|
13
|
-
epochOrSlot: bigint; // Epoch number for epoch-based offenses,
|
|
13
|
+
epochOrSlot: bigint; // Epoch number for epoch-based offenses, slot number for slot-based
|
|
14
14
|
}
|
|
15
15
|
|
|
16
16
|
// Event map for specific, known events of a watcher
|