@aztec/slasher 1.2.0 → 2.0.0-nightly.20250813
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/attestations_block_watcher.d.ts +34 -0
- package/dest/attestations_block_watcher.d.ts.map +1 -0
- package/dest/attestations_block_watcher.js +170 -0
- package/dest/config.d.ts +3 -25
- package/dest/config.d.ts.map +1 -1
- package/dest/config.js +32 -33
- package/dest/epoch_prune_watcher.d.ts +4 -3
- package/dest/epoch_prune_watcher.d.ts.map +1 -1
- package/dest/epoch_prune_watcher.js +15 -9
- package/dest/index.d.ts +2 -0
- package/dest/index.d.ts.map +1 -1
- package/dest/index.js +2 -0
- package/dest/slasher_client.d.ts +17 -16
- package/dest/slasher_client.d.ts.map +1 -1
- package/dest/slasher_client.js +66 -35
- package/package.json +8 -8
- package/src/attestations_block_watcher.ts +219 -0
- package/src/config.ts +35 -51
- package/src/epoch_prune_watcher.ts +19 -12
- package/src/index.ts +2 -0
- package/src/slasher_client.ts +93 -48
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { EpochCache } from '@aztec/epoch-cache';
|
|
2
|
+
import { type L2BlockSourceEventEmitter } from '@aztec/stdlib/block';
|
|
3
|
+
import { type SlasherConfig, type WantToSlashArgs, type Watcher, type WatcherEmitter } from './config.js';
|
|
4
|
+
declare const AttestationsBlockWatcher_base: new () => WatcherEmitter;
|
|
5
|
+
/**
|
|
6
|
+
* This watcher is responsible for detecting invalid blocks and creating slashing arguments for offenders.
|
|
7
|
+
* An invalid block is one that doesn't have enough attestations or has incorrect attestations.
|
|
8
|
+
* The proposer of an invalid block should be slashed.
|
|
9
|
+
* If there's another block consecutive to the invalid one, its proposer and attestors should also be slashed.
|
|
10
|
+
*/
|
|
11
|
+
export declare class AttestationsBlockWatcher extends AttestationsBlockWatcher_base implements Watcher {
|
|
12
|
+
private l2BlockSource;
|
|
13
|
+
private epochCache;
|
|
14
|
+
private config;
|
|
15
|
+
private log;
|
|
16
|
+
private maxInvalidBlocks;
|
|
17
|
+
private invalidArchiveRoots;
|
|
18
|
+
private badAttestors;
|
|
19
|
+
private badProposers;
|
|
20
|
+
private boundHandleInvalidBlock;
|
|
21
|
+
constructor(l2BlockSource: L2BlockSourceEventEmitter, epochCache: EpochCache, config: Pick<SlasherConfig, 'slashAttestDescendantOfInvalidPenalty' | 'slashAttestDescendantOfInvalidMaxPenalty' | 'slashProposeInvalidAttestationsPenalty' | 'slashProposeInvalidAttestationsMaxPenalty'>);
|
|
22
|
+
start(): Promise<void>;
|
|
23
|
+
stop(): Promise<void>;
|
|
24
|
+
shouldSlash({ amount, offense, validator }: WantToSlashArgs): Promise<boolean>;
|
|
25
|
+
private handleInvalidBlock;
|
|
26
|
+
private slashAttestorsOnAncestorInvalid;
|
|
27
|
+
private slashProposer;
|
|
28
|
+
private getOffenseFromInvalidationReason;
|
|
29
|
+
private getMaxPenalty;
|
|
30
|
+
private hasOffended;
|
|
31
|
+
private addInvalidBlock;
|
|
32
|
+
}
|
|
33
|
+
export {};
|
|
34
|
+
//# sourceMappingURL=attestations_block_watcher.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"attestations_block_watcher.d.ts","sourceRoot":"","sources":["../src/attestations_block_watcher.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAC;AAEhD,OAAO,EAGL,KAAK,yBAAyB,EAI/B,MAAM,qBAAqB,CAAC;AAK7B,OAAO,EACL,KAAK,aAAa,EAElB,KAAK,eAAe,EACpB,KAAK,OAAO,EACZ,KAAK,cAAc,EACpB,MAAM,aAAa,CAAC;6CAQ0C,UAAU,cAAc;AANvF;;;;;GAKG;AACH,qBAAa,wBAAyB,SAAQ,6BAA2C,YAAW,OAAO;IA0BvG,OAAO,CAAC,aAAa;IACrB,OAAO,CAAC,UAAU;IAClB,OAAO,CAAC,MAAM;IA3BhB,OAAO,CAAC,GAAG,CAAsD;IAGjE,OAAO,CAAC,gBAAgB,CAAO;IAG/B,OAAO,CAAC,mBAAmB,CAA0B;IAGrD,OAAO,CAAC,YAAY,CAA0B;IAC9C,OAAO,CAAC,YAAY,CAA0B;IAE9C,OAAO,CAAC,uBAAuB,CAU7B;gBAGQ,aAAa,EAAE,yBAAyB,EACxC,UAAU,EAAE,UAAU,EACtB,MAAM,EAAE,IAAI,CAClB,aAAa,EACX,uCAAuC,GACvC,0CAA0C,GAC1C,wCAAwC,GACxC,2CAA2C,CAC9C;IAMI,KAAK;IAKL,IAAI;IAQJ,WAAW,CAAC,EAAE,MAAM,EAAE,OAAO,EAAE,SAAS,EAAE,EAAE,eAAe,GAAG,OAAO,CAAC,OAAO,CAAC;IAiBrF,OAAO,CAAC,kBAAkB;IAyB1B,OAAO,CAAC,+BAA+B;IAyBvC,OAAO,CAAC,aAAa;IAwBrB,OAAO,CAAC,gCAAgC;IAaxC,OAAO,CAAC,aAAa;IAYrB,OAAO,CAAC,WAAW;IAYnB,OAAO,CAAC,eAAe;CASxB"}
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
import { createLogger } from '@aztec/foundation/log';
|
|
2
|
+
import { L2BlockSourceEvents } from '@aztec/stdlib/block';
|
|
3
|
+
import { Offense } from '@aztec/stdlib/slashing';
|
|
4
|
+
import EventEmitter from 'node:events';
|
|
5
|
+
import { WANT_TO_SLASH_EVENT } from './config.js';
|
|
6
|
+
/**
|
|
7
|
+
* This watcher is responsible for detecting invalid blocks and creating slashing arguments for offenders.
|
|
8
|
+
* An invalid block is one that doesn't have enough attestations or has incorrect attestations.
|
|
9
|
+
* The proposer of an invalid block should be slashed.
|
|
10
|
+
* If there's another block consecutive to the invalid one, its proposer and attestors should also be slashed.
|
|
11
|
+
*/ export class AttestationsBlockWatcher extends EventEmitter {
|
|
12
|
+
l2BlockSource;
|
|
13
|
+
epochCache;
|
|
14
|
+
config;
|
|
15
|
+
log;
|
|
16
|
+
// Only keep track of the last N invalid blocks
|
|
17
|
+
maxInvalidBlocks;
|
|
18
|
+
// All invalid archive roots seen
|
|
19
|
+
invalidArchiveRoots;
|
|
20
|
+
// TODO(#16140): Bad validators are never cleared even after slashing
|
|
21
|
+
badAttestors;
|
|
22
|
+
badProposers;
|
|
23
|
+
boundHandleInvalidBlock;
|
|
24
|
+
constructor(l2BlockSource, epochCache, config){
|
|
25
|
+
super(), this.l2BlockSource = l2BlockSource, this.epochCache = epochCache, this.config = config, this.log = createLogger('attestations-block-watcher'), this.maxInvalidBlocks = 100, this.invalidArchiveRoots = new Set(), this.badAttestors = new Set(), this.badProposers = new Set(), this.boundHandleInvalidBlock = (event)=>{
|
|
26
|
+
try {
|
|
27
|
+
this.handleInvalidBlock(event);
|
|
28
|
+
} catch (err) {
|
|
29
|
+
this.log.error('Error handling invalid block', err, {
|
|
30
|
+
...event.validationResult.block.block.toBlockInfo(),
|
|
31
|
+
...event.validationResult.block.l1,
|
|
32
|
+
reason: event.validationResult.reason
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
};
|
|
36
|
+
this.log.info('InvalidBlockWatcher initialized');
|
|
37
|
+
}
|
|
38
|
+
start() {
|
|
39
|
+
this.l2BlockSource.on(L2BlockSourceEvents.InvalidAttestationsBlockDetected, this.boundHandleInvalidBlock);
|
|
40
|
+
return Promise.resolve();
|
|
41
|
+
}
|
|
42
|
+
stop() {
|
|
43
|
+
this.l2BlockSource.removeListener(L2BlockSourceEvents.InvalidAttestationsBlockDetected, this.boundHandleInvalidBlock);
|
|
44
|
+
return Promise.resolve();
|
|
45
|
+
}
|
|
46
|
+
shouldSlash({ amount, offense, validator }) {
|
|
47
|
+
const maxPenalty = this.getMaxPenalty(offense);
|
|
48
|
+
const logData = {
|
|
49
|
+
validator,
|
|
50
|
+
amount,
|
|
51
|
+
offense,
|
|
52
|
+
maxPenalty
|
|
53
|
+
};
|
|
54
|
+
if (amount > maxPenalty) {
|
|
55
|
+
this.log.warn(`Slash amount ${amount} exceeds maximum penalty ${maxPenalty} for offense ${offense}`, logData);
|
|
56
|
+
return Promise.resolve(false);
|
|
57
|
+
}
|
|
58
|
+
if (this.hasOffended(offense, validator)) {
|
|
59
|
+
this.log.verbose(`Agreeing to slash validator ${validator} for offense ${offense}`, logData);
|
|
60
|
+
return Promise.resolve(true);
|
|
61
|
+
}
|
|
62
|
+
this.log.debug(`Refusing to slash validator ${validator} for offense ${offense}`, logData);
|
|
63
|
+
return Promise.resolve(false);
|
|
64
|
+
}
|
|
65
|
+
handleInvalidBlock(event) {
|
|
66
|
+
const { validationResult } = event;
|
|
67
|
+
const block = validationResult.block.block;
|
|
68
|
+
// Check if we already have processed this block, archiver may emit the same event multiple times
|
|
69
|
+
if (this.invalidArchiveRoots.has(block.archive.root.toString())) {
|
|
70
|
+
this.log.trace(`Already processed invalid block ${block.number}`);
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
this.log.verbose(`Detected invalid block ${block.number}`, {
|
|
74
|
+
...block.toBlockInfo(),
|
|
75
|
+
reason: validationResult.valid === false ? validationResult.reason : 'unknown'
|
|
76
|
+
});
|
|
77
|
+
// Store the invalid block
|
|
78
|
+
this.addInvalidBlock(event.validationResult.block);
|
|
79
|
+
// Slash the proposer of the invalid block
|
|
80
|
+
this.slashProposer(event.validationResult);
|
|
81
|
+
// Check if the parent of this block is invalid as well, if so, we will slash its attestors as well
|
|
82
|
+
this.slashAttestorsOnAncestorInvalid(event.validationResult);
|
|
83
|
+
}
|
|
84
|
+
slashAttestorsOnAncestorInvalid(validationResult) {
|
|
85
|
+
const block = validationResult.block;
|
|
86
|
+
const parentArchive = block.block.header.lastArchive.root.toString();
|
|
87
|
+
if (this.invalidArchiveRoots.has(block.block.header.lastArchive.root.toString())) {
|
|
88
|
+
const attestors = validationResult.attestations.map((a)=>a.getSender());
|
|
89
|
+
this.log.info(`Want to slash attestors of block ${block.block.number} built on invalid block`, {
|
|
90
|
+
...block.block.toBlockInfo(),
|
|
91
|
+
...attestors,
|
|
92
|
+
parentArchive
|
|
93
|
+
});
|
|
94
|
+
attestors.forEach((attestor)=>this.badAttestors.add(attestor.toString()));
|
|
95
|
+
this.emit(WANT_TO_SLASH_EVENT, attestors.map((attestor)=>({
|
|
96
|
+
validator: attestor,
|
|
97
|
+
amount: this.config.slashAttestDescendantOfInvalidPenalty,
|
|
98
|
+
offense: Offense.ATTESTED_DESCENDANT_OF_INVALID
|
|
99
|
+
})));
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
slashProposer(validationResult) {
|
|
103
|
+
const { reason, block } = validationResult;
|
|
104
|
+
const blockNumber = block.block.number;
|
|
105
|
+
const slot = block.block.header.getSlot();
|
|
106
|
+
const proposer = this.epochCache.getProposerFromEpochCommittee(validationResult, slot);
|
|
107
|
+
if (!proposer) {
|
|
108
|
+
this.log.warn(`No proposer found for block ${blockNumber} at slot ${slot}`);
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
const offense = this.getOffenseFromInvalidationReason(reason);
|
|
112
|
+
const amount = this.config.slashProposeInvalidAttestationsPenalty;
|
|
113
|
+
const args = {
|
|
114
|
+
validator: proposer,
|
|
115
|
+
amount,
|
|
116
|
+
offense
|
|
117
|
+
};
|
|
118
|
+
this.log.info(`Want to slash proposer of block ${blockNumber} due to ${reason}`, {
|
|
119
|
+
...block.block.toBlockInfo(),
|
|
120
|
+
...args
|
|
121
|
+
});
|
|
122
|
+
this.badProposers.add(proposer.toString());
|
|
123
|
+
this.emit(WANT_TO_SLASH_EVENT, [
|
|
124
|
+
args
|
|
125
|
+
]);
|
|
126
|
+
}
|
|
127
|
+
getOffenseFromInvalidationReason(reason) {
|
|
128
|
+
switch(reason){
|
|
129
|
+
case 'invalid-attestation':
|
|
130
|
+
return Offense.PROPOSED_INCORRECT_ATTESTATIONS;
|
|
131
|
+
case 'insufficient-attestations':
|
|
132
|
+
return Offense.PROPOSED_INSUFFICIENT_ATTESTATIONS;
|
|
133
|
+
default:
|
|
134
|
+
{
|
|
135
|
+
const _ = reason;
|
|
136
|
+
return Offense.UNKNOWN;
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
getMaxPenalty(offense) {
|
|
141
|
+
switch(offense){
|
|
142
|
+
case Offense.PROPOSED_INCORRECT_ATTESTATIONS:
|
|
143
|
+
case Offense.PROPOSED_INSUFFICIENT_ATTESTATIONS:
|
|
144
|
+
return this.config.slashProposeInvalidAttestationsMaxPenalty;
|
|
145
|
+
case Offense.ATTESTED_DESCENDANT_OF_INVALID:
|
|
146
|
+
return this.config.slashProposeInvalidAttestationsMaxPenalty;
|
|
147
|
+
default:
|
|
148
|
+
return 0n;
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
hasOffended(offense, validator) {
|
|
152
|
+
switch(offense){
|
|
153
|
+
case Offense.PROPOSED_INCORRECT_ATTESTATIONS:
|
|
154
|
+
case Offense.PROPOSED_INSUFFICIENT_ATTESTATIONS:
|
|
155
|
+
return this.badProposers.has(validator.toString());
|
|
156
|
+
case Offense.ATTESTED_DESCENDANT_OF_INVALID:
|
|
157
|
+
return this.badAttestors.has(validator.toString());
|
|
158
|
+
default:
|
|
159
|
+
return false;
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
addInvalidBlock(block) {
|
|
163
|
+
this.invalidArchiveRoots.add(block.block.archive.root.toString());
|
|
164
|
+
// Prune old entries if we exceed the maximum
|
|
165
|
+
if (this.invalidArchiveRoots.size > this.maxInvalidBlocks) {
|
|
166
|
+
const oldestKey = this.invalidArchiveRoots.keys().next().value;
|
|
167
|
+
this.invalidArchiveRoots.delete(oldestKey);
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
}
|
package/dest/config.d.ts
CHANGED
|
@@ -1,15 +1,9 @@
|
|
|
1
1
|
import type { ConfigMappingsType } from '@aztec/foundation/config';
|
|
2
2
|
import { EthAddress } from '@aztec/foundation/eth-address';
|
|
3
3
|
import type { TypedEventEmitter } from '@aztec/foundation/types';
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
VALID_EPOCH_PRUNED = 2,
|
|
8
|
-
INACTIVITY = 3,
|
|
9
|
-
INVALID_BLOCK = 4
|
|
10
|
-
}
|
|
11
|
-
export declare const OffenseToBigInt: Record<Offense, bigint>;
|
|
12
|
-
export declare function bigIntToOffense(offense: bigint): Offense;
|
|
4
|
+
import type { SlasherConfig } from '@aztec/stdlib/interfaces/server';
|
|
5
|
+
import { Offense } from '@aztec/stdlib/slashing';
|
|
6
|
+
export type { SlasherConfig };
|
|
13
7
|
export declare const WANT_TO_SLASH_EVENT: "wantToSlash";
|
|
14
8
|
export interface WantToSlashArgs {
|
|
15
9
|
validator: EthAddress;
|
|
@@ -26,22 +20,6 @@ export type Watcher = WatcherEmitter & {
|
|
|
26
20
|
start?: () => Promise<void>;
|
|
27
21
|
stop?: () => Promise<void>;
|
|
28
22
|
};
|
|
29
|
-
export interface SlasherConfig {
|
|
30
|
-
slashOverridePayload?: EthAddress;
|
|
31
|
-
slashPayloadTtlSeconds: number;
|
|
32
|
-
slashPruneEnabled: boolean;
|
|
33
|
-
slashPrunePenalty: bigint;
|
|
34
|
-
slashPruneMaxPenalty: bigint;
|
|
35
|
-
slashInvalidBlockEnabled: boolean;
|
|
36
|
-
slashInvalidBlockPenalty: bigint;
|
|
37
|
-
slashInvalidBlockMaxPenalty: bigint;
|
|
38
|
-
slashInactivityEnabled: boolean;
|
|
39
|
-
slashInactivityCreateTargetPercentage: number;
|
|
40
|
-
slashInactivitySignalTargetPercentage: number;
|
|
41
|
-
slashInactivityCreatePenalty: bigint;
|
|
42
|
-
slashInactivityMaxPenalty: bigint;
|
|
43
|
-
slashProposerRoundPollingIntervalSeconds: number;
|
|
44
|
-
}
|
|
45
23
|
export declare const DefaultSlasherConfig: SlasherConfig;
|
|
46
24
|
export declare const slasherConfigMappings: ConfigMappingsType<SlasherConfig>;
|
|
47
25
|
//# sourceMappingURL=config.d.ts.map
|
package/dest/config.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,0BAA0B,CAAC;AASnE,OAAO,EAAE,UAAU,EAAE,MAAM,+BAA+B,CAAC;AAC3D,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,yBAAyB,CAAC;AACjE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,iCAAiC,CAAC;AACrE,OAAO,EAAE,OAAO,EAAE,MAAM,wBAAwB,CAAC;AAEjD,YAAY,EAAE,aAAa,EAAE,CAAC;AAE9B,eAAO,MAAM,mBAAmB,EAAG,aAAsB,CAAC;AAE1D,MAAM,WAAW,eAAe;IAC9B,SAAS,EAAE,UAAU,CAAC;IACtB,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,OAAO,CAAC;CAClB;AAGD,MAAM,WAAW,eAAe;IAC9B,CAAC,mBAAmB,CAAC,EAAE,CAAC,IAAI,EAAE,eAAe,EAAE,KAAK,IAAI,CAAC;CAC1D;AAED,MAAM,MAAM,cAAc,GAAG,iBAAiB,CAAC,eAAe,CAAC,CAAC;AAEhE,MAAM,MAAM,YAAY,GAAG,CAAC,IAAI,EAAE,eAAe,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;AAEvE,MAAM,MAAM,OAAO,GAAG,cAAc,GAAG;IACrC,WAAW,EAAE,YAAY,CAAC;IAC1B,KAAK,CAAC,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IAC5B,IAAI,CAAC,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;CAC5B,CAAC;AAEF,eAAO,MAAM,oBAAoB,EAAE,aAoBlC,CAAC;AAEF,eAAO,MAAM,qBAAqB,EAAE,kBAAkB,CAAC,aAAa,CAyGnE,CAAC"}
|
package/dest/config.js
CHANGED
|
@@ -1,36 +1,6 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { NULL_KEY } from '@aztec/ethereum';
|
|
2
|
+
import { SecretValue, bigintConfigHelper, booleanConfigHelper, floatConfigHelper, numberConfigHelper, secretValueConfigHelper } from '@aztec/foundation/config';
|
|
2
3
|
import { EthAddress } from '@aztec/foundation/eth-address';
|
|
3
|
-
export var Offense = /*#__PURE__*/ function(Offense) {
|
|
4
|
-
Offense[Offense["UNKNOWN"] = 0] = "UNKNOWN";
|
|
5
|
-
Offense[Offense["DATA_WITHHOLDING"] = 1] = "DATA_WITHHOLDING";
|
|
6
|
-
Offense[Offense["VALID_EPOCH_PRUNED"] = 2] = "VALID_EPOCH_PRUNED";
|
|
7
|
-
Offense[Offense["INACTIVITY"] = 3] = "INACTIVITY";
|
|
8
|
-
Offense[Offense["INVALID_BLOCK"] = 4] = "INVALID_BLOCK";
|
|
9
|
-
return Offense;
|
|
10
|
-
}({});
|
|
11
|
-
export const OffenseToBigInt = {
|
|
12
|
-
[0]: 0n,
|
|
13
|
-
[1]: 1n,
|
|
14
|
-
[2]: 2n,
|
|
15
|
-
[3]: 3n,
|
|
16
|
-
[4]: 4n
|
|
17
|
-
};
|
|
18
|
-
export function bigIntToOffense(offense) {
|
|
19
|
-
switch(offense){
|
|
20
|
-
case 0n:
|
|
21
|
-
return 0;
|
|
22
|
-
case 1n:
|
|
23
|
-
return 1;
|
|
24
|
-
case 2n:
|
|
25
|
-
return 2;
|
|
26
|
-
case 3n:
|
|
27
|
-
return 3;
|
|
28
|
-
case 4n:
|
|
29
|
-
return 4;
|
|
30
|
-
default:
|
|
31
|
-
throw new Error(`Unknown offense: ${offense}`);
|
|
32
|
-
}
|
|
33
|
-
}
|
|
34
4
|
export const WANT_TO_SLASH_EVENT = 'wantToSlash';
|
|
35
5
|
export const DefaultSlasherConfig = {
|
|
36
6
|
slashPayloadTtlSeconds: 60 * 60 * 24,
|
|
@@ -46,7 +16,12 @@ export const DefaultSlasherConfig = {
|
|
|
46
16
|
slashInactivitySignalTargetPercentage: 0.6,
|
|
47
17
|
slashInactivityCreatePenalty: 1n,
|
|
48
18
|
slashInactivityMaxPenalty: 100n,
|
|
49
|
-
|
|
19
|
+
slashProposeInvalidAttestationsPenalty: 1n,
|
|
20
|
+
slashProposeInvalidAttestationsMaxPenalty: 100n,
|
|
21
|
+
slashAttestDescendantOfInvalidPenalty: 1n,
|
|
22
|
+
slashAttestDescendantOfInvalidMaxPenalty: 100n,
|
|
23
|
+
slashProposerRoundPollingIntervalSeconds: 12,
|
|
24
|
+
slasherPrivateKey: new SecretValue(undefined)
|
|
50
25
|
};
|
|
51
26
|
export const slasherConfigMappings = {
|
|
52
27
|
slashPayloadTtlSeconds: {
|
|
@@ -123,8 +98,32 @@ export const slasherConfigMappings = {
|
|
|
123
98
|
description: 'Maximum penalty amount for slashing an inactive validator.',
|
|
124
99
|
...bigintConfigHelper(DefaultSlasherConfig.slashInactivityMaxPenalty)
|
|
125
100
|
},
|
|
101
|
+
slashProposeInvalidAttestationsPenalty: {
|
|
102
|
+
env: 'SLASH_PROPOSE_INVALID_ATTESTATIONS_PENALTY',
|
|
103
|
+
description: 'Penalty amount for slashing a proposer that proposed invalid attestations.',
|
|
104
|
+
...bigintConfigHelper(DefaultSlasherConfig.slashProposeInvalidAttestationsPenalty)
|
|
105
|
+
},
|
|
106
|
+
slashProposeInvalidAttestationsMaxPenalty: {
|
|
107
|
+
env: 'SLASH_PROPOSE_INVALID_ATTESTATIONS_MAX_PENALTY',
|
|
108
|
+
description: 'Maximum penalty amount for slashing a proposer that proposed invalid attestations.',
|
|
109
|
+
...bigintConfigHelper(DefaultSlasherConfig.slashProposeInvalidAttestationsMaxPenalty)
|
|
110
|
+
},
|
|
111
|
+
slashAttestDescendantOfInvalidPenalty: {
|
|
112
|
+
env: 'SLASH_ATTEST_DESCENDANT_OF_INVALID_PENALTY',
|
|
113
|
+
description: 'Penalty amount for slashing a validator that attested to a descendant of an invalid block.',
|
|
114
|
+
...bigintConfigHelper(DefaultSlasherConfig.slashAttestDescendantOfInvalidPenalty)
|
|
115
|
+
},
|
|
116
|
+
slashAttestDescendantOfInvalidMaxPenalty: {
|
|
117
|
+
env: 'SLASH_ATTEST_DESCENDANT_OF_INVALID_MAX_PENALTY',
|
|
118
|
+
description: 'Maximum penalty amount for slashing a validator that attested to a descendant of an invalid block.',
|
|
119
|
+
...bigintConfigHelper(DefaultSlasherConfig.slashAttestDescendantOfInvalidMaxPenalty)
|
|
120
|
+
},
|
|
126
121
|
slashProposerRoundPollingIntervalSeconds: {
|
|
127
122
|
description: 'Polling interval for slashing proposer round in seconds.',
|
|
128
123
|
...numberConfigHelper(DefaultSlasherConfig.slashProposerRoundPollingIntervalSeconds)
|
|
124
|
+
},
|
|
125
|
+
slasherPrivateKey: {
|
|
126
|
+
description: 'Private key used for creating slash payloads.',
|
|
127
|
+
...secretValueConfigHelper((val)=>val ? `0x${val.replace('0x', '')}` : NULL_KEY)
|
|
129
128
|
}
|
|
130
129
|
};
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { EpochCache } from '@aztec/epoch-cache';
|
|
2
2
|
import { L2Block, type L2BlockSourceEventEmitter } from '@aztec/stdlib/block';
|
|
3
|
-
import type { IFullNodeBlockBuilder,
|
|
3
|
+
import type { IFullNodeBlockBuilder, ITxProvider, MerkleTreeWriteOperations } from '@aztec/stdlib/interfaces/server';
|
|
4
4
|
import type { L1ToL2MessageSource } from '@aztec/stdlib/messaging';
|
|
5
5
|
import { type WantToSlashArgs, type Watcher, type WatcherEmitter } from './config.js';
|
|
6
6
|
declare const EpochPruneWatcher_base: new () => WatcherEmitter;
|
|
@@ -14,14 +14,15 @@ export declare class EpochPruneWatcher extends EpochPruneWatcher_base implements
|
|
|
14
14
|
private l2BlockSource;
|
|
15
15
|
private l1ToL2MessageSource;
|
|
16
16
|
private epochCache;
|
|
17
|
-
private
|
|
17
|
+
private txProvider;
|
|
18
18
|
private blockBuilder;
|
|
19
19
|
private penalty;
|
|
20
20
|
private maxPenalty;
|
|
21
21
|
private log;
|
|
22
22
|
private slashableCommittees;
|
|
23
23
|
private maxSlashableEpochs;
|
|
24
|
-
|
|
24
|
+
private boundHandlePruneL2Blocks;
|
|
25
|
+
constructor(l2BlockSource: L2BlockSourceEventEmitter, l1ToL2MessageSource: L1ToL2MessageSource, epochCache: EpochCache, txProvider: Pick<ITxProvider, 'getAvailableTxs'>, blockBuilder: IFullNodeBlockBuilder, penalty: bigint, maxPenalty: bigint);
|
|
25
26
|
start(): Promise<void>;
|
|
26
27
|
stop(): Promise<void>;
|
|
27
28
|
private handlePruneL2Blocks;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"epoch_prune_watcher.d.ts","sourceRoot":"","sources":["../src/epoch_prune_watcher.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"epoch_prune_watcher.d.ts","sourceRoot":"","sources":["../src/epoch_prune_watcher.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAC;AAEhD,OAAO,EAEL,OAAO,EAEP,KAAK,yBAAyB,EAE/B,MAAM,qBAAqB,CAAC;AAC7B,OAAO,KAAK,EAAE,qBAAqB,EAAE,WAAW,EAAE,yBAAyB,EAAE,MAAM,iCAAiC,CAAC;AACrH,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,yBAAyB,CAAC;AAWnE,OAAO,EAAuB,KAAK,eAAe,EAAE,KAAK,OAAO,EAAE,KAAK,cAAc,EAAE,MAAM,aAAa,CAAC;sCAQnD,UAAU,cAAc;AANhF;;;;;GAKG;AACH,qBAAa,iBAAkB,SAAQ,sBAA2C,YAAW,OAAO;IAYhG,OAAO,CAAC,aAAa;IACrB,OAAO,CAAC,mBAAmB;IAC3B,OAAO,CAAC,UAAU;IAClB,OAAO,CAAC,UAAU;IAClB,OAAO,CAAC,YAAY;IACpB,OAAO,CAAC,OAAO;IACf,OAAO,CAAC,UAAU;IAjBpB,OAAO,CAAC,GAAG,CAA+C;IAG1D,OAAO,CAAC,mBAAmB,CAAwC;IAEnE,OAAO,CAAC,kBAAkB,CAAO;IAGjC,OAAO,CAAC,wBAAwB,CAAuC;gBAG7D,aAAa,EAAE,yBAAyB,EACxC,mBAAmB,EAAE,mBAAmB,EACxC,UAAU,EAAE,UAAU,EACtB,UAAU,EAAE,IAAI,CAAC,WAAW,EAAE,iBAAiB,CAAC,EAChD,YAAY,EAAE,qBAAqB,EACnC,OAAO,EAAE,MAAM,EACf,UAAU,EAAE,MAAM;IAMrB,KAAK;IAKL,IAAI;IAKX,OAAO,CAAC,mBAAmB;IA8Cd,cAAc,CAAC,MAAM,EAAE,OAAO,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC;IAchD,aAAa,CAAC,WAAW,EAAE,OAAO,EAAE,IAAI,EAAE,yBAAyB,GAAG,OAAO,CAAC,IAAI,CAAC;IAgChG,OAAO,CAAC,oBAAoB;YAUd,qBAAqB;IASnC,OAAO,CAAC,wBAAwB;IAQhC,OAAO,CAAC,mBAAmB;IAIpB,WAAW,CAAC,EAAE,SAAS,EAAE,MAAM,EAAE,EAAE,eAAe,GAAG,OAAO,CAAC,OAAO,CAAC;CAS7E"}
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { createLogger } from '@aztec/foundation/log';
|
|
2
2
|
import { L2BlockSourceEvents } from '@aztec/stdlib/block';
|
|
3
|
+
import { Offense } from '@aztec/stdlib/slashing';
|
|
3
4
|
import { ReExFailedTxsError, ReExStateMismatchError, TransactionsNotAvailableError, ValidatorError } from '@aztec/stdlib/validators';
|
|
4
5
|
import EventEmitter from 'node:events';
|
|
5
|
-
import {
|
|
6
|
+
import { WANT_TO_SLASH_EVENT } from './config.js';
|
|
6
7
|
/**
|
|
7
8
|
* This watcher is responsible for detecting chain prunes and creating slashing arguments for the committee.
|
|
8
9
|
* It only wants to slash if:
|
|
@@ -12,7 +13,7 @@ import { Offense, WANT_TO_SLASH_EVENT } from './config.js';
|
|
|
12
13
|
l2BlockSource;
|
|
13
14
|
l1ToL2MessageSource;
|
|
14
15
|
epochCache;
|
|
15
|
-
|
|
16
|
+
txProvider;
|
|
16
17
|
blockBuilder;
|
|
17
18
|
penalty;
|
|
18
19
|
maxPenalty;
|
|
@@ -21,16 +22,18 @@ import { Offense, WANT_TO_SLASH_EVENT } from './config.js';
|
|
|
21
22
|
slashableCommittees;
|
|
22
23
|
// Only keep track of the last N slashable epochs
|
|
23
24
|
maxSlashableEpochs;
|
|
24
|
-
|
|
25
|
-
|
|
25
|
+
// Store bound function reference for proper listener removal
|
|
26
|
+
boundHandlePruneL2Blocks;
|
|
27
|
+
constructor(l2BlockSource, l1ToL2MessageSource, epochCache, txProvider, blockBuilder, penalty, maxPenalty){
|
|
28
|
+
super(), this.l2BlockSource = l2BlockSource, this.l1ToL2MessageSource = l1ToL2MessageSource, this.epochCache = epochCache, this.txProvider = txProvider, this.blockBuilder = blockBuilder, this.penalty = penalty, this.maxPenalty = maxPenalty, this.log = createLogger('epoch-prune-watcher'), this.slashableCommittees = new Map(), this.maxSlashableEpochs = 100, this.boundHandlePruneL2Blocks = this.handlePruneL2Blocks.bind(this);
|
|
26
29
|
this.log.info('EpochPruneWatcher initialized');
|
|
27
30
|
}
|
|
28
31
|
start() {
|
|
29
|
-
this.l2BlockSource.on(L2BlockSourceEvents.L2PruneDetected, this.
|
|
32
|
+
this.l2BlockSource.on(L2BlockSourceEvents.L2PruneDetected, this.boundHandlePruneL2Blocks);
|
|
30
33
|
return Promise.resolve();
|
|
31
34
|
}
|
|
32
35
|
stop() {
|
|
33
|
-
this.l2BlockSource.removeListener(L2BlockSourceEvents.L2PruneDetected, this.
|
|
36
|
+
this.l2BlockSource.removeListener(L2BlockSourceEvents.L2PruneDetected, this.boundHandlePruneL2Blocks);
|
|
34
37
|
return Promise.resolve();
|
|
35
38
|
}
|
|
36
39
|
handlePruneL2Blocks(event) {
|
|
@@ -89,9 +92,12 @@ import { Offense, WANT_TO_SLASH_EVENT } from './config.js';
|
|
|
89
92
|
async validateBlock(blockFromL1, fork) {
|
|
90
93
|
this.log.debug(`Validating pruned block ${blockFromL1.header.globalVariables.blockNumber}`);
|
|
91
94
|
const txHashes = blockFromL1.body.txEffects.map((txEffect)=>txEffect.txHash);
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
+
// We load txs from the mempool directly, since the TxCollector running in the background has already been
|
|
96
|
+
// trying to fetch them from nodes or via reqresp. If we haven't managed to collect them by now,
|
|
97
|
+
// it's likely that they are not available in the network at all.
|
|
98
|
+
const { txs, missingTxs } = await this.txProvider.getAvailableTxs(txHashes);
|
|
99
|
+
if (missingTxs && missingTxs.length > 0) {
|
|
100
|
+
throw new TransactionsNotAvailableError(missingTxs);
|
|
95
101
|
}
|
|
96
102
|
const l1ToL2Messages = await this.l1ToL2MessageSource.getL1ToL2Messages(blockFromL1.number);
|
|
97
103
|
const { block, failedTxs, numTxs } = await this.blockBuilder.buildBlock(txs, l1ToL2Messages, blockFromL1.header.globalVariables, {}, fork);
|
package/dest/index.d.ts
CHANGED
package/dest/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,aAAa,CAAC;AAC5B,cAAc,0BAA0B,CAAC;AACzC,cAAc,qBAAqB,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,aAAa,CAAC;AAC5B,cAAc,0BAA0B,CAAC;AACzC,cAAc,qBAAqB,CAAC;AACpC,cAAc,iCAAiC,CAAC;AAChD,cAAc,wBAAwB,CAAC"}
|
package/dest/index.js
CHANGED
package/dest/slasher_client.d.ts
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
|
-
import { type
|
|
1
|
+
import { type L1ReaderConfig, L1TxUtils, SlashingProposerContract, type ViemClient } from '@aztec/ethereum';
|
|
2
2
|
import { EthAddress } from '@aztec/foundation/eth-address';
|
|
3
3
|
import type { DateProvider } from '@aztec/foundation/timer';
|
|
4
4
|
import { SlashFactoryAbi } from '@aztec/l1-artifacts';
|
|
5
|
+
import type { SlasherConfig } from '@aztec/stdlib/interfaces/server';
|
|
6
|
+
import { type Offense } from '@aztec/stdlib/slashing';
|
|
5
7
|
import { type GetContractReturnType } from 'viem';
|
|
6
|
-
import {
|
|
8
|
+
import { type Watcher } from './config.js';
|
|
7
9
|
type MonitoredSlashPayload = {
|
|
8
10
|
payloadAddress: EthAddress;
|
|
9
11
|
validators: readonly EthAddress[];
|
|
@@ -37,8 +39,8 @@ type MonitoredSlashPayload = {
|
|
|
37
39
|
* - TODO(#14421): Only vote on the proposal if it is possible to reach quorum, e.g., if 6 votes are needed and only 4 slots are left don't vote.
|
|
38
40
|
*/
|
|
39
41
|
export declare class SlasherClient {
|
|
40
|
-
config: SlasherConfig
|
|
41
|
-
protected slashFactoryContract: GetContractReturnType<typeof SlashFactoryAbi,
|
|
42
|
+
config: Omit<SlasherConfig, 'slasherPrivateKey'>;
|
|
43
|
+
protected slashFactoryContract: GetContractReturnType<typeof SlashFactoryAbi, ViemClient>;
|
|
42
44
|
private slashingProposer;
|
|
43
45
|
private l1TxUtils;
|
|
44
46
|
private watchers;
|
|
@@ -47,22 +49,21 @@ export declare class SlasherClient {
|
|
|
47
49
|
private monitoredPayloads;
|
|
48
50
|
private unwatchCallbacks;
|
|
49
51
|
private overridePayloadActive;
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
52
|
+
private slashingExecutionDelayInRounds;
|
|
53
|
+
static new(config: Omit<SlasherConfig, 'slasherPrivateKey'>, l1Contracts: Pick<L1ReaderConfig['l1Contracts'], 'rollupAddress' | 'slashFactoryAddress'>, l1TxUtils: L1TxUtils | undefined, l1Client: ViemClient, watchers: Watcher[], dateProvider: DateProvider): Promise<SlasherClient>;
|
|
54
|
+
constructor(config: Omit<SlasherConfig, 'slasherPrivateKey'>, slashFactoryContract: GetContractReturnType<typeof SlashFactoryAbi, ViemClient>, slashingProposer: SlashingProposerContract, l1TxUtils: L1TxUtils | undefined, watchers: Watcher[], dateProvider: DateProvider, log?: import("@aztec/foundation/log").Logger);
|
|
55
|
+
start(): Promise<void>;
|
|
53
56
|
/**
|
|
54
57
|
* Allows consumers to stop the instance of the slasher client.
|
|
55
58
|
* 'ready' will now return 'false' and the running promise that keeps the client synced is interrupted.
|
|
56
59
|
*/
|
|
57
60
|
stop(): Promise<void>;
|
|
58
61
|
clearMonitoredPayloads(): void;
|
|
62
|
+
setSlashingProposer(slashingProposer: SlashingProposerContract): Promise<void>;
|
|
59
63
|
/**
|
|
60
64
|
* Update the config of the slasher client
|
|
61
65
|
*
|
|
62
|
-
* @param config - the new config.
|
|
63
|
-
* - slashOverridePayload
|
|
64
|
-
* - slashPayloadTtlSeconds
|
|
65
|
-
* - slashProposerRoundPollingIntervalSeconds
|
|
66
|
+
* @param config - the new config. Cannot update the slasher private key.
|
|
66
67
|
*/
|
|
67
68
|
updateConfig(config: Partial<SlasherConfig>): void;
|
|
68
69
|
/**
|
|
@@ -89,9 +90,9 @@ export declare class SlasherClient {
|
|
|
89
90
|
*
|
|
90
91
|
* @param {round: bigint; proposal: `0x${string}`} param0
|
|
91
92
|
*/
|
|
92
|
-
protected
|
|
93
|
+
protected payloadSubmitted({ round, payload }: {
|
|
93
94
|
round: bigint;
|
|
94
|
-
|
|
95
|
+
payload: `0x${string}`;
|
|
95
96
|
}): void;
|
|
96
97
|
/**
|
|
97
98
|
* This is called when a watcher emits WANT_TO_SLASH_EVENT.
|
|
@@ -144,13 +145,13 @@ export declare class SlasherClient {
|
|
|
144
145
|
*/
|
|
145
146
|
private filterExpiredPayloads;
|
|
146
147
|
/**
|
|
147
|
-
*
|
|
148
|
+
* Submit a round to the Slasher if we agree with the payload.
|
|
148
149
|
*
|
|
149
|
-
* Bound to the slashing proposer contract's
|
|
150
|
+
* Bound to the slashing proposer contract's listenToSubmittablePayloads method in the constructor.
|
|
150
151
|
*
|
|
151
152
|
* @param {proposal: `0x${string}`; round: bigint} param0
|
|
152
153
|
*/
|
|
153
|
-
private
|
|
154
|
+
private submitRoundIfAgree;
|
|
154
155
|
private getAddressAndIsDeployed;
|
|
155
156
|
}
|
|
156
157
|
export {};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"slasher_client.d.ts","sourceRoot":"","sources":["../src/slasher_client.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,KAAK,
|
|
1
|
+
{"version":3,"file":"slasher_client.d.ts","sourceRoot":"","sources":["../src/slasher_client.ts"],"names":[],"mappings":"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"}
|