@aztec/slasher 4.0.0-nightly.20260112 → 4.0.0-nightly.20260114
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/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/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 +33 -33
- package/dest/watchers/epoch_prune_watcher.d.ts +6 -5
- package/dest/watchers/epoch_prune_watcher.d.ts.map +1 -1
- package/dest/watchers/epoch_prune_watcher.js +16 -6
- package/package.json +13 -11
- package/src/config.ts +17 -16
- package/src/generated/slasher-defaults.ts +21 -0
- package/src/watchers/attestations_block_watcher.ts +49 -43
- package/src/watchers/epoch_prune_watcher.ts +21 -9
package/dest/config.d.ts
CHANGED
|
@@ -3,4 +3,4 @@ import type { SlasherConfig } from '@aztec/stdlib/interfaces/server';
|
|
|
3
3
|
export type { SlasherConfig };
|
|
4
4
|
export declare const DefaultSlasherConfig: SlasherConfig;
|
|
5
5
|
export declare const slasherConfigMappings: ConfigMappingsType<SlasherConfig>;
|
|
6
|
-
//# sourceMappingURL=data:application/json;base64,
|
|
6
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY29uZmlnLmQudHMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi9zcmMvY29uZmlnLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBLE9BQU8sS0FBSyxFQUFFLGtCQUFrQixFQUFFLE1BQU0sMEJBQTBCLENBQUM7QUFRbkUsT0FBTyxLQUFLLEVBQUUsYUFBYSxFQUFFLE1BQU0saUNBQWlDLENBQUM7QUFJckUsWUFBWSxFQUFFLGFBQWEsRUFBRSxDQUFDO0FBRTlCLGVBQU8sTUFBTSxvQkFBb0IsRUFBRSxhQW9CbEMsQ0FBQztBQUVGLGVBQU8sTUFBTSxxQkFBcUIsRUFBRSxrQkFBa0IsQ0FBQyxhQUFhLENBeUhuRSxDQUFDIn0=
|
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":"AAAA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,0BAA0B,CAAC;AAQnE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,iCAAiC,CAAC;AAIrE,YAAY,EAAE,aAAa,EAAE,CAAC;AAE9B,eAAO,MAAM,oBAAoB,EAAE,aAoBlC,CAAC;AAEF,eAAO,MAAM,qBAAqB,EAAE,kBAAkB,CAAC,aAAa,CAyHnE,CAAC"}
|
package/dest/config.js
CHANGED
|
@@ -1,25 +1,25 @@
|
|
|
1
|
-
import { DefaultL1ContractsConfig } from '@aztec/ethereum/config';
|
|
2
1
|
import { bigintConfigHelper, booleanConfigHelper, floatConfigHelper, numberConfigHelper } from '@aztec/foundation/config';
|
|
3
2
|
import { EthAddress } from '@aztec/foundation/eth-address';
|
|
3
|
+
import { slasherDefaultEnv } from './generated/slasher-defaults.js';
|
|
4
4
|
export const DefaultSlasherConfig = {
|
|
5
5
|
slashOverridePayload: undefined,
|
|
6
|
-
slashMinPenaltyPercentage:
|
|
7
|
-
slashMaxPenaltyPercentage:
|
|
6
|
+
slashMinPenaltyPercentage: slasherDefaultEnv.SLASH_MIN_PENALTY_PERCENTAGE,
|
|
7
|
+
slashMaxPenaltyPercentage: slasherDefaultEnv.SLASH_MAX_PENALTY_PERCENTAGE,
|
|
8
8
|
slashValidatorsAlways: [],
|
|
9
9
|
slashValidatorsNever: [],
|
|
10
|
-
slashPrunePenalty:
|
|
11
|
-
slashDataWithholdingPenalty:
|
|
12
|
-
slashInactivityTargetPercentage:
|
|
13
|
-
slashInactivityConsecutiveEpochThreshold:
|
|
14
|
-
slashBroadcastedInvalidBlockPenalty:
|
|
15
|
-
slashInactivityPenalty:
|
|
16
|
-
slashProposeInvalidAttestationsPenalty:
|
|
17
|
-
slashAttestDescendantOfInvalidPenalty:
|
|
18
|
-
slashUnknownPenalty:
|
|
19
|
-
slashOffenseExpirationRounds:
|
|
20
|
-
slashMaxPayloadSize:
|
|
21
|
-
slashGracePeriodL2Slots:
|
|
22
|
-
slashExecuteRoundsLookBack:
|
|
10
|
+
slashPrunePenalty: BigInt(slasherDefaultEnv.SLASH_PRUNE_PENALTY),
|
|
11
|
+
slashDataWithholdingPenalty: BigInt(slasherDefaultEnv.SLASH_DATA_WITHHOLDING_PENALTY),
|
|
12
|
+
slashInactivityTargetPercentage: slasherDefaultEnv.SLASH_INACTIVITY_TARGET_PERCENTAGE,
|
|
13
|
+
slashInactivityConsecutiveEpochThreshold: slasherDefaultEnv.SLASH_INACTIVITY_CONSECUTIVE_EPOCH_THRESHOLD,
|
|
14
|
+
slashBroadcastedInvalidBlockPenalty: BigInt(slasherDefaultEnv.SLASH_INVALID_BLOCK_PENALTY),
|
|
15
|
+
slashInactivityPenalty: BigInt(slasherDefaultEnv.SLASH_INACTIVITY_PENALTY),
|
|
16
|
+
slashProposeInvalidAttestationsPenalty: BigInt(slasherDefaultEnv.SLASH_PROPOSE_INVALID_ATTESTATIONS_PENALTY),
|
|
17
|
+
slashAttestDescendantOfInvalidPenalty: BigInt(slasherDefaultEnv.SLASH_ATTEST_DESCENDANT_OF_INVALID_PENALTY),
|
|
18
|
+
slashUnknownPenalty: BigInt(slasherDefaultEnv.SLASH_UNKNOWN_PENALTY),
|
|
19
|
+
slashOffenseExpirationRounds: slasherDefaultEnv.SLASH_OFFENSE_EXPIRATION_ROUNDS,
|
|
20
|
+
slashMaxPayloadSize: slasherDefaultEnv.SLASH_MAX_PAYLOAD_SIZE,
|
|
21
|
+
slashGracePeriodL2Slots: slasherDefaultEnv.SLASH_GRACE_PERIOD_L2_SLOTS,
|
|
22
|
+
slashExecuteRoundsLookBack: slasherDefaultEnv.SLASH_EXECUTE_ROUNDS_LOOK_BACK,
|
|
23
23
|
slashSelfAllowed: false
|
|
24
24
|
};
|
|
25
25
|
export const slasherConfigMappings = {
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/** Default slasher configuration values from network-defaults.yml */
|
|
2
|
+
export declare const slasherDefaultEnv: {
|
|
3
|
+
readonly SLASH_MIN_PENALTY_PERCENTAGE: 0.5;
|
|
4
|
+
readonly SLASH_MAX_PENALTY_PERCENTAGE: 2;
|
|
5
|
+
readonly SLASH_OFFENSE_EXPIRATION_ROUNDS: 4;
|
|
6
|
+
readonly SLASH_MAX_PAYLOAD_SIZE: 50;
|
|
7
|
+
readonly SLASH_EXECUTE_ROUNDS_LOOK_BACK: 4;
|
|
8
|
+
readonly SLASH_PRUNE_PENALTY: 10000000000000000000;
|
|
9
|
+
readonly SLASH_DATA_WITHHOLDING_PENALTY: 10000000000000000000;
|
|
10
|
+
readonly SLASH_INACTIVITY_TARGET_PERCENTAGE: 0.9;
|
|
11
|
+
readonly SLASH_INACTIVITY_CONSECUTIVE_EPOCH_THRESHOLD: 1;
|
|
12
|
+
readonly SLASH_INACTIVITY_PENALTY: 10000000000000000000;
|
|
13
|
+
readonly SLASH_PROPOSE_INVALID_ATTESTATIONS_PENALTY: 10000000000000000000;
|
|
14
|
+
readonly SLASH_ATTEST_DESCENDANT_OF_INVALID_PENALTY: 10000000000000000000;
|
|
15
|
+
readonly SLASH_UNKNOWN_PENALTY: 10000000000000000000;
|
|
16
|
+
readonly SLASH_INVALID_BLOCK_PENALTY: 10000000000000000000;
|
|
17
|
+
readonly SLASH_GRACE_PERIOD_L2_SLOTS: 0;
|
|
18
|
+
};
|
|
19
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoic2xhc2hlci1kZWZhdWx0cy5kLnRzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vc3JjL2dlbmVyYXRlZC9zbGFzaGVyLWRlZmF1bHRzLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUdBLHFFQUFxRTtBQUNyRSxlQUFPLE1BQU0saUJBQWlCOzs7Ozs7Ozs7Ozs7Ozs7O0NBZ0JwQixDQUFDIn0=
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"slasher-defaults.d.ts","sourceRoot":"","sources":["../../src/generated/slasher-defaults.ts"],"names":[],"mappings":"AAGA,qEAAqE;AACrE,eAAO,MAAM,iBAAiB;;;;;;;;;;;;;;;;CAgBpB,CAAC"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
// Auto-generated from spartan/environments/network-defaults.yml
|
|
2
|
+
// Do not edit manually - run yarn generate to regenerate
|
|
3
|
+
/** Default slasher configuration values from network-defaults.yml */ export const slasherDefaultEnv = {
|
|
4
|
+
SLASH_MIN_PENALTY_PERCENTAGE: 0.5,
|
|
5
|
+
SLASH_MAX_PENALTY_PERCENTAGE: 2,
|
|
6
|
+
SLASH_OFFENSE_EXPIRATION_ROUNDS: 4,
|
|
7
|
+
SLASH_MAX_PAYLOAD_SIZE: 50,
|
|
8
|
+
SLASH_EXECUTE_ROUNDS_LOOK_BACK: 4,
|
|
9
|
+
SLASH_PRUNE_PENALTY: 10000000000000000000,
|
|
10
|
+
SLASH_DATA_WITHHOLDING_PENALTY: 10000000000000000000,
|
|
11
|
+
SLASH_INACTIVITY_TARGET_PERCENTAGE: 0.9,
|
|
12
|
+
SLASH_INACTIVITY_CONSECUTIVE_EPOCH_THRESHOLD: 1,
|
|
13
|
+
SLASH_INACTIVITY_PENALTY: 10000000000000000000,
|
|
14
|
+
SLASH_PROPOSE_INVALID_ATTESTATIONS_PENALTY: 10000000000000000000,
|
|
15
|
+
SLASH_ATTEST_DESCENDANT_OF_INVALID_PENALTY: 10000000000000000000,
|
|
16
|
+
SLASH_UNKNOWN_PENALTY: 10000000000000000000,
|
|
17
|
+
SLASH_INVALID_BLOCK_PENALTY: 10000000000000000000,
|
|
18
|
+
SLASH_GRACE_PERIOD_L2_SLOTS: 0
|
|
19
|
+
};
|
|
@@ -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,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiYXR0ZXN0YXRpb25zX2Jsb2NrX3dhdGNoZXIuZC50cyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uL3NyYy93YXRjaGVycy9hdHRlc3RhdGlvbnNfYmxvY2tfd2F0Y2hlci50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxPQUFPLEVBQUUsVUFBVSxFQUFFLE1BQU0sb0JBQW9CLENBQUM7QUFJaEQsT0FBTyxFQUVMLEtBQUsseUJBQXlCLEVBRy9CLE1BQU0scUJBQXFCLENBQUM7QUFNN0IsT0FBTyxLQUFLLEVBQUUsYUFBYSxFQUFFLE1BQU0sY0FBYyxDQUFDO0FBQ2xELE9BQU8sRUFBNkMsS0FBSyxPQUFPLEVBQUUsS0FBSyxjQUFjLEVBQUUsTUFBTSxlQUFlLENBQUM7QUFFN0csUUFBQSxNQUFNLGtDQUFrQyw4RkFHOUIsQ0FBQztBQUVYLEtBQUssOEJBQThCLEdBQUcsSUFBSSxDQUFDLGFBQWEsRUFBRSxDQUFDLE9BQU8sa0NBQWtDLENBQUMsQ0FBQyxNQUFNLENBQUMsQ0FBQyxDQUFDOztBQUUvRzs7Ozs7R0FLRztBQUNILHFCQUFhLHdCQUF5QixTQUFRLDZCQUEyQyxZQUFXLE9BQU87SUF1QnZHLE9BQU8sQ0FBQyxhQUFhO0lBQ3JCLE9BQU8sQ0FBQyxVQUFVO0lBdkJwQixPQUFPLENBQUMsR0FBRyxDQUFzRDtJQUdqRSxPQUFPLENBQUMscUJBQXFCLENBQU87SUFHcEMsT0FBTyxDQUFDLG1CQUFtQixDQUEwQjtJQUVyRCxPQUFPLENBQUMsTUFBTSxDQUFpQztJQUUvQyxPQUFPLENBQUMsNEJBQTRCLENBU2xDO0lBRUYsWUFDVSxhQUFhLEVBQUUseUJBQXlCLEVBQ3hDLFVBQVUsRUFBRSxVQUFVLEVBQzlCLE1BQU0sRUFBRSw4QkFBOEIsRUFLdkM7SUFFTSxZQUFZLENBQUMsU0FBUyxFQUFFLE9BQU8sQ0FBQyw4QkFBOEIsQ0FBQyxRQUdyRTtJQUVNLEtBQUssa0JBTVg7SUFFTSxJQUFJLGtCQU1WO0lBRUQsT0FBTyxDQUFDLHVCQUF1QjtJQXlCL0IsT0FBTyxDQUFDLCtCQUErQjtJQTJCdkMsT0FBTyxDQUFDLGFBQWE7SUFpQ3JCLE9BQU8sQ0FBQyxnQ0FBZ0M7SUFheEMsT0FBTyxDQUFDLG9CQUFvQjtDQVM3QiJ9
|
|
@@ -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,kBAMX;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.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
|
+
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,14 +83,14 @@ 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 =
|
|
91
|
+
const { reason, checkpoint } = validationResult;
|
|
92
|
+
const checkpointNumber = checkpoint.checkpointNumber;
|
|
93
|
+
const slot = checkpoint.slotNumber;
|
|
94
94
|
const epochCommitteeInfo = {
|
|
95
95
|
committee: validationResult.committee,
|
|
96
96
|
seed: validationResult.seed,
|
|
@@ -98,7 +98,7 @@ const AttestationsBlockWatcherConfigKeys = [
|
|
|
98
98
|
};
|
|
99
99
|
const proposer = this.epochCache.getProposerFromEpochCommittee(epochCommitteeInfo, slot);
|
|
100
100
|
if (!proposer) {
|
|
101
|
-
this.log.warn(`No proposer found for
|
|
101
|
+
this.log.warn(`No proposer found for checkpoint ${checkpointNumber} at slot ${slot}`);
|
|
102
102
|
return;
|
|
103
103
|
}
|
|
104
104
|
const offense = this.getOffenseFromInvalidationReason(reason);
|
|
@@ -109,8 +109,8 @@ const AttestationsBlockWatcherConfigKeys = [
|
|
|
109
109
|
offenseType: offense,
|
|
110
110
|
epochOrSlot: BigInt(slot)
|
|
111
111
|
};
|
|
112
|
-
this.log.info(`Want to slash proposer of
|
|
113
|
-
...
|
|
112
|
+
this.log.info(`Want to slash proposer of checkpoint ${checkpointNumber} due to ${reason}`, {
|
|
113
|
+
...checkpoint,
|
|
114
114
|
...args
|
|
115
115
|
});
|
|
116
116
|
this.emit(WANT_TO_SLASH_EVENT, [
|
|
@@ -130,10 +130,10 @@ const AttestationsBlockWatcherConfigKeys = [
|
|
|
130
130
|
}
|
|
131
131
|
}
|
|
132
132
|
}
|
|
133
|
-
|
|
134
|
-
this.invalidArchiveRoots.add(
|
|
133
|
+
addInvalidCheckpoint(checkpoint) {
|
|
134
|
+
this.invalidArchiveRoots.add(checkpoint.archive.toString());
|
|
135
135
|
// Prune old entries if we exceed the maximum
|
|
136
|
-
if (this.invalidArchiveRoots.size > this.
|
|
136
|
+
if (this.invalidArchiveRoots.size > this.maxInvalidCheckpoints) {
|
|
137
137
|
const oldestKey = this.invalidArchiveRoots.keys().next().value;
|
|
138
138
|
this.invalidArchiveRoots.delete(oldestKey);
|
|
139
139
|
}
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { EpochCache } from '@aztec/epoch-cache';
|
|
2
|
-
import {
|
|
2
|
+
import type { Fr } from '@aztec/foundation/curves/bn254';
|
|
3
|
+
import { L2BlockNew, type L2BlockSourceEventEmitter } from '@aztec/stdlib/block';
|
|
3
4
|
import type { IFullNodeBlockBuilder, ITxProvider, MerkleTreeWriteOperations, SlasherConfig } from '@aztec/stdlib/interfaces/server';
|
|
4
|
-
import type
|
|
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]>;
|
|
@@ -28,10 +29,10 @@ export declare class EpochPruneWatcher extends EpochPruneWatcher_base implements
|
|
|
28
29
|
private handlePruneL2Blocks;
|
|
29
30
|
private emitSlashForEpoch;
|
|
30
31
|
private processPruneL2Blocks;
|
|
31
|
-
validateBlocks(blocks:
|
|
32
|
-
validateBlock(blockFromL1:
|
|
32
|
+
validateBlocks(blocks: L2BlockNew[]): Promise<void>;
|
|
33
|
+
validateBlock(blockFromL1: L2BlockNew, 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,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiZXBvY2hfcHJ1bmVfd2F0Y2hlci5kLnRzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vc3JjL3dhdGNoZXJzL2Vwb2NoX3BydW5lX3dhdGNoZXIudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsT0FBTyxFQUFFLFVBQVUsRUFBRSxNQUFNLG9CQUFvQixDQUFDO0FBR2hELE9BQU8sS0FBSyxFQUFFLEVBQUUsRUFBRSxNQUFNLGdDQUFnQyxDQUFDO0FBRXpELE9BQU8sRUFFTCxVQUFVLEVBRVYsS0FBSyx5QkFBeUIsRUFFL0IsTUFBTSxxQkFBcUIsQ0FBQztBQUU3QixPQUFPLEtBQUssRUFDVixxQkFBcUIsRUFDckIsV0FBVyxFQUNYLHlCQUF5QixFQUN6QixhQUFhLEVBQ2QsTUFBTSxpQ0FBaUMsQ0FBQztBQUN6QyxPQUFPLEVBQUUsS0FBSyxtQkFBbUIsRUFBNEIsTUFBTSx5QkFBeUIsQ0FBQztBQVc3RixPQUFPLEVBQTZDLEtBQUssT0FBTyxFQUFFLEtBQUssY0FBYyxFQUFFLE1BQU0sZUFBZSxDQUFDO0FBRTdHLFFBQUEsTUFBTSxvQ0FBb0MsK0RBQWdFLENBQUM7QUFFM0csS0FBSywwQkFBMEIsR0FBRyxJQUFJLENBQUMsYUFBYSxFQUFFLENBQUMsT0FBTyxvQ0FBb0MsQ0FBQyxDQUFDLE1BQU0sQ0FBQyxDQUFDLENBQUM7O0FBRTdHOzs7OztHQUtHO0FBQ0gscUJBQWEsaUJBQWtCLFNBQVEsc0JBQTJDLFlBQVcsT0FBTztJQVNoRyxPQUFPLENBQUMsYUFBYTtJQUNyQixPQUFPLENBQUMsbUJBQW1CO0lBQzNCLE9BQU8sQ0FBQyxVQUFVO0lBQ2xCLE9BQU8sQ0FBQyxVQUFVO0lBQ2xCLE9BQU8sQ0FBQyxZQUFZO0lBWnRCLE9BQU8sQ0FBQyxHQUFHLENBQStDO0lBRzFELE9BQU8sQ0FBQyx3QkFBd0IsQ0FBdUM7SUFFdkUsT0FBTyxDQUFDLFNBQVMsQ0FBNkI7SUFFOUMsWUFDVSxhQUFhLEVBQUUseUJBQXlCLEVBQ3hDLG1CQUFtQixFQUFFLG1CQUFtQixFQUN4QyxVQUFVLEVBQUUsVUFBVSxFQUN0QixVQUFVLEVBQUUsSUFBSSxDQUFDLFdBQVcsRUFBRSxpQkFBaUIsQ0FBQyxFQUNoRCxZQUFZLEVBQUUscUJBQXFCLEVBQzNDLFNBQVMsRUFBRSwwQkFBMEIsRUFPdEM7SUFFTSxLQUFLLGtCQUdYO0lBRU0sSUFBSSxrQkFHVjtJQUVNLFlBQVksQ0FBQyxNQUFNLEVBQUUsT0FBTyxDQUFDLGFBQWEsQ0FBQyxHQUFHLElBQUksQ0FHeEQ7SUFFRCxPQUFPLENBQUMsbUJBQW1CO1lBT2IsaUJBQWlCO1lBV2pCLG9CQUFvQjtJQXdCckIsY0FBYyxDQUFDLE1BQU0sRUFBRSxVQUFVLEVBQUUsR0FBRyxPQUFPLENBQUMsSUFBSSxDQUFDLENBa0IvRDtJQUVZLGFBQWEsQ0FDeEIsV0FBVyxFQUFFLFVBQVUsRUFDdkIsMkJBQTJCLEVBQUUsRUFBRSxFQUFFLEVBQ2pDLElBQUksRUFBRSx5QkFBeUIsR0FDOUIsT0FBTyxDQUFDLElBQUksQ0FBQyxDQWdDZjtZQUVhLHFCQUFxQjtJQVNuQyxPQUFPLENBQUMsd0JBQXdCO0NBZ0JqQyJ9
|
|
@@ -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,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,EAAE,KAAK,mBAAmB,EAA4B,MAAM,yBAAyB,CAAC;AAW7F,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,CAkB/D;IAEY,aAAa,CACxB,WAAW,EAAE,UAAU,EACvB,2BAA2B,EAAE,EAAE,EAAE,EACjC,IAAI,EAAE,yBAAyB,GAC9B,OAAO,CAAC,IAAI,CAAC,CAgCf;YAEa,qBAAqB;IASnC,OAAO,CAAC,wBAAwB;CAgBjC"}
|
|
@@ -3,6 +3,7 @@ import { merge, pick } from '@aztec/foundation/collection';
|
|
|
3
3
|
import { createLogger } from '@aztec/foundation/log';
|
|
4
4
|
import { L2BlockSourceEvents } from '@aztec/stdlib/block';
|
|
5
5
|
import { getEpochAtSlot } from '@aztec/stdlib/epoch-helpers';
|
|
6
|
+
import { computeCheckpointOutHash } from '@aztec/stdlib/messaging';
|
|
6
7
|
import { OffenseType, getOffenseTypeName } from '@aztec/stdlib/slashing';
|
|
7
8
|
import { ReExFailedTxsError, ReExStateMismatchError, TransactionsNotAvailableError, ValidatorError } from '@aztec/stdlib/validators';
|
|
8
9
|
import EventEmitter from 'node:events';
|
|
@@ -32,11 +33,11 @@ const EpochPruneWatcherPenaltiesConfigKeys = [
|
|
|
32
33
|
this.log.verbose(`EpochPruneWatcher initialized with penalties: valid epoch pruned=${penalties.slashPrunePenalty} data withholding=${penalties.slashDataWithholdingPenalty}`);
|
|
33
34
|
}
|
|
34
35
|
start() {
|
|
35
|
-
this.l2BlockSource.on(L2BlockSourceEvents.L2PruneDetected, this.boundHandlePruneL2Blocks);
|
|
36
|
+
this.l2BlockSource.events.on(L2BlockSourceEvents.L2PruneDetected, this.boundHandlePruneL2Blocks);
|
|
36
37
|
return Promise.resolve();
|
|
37
38
|
}
|
|
38
39
|
stop() {
|
|
39
|
-
this.l2BlockSource.removeListener(L2BlockSourceEvents.L2PruneDetected, this.boundHandlePruneL2Blocks);
|
|
40
|
+
this.l2BlockSource.events.removeListener(L2BlockSourceEvents.L2PruneDetected, this.boundHandlePruneL2Blocks);
|
|
40
41
|
return Promise.resolve();
|
|
41
42
|
}
|
|
42
43
|
updateConfig(config) {
|
|
@@ -62,7 +63,7 @@ const EpochPruneWatcherPenaltiesConfigKeys = [
|
|
|
62
63
|
async processPruneL2Blocks(blocks, epochNumber) {
|
|
63
64
|
try {
|
|
64
65
|
const l1Constants = this.epochCache.getL1Constants();
|
|
65
|
-
const epochBlocks = blocks.filter((b)=>getEpochAtSlot(b.
|
|
66
|
+
const epochBlocks = blocks.filter((b)=>getEpochAtSlot(b.header.getSlot(), l1Constants) === epochNumber);
|
|
66
67
|
this.log.info(`Detected chain prune. Validating epoch ${epochNumber} with blocks ${epochBlocks[0]?.number} to ${epochBlocks[epochBlocks.length - 1]?.number}.`, {
|
|
67
68
|
blocks: epochBlocks.map((b)=>b.toBlockInfo())
|
|
68
69
|
});
|
|
@@ -84,16 +85,25 @@ const EpochPruneWatcherPenaltiesConfigKeys = [
|
|
|
84
85
|
if (blocks.length === 0) {
|
|
85
86
|
return;
|
|
86
87
|
}
|
|
88
|
+
let previousCheckpointOutHashes = [];
|
|
87
89
|
const fork = await this.blockBuilder.getFork(BlockNumber(blocks[0].header.globalVariables.blockNumber - 1));
|
|
88
90
|
try {
|
|
89
91
|
for (const block of blocks){
|
|
90
|
-
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
|
+
];
|
|
91
101
|
}
|
|
92
102
|
} finally{
|
|
93
103
|
await fork.close();
|
|
94
104
|
}
|
|
95
105
|
}
|
|
96
|
-
async validateBlock(blockFromL1, fork) {
|
|
106
|
+
async validateBlock(blockFromL1, previousCheckpointOutHashes, fork) {
|
|
97
107
|
this.log.debug(`Validating pruned block ${blockFromL1.header.globalVariables.blockNumber}`);
|
|
98
108
|
const txHashes = blockFromL1.body.txEffects.map((txEffect)=>txEffect.txHash);
|
|
99
109
|
// We load txs from the mempool directly, since the TxCollector running in the background has already been
|
|
@@ -105,7 +115,7 @@ const EpochPruneWatcherPenaltiesConfigKeys = [
|
|
|
105
115
|
}
|
|
106
116
|
const checkpointNumber = CheckpointNumber.fromBlockNumber(blockFromL1.number);
|
|
107
117
|
const l1ToL2Messages = await this.l1ToL2MessageSource.getL1ToL2Messages(checkpointNumber);
|
|
108
|
-
const { block, failedTxs, numTxs } = await this.blockBuilder.buildBlock(txs, l1ToL2Messages, blockFromL1.header.globalVariables, {}, fork);
|
|
118
|
+
const { block, failedTxs, numTxs } = await this.blockBuilder.buildBlock(txs, l1ToL2Messages, previousCheckpointOutHashes, blockFromL1.header.globalVariables, {}, fork);
|
|
109
119
|
if (numTxs !== txs.length) {
|
|
110
120
|
// This should be detected by state mismatch, but this makes it easier to debug.
|
|
111
121
|
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": "4.0.0-nightly.
|
|
3
|
+
"version": "4.0.0-nightly.20260114",
|
|
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
14
|
"build": "yarn clean && ../scripts/tsc.sh",
|
|
14
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,20 +56,20 @@
|
|
|
54
56
|
]
|
|
55
57
|
},
|
|
56
58
|
"dependencies": {
|
|
57
|
-
"@aztec/epoch-cache": "4.0.0-nightly.
|
|
58
|
-
"@aztec/ethereum": "4.0.0-nightly.
|
|
59
|
-
"@aztec/foundation": "4.0.0-nightly.
|
|
60
|
-
"@aztec/kv-store": "4.0.0-nightly.
|
|
61
|
-
"@aztec/l1-artifacts": "4.0.0-nightly.
|
|
62
|
-
"@aztec/stdlib": "4.0.0-nightly.
|
|
63
|
-
"@aztec/telemetry-client": "4.0.0-nightly.
|
|
59
|
+
"@aztec/epoch-cache": "4.0.0-nightly.20260114",
|
|
60
|
+
"@aztec/ethereum": "4.0.0-nightly.20260114",
|
|
61
|
+
"@aztec/foundation": "4.0.0-nightly.20260114",
|
|
62
|
+
"@aztec/kv-store": "4.0.0-nightly.20260114",
|
|
63
|
+
"@aztec/l1-artifacts": "4.0.0-nightly.20260114",
|
|
64
|
+
"@aztec/stdlib": "4.0.0-nightly.20260114",
|
|
65
|
+
"@aztec/telemetry-client": "4.0.0-nightly.20260114",
|
|
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": "4.0.0-nightly.
|
|
72
|
+
"@aztec/aztec.js": "4.0.0-nightly.20260114",
|
|
71
73
|
"@jest/globals": "^30.0.0",
|
|
72
74
|
"@types/jest": "^30.0.0",
|
|
73
75
|
"@types/node": "^22.15.17",
|
package/src/config.ts
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { DefaultL1ContractsConfig } from '@aztec/ethereum/config';
|
|
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
|
|
|
@@ -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;
|
|
@@ -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,60 @@ export class AttestationsBlockWatcher extends (EventEmitter as new () => Watcher
|
|
|
67
67
|
}
|
|
68
68
|
|
|
69
69
|
public start() {
|
|
70
|
-
this.l2BlockSource.on(
|
|
70
|
+
this.l2BlockSource.events.on(
|
|
71
|
+
L2BlockSourceEvents.InvalidAttestationsCheckpointDetected,
|
|
72
|
+
this.boundHandleInvalidCheckpoint,
|
|
73
|
+
);
|
|
71
74
|
return Promise.resolve();
|
|
72
75
|
}
|
|
73
76
|
|
|
74
77
|
public stop() {
|
|
75
|
-
this.l2BlockSource.removeListener(
|
|
76
|
-
L2BlockSourceEvents.
|
|
77
|
-
this.
|
|
78
|
+
this.l2BlockSource.events.removeListener(
|
|
79
|
+
L2BlockSourceEvents.InvalidAttestationsCheckpointDetected,
|
|
80
|
+
this.boundHandleInvalidCheckpoint,
|
|
78
81
|
);
|
|
79
82
|
return Promise.resolve();
|
|
80
83
|
}
|
|
81
84
|
|
|
82
|
-
private
|
|
85
|
+
private handleInvalidCheckpoint(event: InvalidCheckpointDetectedEvent): void {
|
|
83
86
|
const { validationResult } = event;
|
|
84
|
-
const
|
|
87
|
+
const checkpoint = validationResult.checkpoint;
|
|
85
88
|
|
|
86
|
-
// Check if we already have processed this
|
|
87
|
-
if (this.invalidArchiveRoots.has(
|
|
88
|
-
this.log.trace(`Already processed invalid
|
|
89
|
+
// Check if we already have processed this checkpoint, archiver may emit the same event multiple times
|
|
90
|
+
if (this.invalidArchiveRoots.has(checkpoint.archive.toString())) {
|
|
91
|
+
this.log.trace(`Already processed invalid checkpoint ${checkpoint.checkpointNumber}`);
|
|
89
92
|
return;
|
|
90
93
|
}
|
|
91
94
|
|
|
92
|
-
this.log.verbose(`Detected invalid
|
|
93
|
-
...
|
|
95
|
+
this.log.verbose(`Detected invalid checkpoint ${checkpoint.checkpointNumber}`, {
|
|
96
|
+
...checkpoint,
|
|
94
97
|
reason: validationResult.valid === false ? validationResult.reason : 'unknown',
|
|
95
98
|
});
|
|
96
99
|
|
|
97
|
-
// Store the invalid
|
|
98
|
-
this.
|
|
100
|
+
// Store the invalid checkpoint
|
|
101
|
+
this.addInvalidCheckpoint(event.validationResult.checkpoint);
|
|
99
102
|
|
|
100
|
-
// Slash the proposer of the invalid
|
|
103
|
+
// Slash the proposer of the invalid checkpoint
|
|
101
104
|
this.slashProposer(event.validationResult);
|
|
102
105
|
|
|
103
|
-
// Check if the parent of this
|
|
106
|
+
// Check if the parent of this checkpoint is invalid as well, if so, we will slash its attestors as well
|
|
104
107
|
this.slashAttestorsOnAncestorInvalid(event.validationResult);
|
|
105
108
|
}
|
|
106
109
|
|
|
107
|
-
private slashAttestorsOnAncestorInvalid(validationResult:
|
|
108
|
-
const
|
|
110
|
+
private slashAttestorsOnAncestorInvalid(validationResult: ValidateCheckpointNegativeResult) {
|
|
111
|
+
const checkpoint = validationResult.checkpoint;
|
|
109
112
|
|
|
110
|
-
const parentArchive =
|
|
113
|
+
const parentArchive = checkpoint.lastArchive.toString();
|
|
111
114
|
if (this.invalidArchiveRoots.has(parentArchive)) {
|
|
112
115
|
const attestors = validationResult.attestors;
|
|
113
|
-
this.log.info(
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
116
|
+
this.log.info(
|
|
117
|
+
`Want to slash attestors of checkpoint ${checkpoint.checkpointNumber} built on invalid checkpoint`,
|
|
118
|
+
{
|
|
119
|
+
...checkpoint,
|
|
120
|
+
...attestors,
|
|
121
|
+
parentArchive,
|
|
122
|
+
},
|
|
123
|
+
);
|
|
118
124
|
|
|
119
125
|
this.emit(
|
|
120
126
|
WANT_TO_SLASH_EVENT,
|
|
@@ -122,16 +128,16 @@ export class AttestationsBlockWatcher extends (EventEmitter as new () => Watcher
|
|
|
122
128
|
validator: attestor,
|
|
123
129
|
amount: this.config.slashAttestDescendantOfInvalidPenalty,
|
|
124
130
|
offenseType: OffenseType.ATTESTED_DESCENDANT_OF_INVALID,
|
|
125
|
-
epochOrSlot: BigInt(SlotNumber(
|
|
131
|
+
epochOrSlot: BigInt(SlotNumber(checkpoint.slotNumber)),
|
|
126
132
|
})),
|
|
127
133
|
);
|
|
128
134
|
}
|
|
129
135
|
}
|
|
130
136
|
|
|
131
|
-
private slashProposer(validationResult:
|
|
132
|
-
const { reason,
|
|
133
|
-
const
|
|
134
|
-
const slot =
|
|
137
|
+
private slashProposer(validationResult: ValidateCheckpointNegativeResult) {
|
|
138
|
+
const { reason, checkpoint } = validationResult;
|
|
139
|
+
const checkpointNumber = checkpoint.checkpointNumber;
|
|
140
|
+
const slot = checkpoint.slotNumber;
|
|
135
141
|
const epochCommitteeInfo = {
|
|
136
142
|
committee: validationResult.committee,
|
|
137
143
|
seed: validationResult.seed,
|
|
@@ -140,7 +146,7 @@ export class AttestationsBlockWatcher extends (EventEmitter as new () => Watcher
|
|
|
140
146
|
const proposer = this.epochCache.getProposerFromEpochCommittee(epochCommitteeInfo, slot);
|
|
141
147
|
|
|
142
148
|
if (!proposer) {
|
|
143
|
-
this.log.warn(`No proposer found for
|
|
149
|
+
this.log.warn(`No proposer found for checkpoint ${checkpointNumber} at slot ${slot}`);
|
|
144
150
|
return;
|
|
145
151
|
}
|
|
146
152
|
|
|
@@ -153,15 +159,15 @@ export class AttestationsBlockWatcher extends (EventEmitter as new () => Watcher
|
|
|
153
159
|
epochOrSlot: BigInt(slot),
|
|
154
160
|
};
|
|
155
161
|
|
|
156
|
-
this.log.info(`Want to slash proposer of
|
|
157
|
-
...
|
|
162
|
+
this.log.info(`Want to slash proposer of checkpoint ${checkpointNumber} due to ${reason}`, {
|
|
163
|
+
...checkpoint,
|
|
158
164
|
...args,
|
|
159
165
|
});
|
|
160
166
|
|
|
161
167
|
this.emit(WANT_TO_SLASH_EVENT, [args]);
|
|
162
168
|
}
|
|
163
169
|
|
|
164
|
-
private getOffenseFromInvalidationReason(reason:
|
|
170
|
+
private getOffenseFromInvalidationReason(reason: ValidateCheckpointNegativeResult['reason']): OffenseType {
|
|
165
171
|
switch (reason) {
|
|
166
172
|
case 'invalid-attestation':
|
|
167
173
|
return OffenseType.PROPOSED_INCORRECT_ATTESTATIONS;
|
|
@@ -174,11 +180,11 @@ export class AttestationsBlockWatcher extends (EventEmitter as new () => Watcher
|
|
|
174
180
|
}
|
|
175
181
|
}
|
|
176
182
|
|
|
177
|
-
private
|
|
178
|
-
this.invalidArchiveRoots.add(
|
|
183
|
+
private addInvalidCheckpoint(checkpoint: CheckpointInfo) {
|
|
184
|
+
this.invalidArchiveRoots.add(checkpoint.archive.toString());
|
|
179
185
|
|
|
180
186
|
// Prune old entries if we exceed the maximum
|
|
181
|
-
if (this.invalidArchiveRoots.size > this.
|
|
187
|
+
if (this.invalidArchiveRoots.size > this.maxInvalidCheckpoints) {
|
|
182
188
|
const oldestKey = this.invalidArchiveRoots.keys().next().value!;
|
|
183
189
|
this.invalidArchiveRoots.delete(oldestKey);
|
|
184
190
|
}
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import { EpochCache } from '@aztec/epoch-cache';
|
|
2
2
|
import { BlockNumber, CheckpointNumber, EpochNumber } from '@aztec/foundation/branded-types';
|
|
3
3
|
import { merge, pick } from '@aztec/foundation/collection';
|
|
4
|
+
import type { Fr } from '@aztec/foundation/curves/bn254';
|
|
4
5
|
import { type Logger, createLogger } from '@aztec/foundation/log';
|
|
5
6
|
import {
|
|
6
7
|
EthAddress,
|
|
7
|
-
|
|
8
|
+
L2BlockNew,
|
|
8
9
|
type L2BlockPruneEvent,
|
|
9
10
|
type L2BlockSourceEventEmitter,
|
|
10
11
|
L2BlockSourceEvents,
|
|
@@ -16,7 +17,7 @@ import type {
|
|
|
16
17
|
MerkleTreeWriteOperations,
|
|
17
18
|
SlasherConfig,
|
|
18
19
|
} from '@aztec/stdlib/interfaces/server';
|
|
19
|
-
import type
|
|
20
|
+
import { type L1ToL2MessageSource, computeCheckpointOutHash } from '@aztec/stdlib/messaging';
|
|
20
21
|
import { OffenseType, getOffenseTypeName } from '@aztec/stdlib/slashing';
|
|
21
22
|
import {
|
|
22
23
|
ReExFailedTxsError,
|
|
@@ -63,12 +64,12 @@ export class EpochPruneWatcher extends (EventEmitter as new () => WatcherEmitter
|
|
|
63
64
|
}
|
|
64
65
|
|
|
65
66
|
public start() {
|
|
66
|
-
this.l2BlockSource.on(L2BlockSourceEvents.L2PruneDetected, this.boundHandlePruneL2Blocks);
|
|
67
|
+
this.l2BlockSource.events.on(L2BlockSourceEvents.L2PruneDetected, this.boundHandlePruneL2Blocks);
|
|
67
68
|
return Promise.resolve();
|
|
68
69
|
}
|
|
69
70
|
|
|
70
71
|
public stop() {
|
|
71
|
-
this.l2BlockSource.removeListener(L2BlockSourceEvents.L2PruneDetected, this.boundHandlePruneL2Blocks);
|
|
72
|
+
this.l2BlockSource.events.removeListener(L2BlockSourceEvents.L2PruneDetected, this.boundHandlePruneL2Blocks);
|
|
72
73
|
return Promise.resolve();
|
|
73
74
|
}
|
|
74
75
|
|
|
@@ -95,10 +96,10 @@ export class EpochPruneWatcher extends (EventEmitter as new () => WatcherEmitter
|
|
|
95
96
|
this.emit(WANT_TO_SLASH_EVENT, args);
|
|
96
97
|
}
|
|
97
98
|
|
|
98
|
-
private async processPruneL2Blocks(blocks:
|
|
99
|
+
private async processPruneL2Blocks(blocks: L2BlockNew[], epochNumber: EpochNumber): Promise<void> {
|
|
99
100
|
try {
|
|
100
101
|
const l1Constants = this.epochCache.getL1Constants();
|
|
101
|
-
const epochBlocks = blocks.filter(b => getEpochAtSlot(b.
|
|
102
|
+
const epochBlocks = blocks.filter(b => getEpochAtSlot(b.header.getSlot(), l1Constants) === epochNumber);
|
|
102
103
|
this.log.info(
|
|
103
104
|
`Detected chain prune. Validating epoch ${epochNumber} with blocks ${epochBlocks[0]?.number} to ${epochBlocks[epochBlocks.length - 1]?.number}.`,
|
|
104
105
|
{ blocks: epochBlocks.map(b => b.toBlockInfo()) },
|
|
@@ -119,21 +120,31 @@ export class EpochPruneWatcher extends (EventEmitter as new () => WatcherEmitter
|
|
|
119
120
|
}
|
|
120
121
|
}
|
|
121
122
|
|
|
122
|
-
public async validateBlocks(blocks:
|
|
123
|
+
public async validateBlocks(blocks: L2BlockNew[]): Promise<void> {
|
|
123
124
|
if (blocks.length === 0) {
|
|
124
125
|
return;
|
|
125
126
|
}
|
|
127
|
+
|
|
128
|
+
let previousCheckpointOutHashes: Fr[] = [];
|
|
126
129
|
const fork = await this.blockBuilder.getFork(BlockNumber(blocks[0].header.globalVariables.blockNumber - 1));
|
|
127
130
|
try {
|
|
128
131
|
for (const block of blocks) {
|
|
129
|
-
await this.validateBlock(block, fork);
|
|
132
|
+
await this.validateBlock(block, previousCheckpointOutHashes, fork);
|
|
133
|
+
|
|
134
|
+
// TODO(mbps): This assumes one block per checkpoint, which is only true for now.
|
|
135
|
+
const checkpointOutHash = computeCheckpointOutHash([block.body.txEffects.map(tx => tx.l2ToL1Msgs)]);
|
|
136
|
+
previousCheckpointOutHashes = [...previousCheckpointOutHashes, checkpointOutHash];
|
|
130
137
|
}
|
|
131
138
|
} finally {
|
|
132
139
|
await fork.close();
|
|
133
140
|
}
|
|
134
141
|
}
|
|
135
142
|
|
|
136
|
-
public async validateBlock(
|
|
143
|
+
public async validateBlock(
|
|
144
|
+
blockFromL1: L2BlockNew,
|
|
145
|
+
previousCheckpointOutHashes: Fr[],
|
|
146
|
+
fork: MerkleTreeWriteOperations,
|
|
147
|
+
): Promise<void> {
|
|
137
148
|
this.log.debug(`Validating pruned block ${blockFromL1.header.globalVariables.blockNumber}`);
|
|
138
149
|
const txHashes = blockFromL1.body.txEffects.map(txEffect => txEffect.txHash);
|
|
139
150
|
// We load txs from the mempool directly, since the TxCollector running in the background has already been
|
|
@@ -150,6 +161,7 @@ export class EpochPruneWatcher extends (EventEmitter as new () => WatcherEmitter
|
|
|
150
161
|
const { block, failedTxs, numTxs } = await this.blockBuilder.buildBlock(
|
|
151
162
|
txs,
|
|
152
163
|
l1ToL2Messages,
|
|
164
|
+
previousCheckpointOutHashes,
|
|
153
165
|
blockFromL1.header.globalVariables,
|
|
154
166
|
{},
|
|
155
167
|
fork,
|