@aztec/aztec-node 2.0.0-nightly.20250902 → 3.0.0-nightly.20250904
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.
|
@@ -14,7 +14,7 @@ export declare class Sentinel extends Sentinel_base implements L2BlockStreamEven
|
|
|
14
14
|
protected archiver: L2BlockSource;
|
|
15
15
|
protected p2p: P2PClient;
|
|
16
16
|
protected store: SentinelStore;
|
|
17
|
-
protected config: Pick<SlasherConfig, 'slashInactivityTargetPercentage' | 'slashInactivityPenalty'>;
|
|
17
|
+
protected config: Pick<SlasherConfig, 'slashInactivityTargetPercentage' | 'slashInactivityPenalty' | 'slashInactivityConsecutiveEpochThreshold'>;
|
|
18
18
|
protected logger: import("@aztec/foundation/log").Logger;
|
|
19
19
|
protected runningPromise: RunningPromise;
|
|
20
20
|
protected blockStream: L2BlockStream;
|
|
@@ -26,7 +26,7 @@ export declare class Sentinel extends Sentinel_base implements L2BlockStreamEven
|
|
|
26
26
|
archive: string;
|
|
27
27
|
attestors: EthAddress[];
|
|
28
28
|
}>;
|
|
29
|
-
constructor(epochCache: EpochCache, archiver: L2BlockSource, p2p: P2PClient, store: SentinelStore, config: Pick<SlasherConfig, 'slashInactivityTargetPercentage' | 'slashInactivityPenalty'>, logger?: import("@aztec/foundation/log").Logger);
|
|
29
|
+
constructor(epochCache: EpochCache, archiver: L2BlockSource, p2p: P2PClient, store: SentinelStore, config: Pick<SlasherConfig, 'slashInactivityTargetPercentage' | 'slashInactivityPenalty' | 'slashInactivityConsecutiveEpochThreshold'>, logger?: import("@aztec/foundation/log").Logger);
|
|
30
30
|
updateConfig(config: Partial<SlasherConfig>): void;
|
|
31
31
|
start(): Promise<void>;
|
|
32
32
|
/** Loads initial slot and initializes blockstream. We will not process anything at or before the initial slot. */
|
|
@@ -36,7 +36,14 @@ export declare class Sentinel extends Sentinel_base implements L2BlockStreamEven
|
|
|
36
36
|
protected handleChainProven(event: L2BlockStreamEvent): Promise<void>;
|
|
37
37
|
protected computeProvenPerformance(epoch: bigint): Promise<ValidatorsEpochPerformance>;
|
|
38
38
|
protected updateProvenPerformance(epoch: bigint, performance: ValidatorsEpochPerformance): Promise<void>;
|
|
39
|
-
|
|
39
|
+
/**
|
|
40
|
+
* Checks if a validator has been inactive for the specified number of consecutive epochs for which we have data on it.
|
|
41
|
+
* @param validator The validator address to check
|
|
42
|
+
* @param currentEpoch Epochs strictly before the current one are evaluated only
|
|
43
|
+
* @param requiredConsecutiveEpochs Number of consecutive epochs required for slashing
|
|
44
|
+
*/
|
|
45
|
+
protected checkPastInactivity(validator: EthAddress, currentEpoch: bigint, requiredConsecutiveEpochs: number): Promise<boolean>;
|
|
46
|
+
protected handleProvenPerformance(epoch: bigint, performance: ValidatorsEpochPerformance): Promise<void>;
|
|
40
47
|
/**
|
|
41
48
|
* Process data for two L2 slots ago.
|
|
42
49
|
* Note that we do not process historical data, since we rely on p2p data for processing,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sentinel.d.ts","sourceRoot":"","sources":["../../src/sentinel/sentinel.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAC;AAErD,OAAO,EAAE,UAAU,EAAE,MAAM,+BAA+B,CAAC;AAE3D,OAAO,EAAE,cAAc,EAAE,MAAM,mCAAmC,CAAC;AACnE,OAAO,EAAqB,KAAK,WAAW,EAAE,MAAM,wBAAwB,CAAC;AAC7E,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAC5C,OAAO,EAAoC,KAAK,OAAO,EAAE,KAAK,cAAc,EAAE,MAAM,gBAAgB,CAAC;AACrG,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AAC3D,OAAO,EACL,KAAK,aAAa,EAClB,aAAa,EACb,KAAK,kBAAkB,EACvB,KAAK,yBAAyB,EAE/B,MAAM,qBAAqB,CAAC;AAE7B,OAAO,KAAK,EACV,oBAAoB,EACpB,cAAc,EACd,sBAAsB,EACtB,qBAAqB,EACrB,mBAAmB,EACnB,0BAA0B,EAC1B,eAAe,EAChB,MAAM,0BAA0B,CAAC;AAIlC,OAAO,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;6BAEI,UAAU,cAAc;AAAvE,qBAAa,QAAS,SAAQ,aAA2C,YAAW,yBAAyB,EAAE,OAAO;IAWlH,SAAS,CAAC,UAAU,EAAE,UAAU;IAChC,SAAS,CAAC,QAAQ,EAAE,aAAa;IACjC,SAAS,CAAC,GAAG,EAAE,SAAS;IACxB,SAAS,CAAC,KAAK,EAAE,aAAa;IAC9B,SAAS,CAAC,MAAM,EAAE,IAAI,
|
|
1
|
+
{"version":3,"file":"sentinel.d.ts","sourceRoot":"","sources":["../../src/sentinel/sentinel.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { countWhile } from '@aztec/foundation/collection';
|
|
1
|
+
import { countWhile, filterAsync } from '@aztec/foundation/collection';
|
|
2
2
|
import { EthAddress } from '@aztec/foundation/eth-address';
|
|
3
3
|
import { createLogger } from '@aztec/foundation/log';
|
|
4
4
|
import { RunningPromise } from '@aztec/foundation/running-promise';
|
|
@@ -87,7 +87,7 @@ export class Sentinel extends EventEmitter {
|
|
|
87
87
|
const performance = await this.computeProvenPerformance(epoch);
|
|
88
88
|
this.logger.info(`Computed proven performance for epoch ${epoch}`, performance);
|
|
89
89
|
await this.updateProvenPerformance(epoch, performance);
|
|
90
|
-
this.handleProvenPerformance(epoch, performance);
|
|
90
|
+
await this.handleProvenPerformance(epoch, performance);
|
|
91
91
|
}
|
|
92
92
|
async computeProvenPerformance(epoch) {
|
|
93
93
|
const headers = await this.archiver.getBlockHeadersForEpoch(epoch);
|
|
@@ -132,10 +132,36 @@ export class Sentinel extends EventEmitter {
|
|
|
132
132
|
updateProvenPerformance(epoch, performance) {
|
|
133
133
|
return this.store.updateProvenPerformance(epoch, performance);
|
|
134
134
|
}
|
|
135
|
-
|
|
136
|
-
|
|
135
|
+
/**
|
|
136
|
+
* Checks if a validator has been inactive for the specified number of consecutive epochs for which we have data on it.
|
|
137
|
+
* @param validator The validator address to check
|
|
138
|
+
* @param currentEpoch Epochs strictly before the current one are evaluated only
|
|
139
|
+
* @param requiredConsecutiveEpochs Number of consecutive epochs required for slashing
|
|
140
|
+
*/ async checkPastInactivity(validator, currentEpoch, requiredConsecutiveEpochs) {
|
|
141
|
+
if (requiredConsecutiveEpochs === 0) {
|
|
142
|
+
return true;
|
|
143
|
+
}
|
|
144
|
+
// Get all historical performance for this validator
|
|
145
|
+
const allPerformance = await this.store.getProvenPerformance(validator);
|
|
146
|
+
// If we don't have enough historical data, don't slash
|
|
147
|
+
if (allPerformance.length < requiredConsecutiveEpochs) {
|
|
148
|
+
this.logger.debug(`Not enough historical data for slashing ${validator} for inactivity (${allPerformance.length} epochs < ${requiredConsecutiveEpochs} required)`);
|
|
149
|
+
return false;
|
|
150
|
+
}
|
|
151
|
+
// Sort by epoch descending to get most recent first, keep only epochs strictly before the current one, and get the first N
|
|
152
|
+
return allPerformance.sort((a, b)=>Number(b.epoch - a.epoch)).filter((p)=>p.epoch < currentEpoch).slice(0, requiredConsecutiveEpochs).every((p)=>p.missed / p.total >= this.config.slashInactivityTargetPercentage);
|
|
153
|
+
}
|
|
154
|
+
async handleProvenPerformance(epoch, performance) {
|
|
155
|
+
const inactiveValidators = Object.entries(performance).filter(([_, { missed, total }])=>{
|
|
137
156
|
return missed / total >= this.config.slashInactivityTargetPercentage;
|
|
138
157
|
}).map(([address])=>address);
|
|
158
|
+
this.logger.debug(`Found ${inactiveValidators.length} inactive validators in epoch ${epoch}`, {
|
|
159
|
+
inactiveValidators,
|
|
160
|
+
epoch,
|
|
161
|
+
inactivityTargetPercentage: this.config.slashInactivityTargetPercentage
|
|
162
|
+
});
|
|
163
|
+
const epochThreshold = this.config.slashInactivityConsecutiveEpochThreshold;
|
|
164
|
+
const criminals = await filterAsync(inactiveValidators, (address)=>this.checkPastInactivity(EthAddress.fromString(address), epoch, epochThreshold - 1));
|
|
139
165
|
const args = criminals.map((address)=>({
|
|
140
166
|
validator: EthAddress.fromString(address),
|
|
141
167
|
amount: this.config.slashInactivityPenalty,
|
|
@@ -143,8 +169,9 @@ export class Sentinel extends EventEmitter {
|
|
|
143
169
|
epochOrSlot: epoch
|
|
144
170
|
}));
|
|
145
171
|
if (criminals.length > 0) {
|
|
146
|
-
this.logger.info(`Identified ${criminals.length} validators to slash due to inactivity`, {
|
|
147
|
-
args
|
|
172
|
+
this.logger.info(`Identified ${criminals.length} validators to slash due to inactivity in at least ${epochThreshold} consecutive epochs`, {
|
|
173
|
+
...args,
|
|
174
|
+
epochThreshold
|
|
148
175
|
});
|
|
149
176
|
this.emit(WANT_TO_SLASH_EVENT, args);
|
|
150
177
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aztec/aztec-node",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "3.0.0-nightly.20250904",
|
|
4
4
|
"main": "dest/index.js",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"exports": {
|
|
@@ -66,29 +66,29 @@
|
|
|
66
66
|
]
|
|
67
67
|
},
|
|
68
68
|
"dependencies": {
|
|
69
|
-
"@aztec/archiver": "
|
|
70
|
-
"@aztec/bb-prover": "
|
|
71
|
-
"@aztec/blob-sink": "
|
|
72
|
-
"@aztec/constants": "
|
|
73
|
-
"@aztec/epoch-cache": "
|
|
74
|
-
"@aztec/ethereum": "
|
|
75
|
-
"@aztec/foundation": "
|
|
76
|
-
"@aztec/kv-store": "
|
|
77
|
-
"@aztec/l1-artifacts": "
|
|
78
|
-
"@aztec/merkle-tree": "
|
|
79
|
-
"@aztec/node-keystore": "
|
|
80
|
-
"@aztec/node-lib": "
|
|
81
|
-
"@aztec/noir-protocol-circuits-types": "
|
|
82
|
-
"@aztec/p2p": "
|
|
83
|
-
"@aztec/protocol-contracts": "
|
|
84
|
-
"@aztec/prover-client": "
|
|
85
|
-
"@aztec/sequencer-client": "
|
|
86
|
-
"@aztec/simulator": "
|
|
87
|
-
"@aztec/slasher": "
|
|
88
|
-
"@aztec/stdlib": "
|
|
89
|
-
"@aztec/telemetry-client": "
|
|
90
|
-
"@aztec/validator-client": "
|
|
91
|
-
"@aztec/world-state": "
|
|
69
|
+
"@aztec/archiver": "3.0.0-nightly.20250904",
|
|
70
|
+
"@aztec/bb-prover": "3.0.0-nightly.20250904",
|
|
71
|
+
"@aztec/blob-sink": "3.0.0-nightly.20250904",
|
|
72
|
+
"@aztec/constants": "3.0.0-nightly.20250904",
|
|
73
|
+
"@aztec/epoch-cache": "3.0.0-nightly.20250904",
|
|
74
|
+
"@aztec/ethereum": "3.0.0-nightly.20250904",
|
|
75
|
+
"@aztec/foundation": "3.0.0-nightly.20250904",
|
|
76
|
+
"@aztec/kv-store": "3.0.0-nightly.20250904",
|
|
77
|
+
"@aztec/l1-artifacts": "3.0.0-nightly.20250904",
|
|
78
|
+
"@aztec/merkle-tree": "3.0.0-nightly.20250904",
|
|
79
|
+
"@aztec/node-keystore": "3.0.0-nightly.20250904",
|
|
80
|
+
"@aztec/node-lib": "3.0.0-nightly.20250904",
|
|
81
|
+
"@aztec/noir-protocol-circuits-types": "3.0.0-nightly.20250904",
|
|
82
|
+
"@aztec/p2p": "3.0.0-nightly.20250904",
|
|
83
|
+
"@aztec/protocol-contracts": "3.0.0-nightly.20250904",
|
|
84
|
+
"@aztec/prover-client": "3.0.0-nightly.20250904",
|
|
85
|
+
"@aztec/sequencer-client": "3.0.0-nightly.20250904",
|
|
86
|
+
"@aztec/simulator": "3.0.0-nightly.20250904",
|
|
87
|
+
"@aztec/slasher": "3.0.0-nightly.20250904",
|
|
88
|
+
"@aztec/stdlib": "3.0.0-nightly.20250904",
|
|
89
|
+
"@aztec/telemetry-client": "3.0.0-nightly.20250904",
|
|
90
|
+
"@aztec/validator-client": "3.0.0-nightly.20250904",
|
|
91
|
+
"@aztec/world-state": "3.0.0-nightly.20250904",
|
|
92
92
|
"koa": "^2.16.1",
|
|
93
93
|
"koa-router": "^12.0.0",
|
|
94
94
|
"tslib": "^2.4.0",
|
package/src/sentinel/sentinel.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { EpochCache } from '@aztec/epoch-cache';
|
|
2
|
-
import { countWhile } from '@aztec/foundation/collection';
|
|
2
|
+
import { countWhile, filterAsync } from '@aztec/foundation/collection';
|
|
3
3
|
import { EthAddress } from '@aztec/foundation/eth-address';
|
|
4
4
|
import { createLogger } from '@aztec/foundation/log';
|
|
5
5
|
import { RunningPromise } from '@aztec/foundation/running-promise';
|
|
@@ -44,7 +44,10 @@ export class Sentinel extends (EventEmitter as new () => WatcherEmitter) impleme
|
|
|
44
44
|
protected archiver: L2BlockSource,
|
|
45
45
|
protected p2p: P2PClient,
|
|
46
46
|
protected store: SentinelStore,
|
|
47
|
-
protected config: Pick<
|
|
47
|
+
protected config: Pick<
|
|
48
|
+
SlasherConfig,
|
|
49
|
+
'slashInactivityTargetPercentage' | 'slashInactivityPenalty' | 'slashInactivityConsecutiveEpochThreshold'
|
|
50
|
+
>,
|
|
48
51
|
protected logger = createLogger('node:sentinel'),
|
|
49
52
|
) {
|
|
50
53
|
super();
|
|
@@ -118,7 +121,7 @@ export class Sentinel extends (EventEmitter as new () => WatcherEmitter) impleme
|
|
|
118
121
|
this.logger.info(`Computed proven performance for epoch ${epoch}`, performance);
|
|
119
122
|
|
|
120
123
|
await this.updateProvenPerformance(epoch, performance);
|
|
121
|
-
this.handleProvenPerformance(epoch, performance);
|
|
124
|
+
await this.handleProvenPerformance(epoch, performance);
|
|
122
125
|
}
|
|
123
126
|
|
|
124
127
|
protected async computeProvenPerformance(epoch: bigint) {
|
|
@@ -161,13 +164,58 @@ export class Sentinel extends (EventEmitter as new () => WatcherEmitter) impleme
|
|
|
161
164
|
return this.store.updateProvenPerformance(epoch, performance);
|
|
162
165
|
}
|
|
163
166
|
|
|
164
|
-
|
|
165
|
-
|
|
167
|
+
/**
|
|
168
|
+
* Checks if a validator has been inactive for the specified number of consecutive epochs for which we have data on it.
|
|
169
|
+
* @param validator The validator address to check
|
|
170
|
+
* @param currentEpoch Epochs strictly before the current one are evaluated only
|
|
171
|
+
* @param requiredConsecutiveEpochs Number of consecutive epochs required for slashing
|
|
172
|
+
*/
|
|
173
|
+
protected async checkPastInactivity(
|
|
174
|
+
validator: EthAddress,
|
|
175
|
+
currentEpoch: bigint,
|
|
176
|
+
requiredConsecutiveEpochs: number,
|
|
177
|
+
): Promise<boolean> {
|
|
178
|
+
if (requiredConsecutiveEpochs === 0) {
|
|
179
|
+
return true;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
// Get all historical performance for this validator
|
|
183
|
+
const allPerformance = await this.store.getProvenPerformance(validator);
|
|
184
|
+
|
|
185
|
+
// If we don't have enough historical data, don't slash
|
|
186
|
+
if (allPerformance.length < requiredConsecutiveEpochs) {
|
|
187
|
+
this.logger.debug(
|
|
188
|
+
`Not enough historical data for slashing ${validator} for inactivity (${allPerformance.length} epochs < ${requiredConsecutiveEpochs} required)`,
|
|
189
|
+
);
|
|
190
|
+
return false;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
// Sort by epoch descending to get most recent first, keep only epochs strictly before the current one, and get the first N
|
|
194
|
+
return allPerformance
|
|
195
|
+
.sort((a, b) => Number(b.epoch - a.epoch))
|
|
196
|
+
.filter(p => p.epoch < currentEpoch)
|
|
197
|
+
.slice(0, requiredConsecutiveEpochs)
|
|
198
|
+
.every(p => p.missed / p.total >= this.config.slashInactivityTargetPercentage);
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
protected async handleProvenPerformance(epoch: bigint, performance: ValidatorsEpochPerformance) {
|
|
202
|
+
const inactiveValidators = Object.entries(performance)
|
|
166
203
|
.filter(([_, { missed, total }]) => {
|
|
167
204
|
return missed / total >= this.config.slashInactivityTargetPercentage;
|
|
168
205
|
})
|
|
169
206
|
.map(([address]) => address as `0x${string}`);
|
|
170
207
|
|
|
208
|
+
this.logger.debug(`Found ${inactiveValidators.length} inactive validators in epoch ${epoch}`, {
|
|
209
|
+
inactiveValidators,
|
|
210
|
+
epoch,
|
|
211
|
+
inactivityTargetPercentage: this.config.slashInactivityTargetPercentage,
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
const epochThreshold = this.config.slashInactivityConsecutiveEpochThreshold;
|
|
215
|
+
const criminals: string[] = await filterAsync(inactiveValidators, address =>
|
|
216
|
+
this.checkPastInactivity(EthAddress.fromString(address), epoch, epochThreshold - 1),
|
|
217
|
+
);
|
|
218
|
+
|
|
171
219
|
const args = criminals.map(address => ({
|
|
172
220
|
validator: EthAddress.fromString(address),
|
|
173
221
|
amount: this.config.slashInactivityPenalty,
|
|
@@ -176,7 +224,10 @@ export class Sentinel extends (EventEmitter as new () => WatcherEmitter) impleme
|
|
|
176
224
|
}));
|
|
177
225
|
|
|
178
226
|
if (criminals.length > 0) {
|
|
179
|
-
this.logger.info(
|
|
227
|
+
this.logger.info(
|
|
228
|
+
`Identified ${criminals.length} validators to slash due to inactivity in at least ${epochThreshold} consecutive epochs`,
|
|
229
|
+
{ ...args, epochThreshold },
|
|
230
|
+
);
|
|
180
231
|
this.emit(WANT_TO_SLASH_EVENT, args);
|
|
181
232
|
}
|
|
182
233
|
}
|