@aztec/aztec-node 0.0.1-commit.b655e406 → 0.0.1-commit.c0b82b2
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/aztec-node/config.d.ts +11 -5
- package/dest/aztec-node/config.d.ts.map +1 -1
- package/dest/aztec-node/config.js +17 -3
- package/dest/aztec-node/node_metrics.d.ts +1 -1
- package/dest/aztec-node/node_metrics.d.ts.map +1 -1
- package/dest/aztec-node/node_metrics.js +9 -16
- package/dest/aztec-node/server.d.ts +68 -125
- package/dest/aztec-node/server.d.ts.map +1 -1
- package/dest/aztec-node/server.js +846 -268
- package/dest/bin/index.d.ts +1 -1
- package/dest/index.d.ts +1 -1
- package/dest/sentinel/config.d.ts +1 -1
- package/dest/sentinel/factory.d.ts +1 -1
- package/dest/sentinel/factory.d.ts.map +1 -1
- package/dest/sentinel/factory.js +1 -1
- package/dest/sentinel/index.d.ts +1 -1
- package/dest/sentinel/sentinel.d.ts +22 -20
- package/dest/sentinel/sentinel.d.ts.map +1 -1
- package/dest/sentinel/sentinel.js +101 -63
- package/dest/sentinel/store.d.ts +6 -5
- package/dest/sentinel/store.d.ts.map +1 -1
- package/dest/sentinel/store.js +14 -9
- package/dest/test/index.d.ts +1 -1
- package/package.json +31 -28
- package/src/aztec-node/config.ts +34 -14
- package/src/aztec-node/node_metrics.ts +6 -17
- package/src/aztec-node/server.ts +595 -351
- package/src/sentinel/factory.ts +1 -6
- package/src/sentinel/sentinel.ts +136 -82
- package/src/sentinel/store.ts +22 -21
package/dest/bin/index.d.ts
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
#!/usr/bin/env -S node --no-warnings
|
|
2
2
|
export {};
|
|
3
|
-
//# sourceMappingURL=
|
|
3
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW5kZXguZC50cyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uL3NyYy9iaW4vaW5kZXgudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IiJ9
|
package/dest/index.d.ts
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
export * from './aztec-node/config.js';
|
|
2
2
|
export * from './aztec-node/server.js';
|
|
3
|
-
//# sourceMappingURL=
|
|
3
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW5kZXguZC50cyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uL3NyYy9pbmRleC50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxjQUFjLHdCQUF3QixDQUFDO0FBQ3ZDLGNBQWMsd0JBQXdCLENBQUMifQ==
|
|
@@ -5,4 +5,4 @@ export type SentinelConfig = {
|
|
|
5
5
|
sentinelEnabled: boolean;
|
|
6
6
|
};
|
|
7
7
|
export declare const sentinelConfigMappings: ConfigMappingsType<SentinelConfig>;
|
|
8
|
-
//# sourceMappingURL=
|
|
8
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY29uZmlnLmQudHMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi9zcmMvc2VudGluZWwvY29uZmlnLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBLE9BQU8sRUFBRSxLQUFLLGtCQUFrQixFQUEyQyxNQUFNLDBCQUEwQixDQUFDO0FBRTVHLE1BQU0sTUFBTSxjQUFjLEdBQUc7SUFDM0IsNkJBQTZCLEVBQUUsTUFBTSxDQUFDO0lBQ3RDLCtDQUErQyxFQUFFLE1BQU0sQ0FBQztJQUN4RCxlQUFlLEVBQUUsT0FBTyxDQUFDO0NBQzFCLENBQUM7QUFFRixlQUFPLE1BQU0sc0JBQXNCLEVBQUUsa0JBQWtCLENBQUMsY0FBYyxDQTRCckUsQ0FBQyJ9
|
|
@@ -6,4 +6,4 @@ import type { SlasherConfig } from '@aztec/stdlib/interfaces/server';
|
|
|
6
6
|
import type { SentinelConfig } from './config.js';
|
|
7
7
|
import { Sentinel } from './sentinel.js';
|
|
8
8
|
export declare function createSentinel(epochCache: EpochCache, archiver: L2BlockSource, p2p: P2PClient, config: SentinelConfig & DataStoreConfig & SlasherConfig, logger?: import("@aztec/foundation/log").Logger): Promise<Sentinel | undefined>;
|
|
9
|
-
//# sourceMappingURL=
|
|
9
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiZmFjdG9yeS5kLnRzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vc3JjL3NlbnRpbmVsL2ZhY3RvcnkudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsT0FBTyxLQUFLLEVBQUUsVUFBVSxFQUFFLE1BQU0sb0JBQW9CLENBQUM7QUFFckQsT0FBTyxLQUFLLEVBQUUsZUFBZSxFQUFFLE1BQU0sd0JBQXdCLENBQUM7QUFFOUQsT0FBTyxLQUFLLEVBQUUsU0FBUyxFQUFFLE1BQU0sWUFBWSxDQUFDO0FBQzVDLE9BQU8sS0FBSyxFQUFFLGFBQWEsRUFBRSxNQUFNLHFCQUFxQixDQUFDO0FBQ3pELE9BQU8sS0FBSyxFQUFFLGFBQWEsRUFBRSxNQUFNLGlDQUFpQyxDQUFDO0FBRXJFLE9BQU8sS0FBSyxFQUFFLGNBQWMsRUFBRSxNQUFNLGFBQWEsQ0FBQztBQUNsRCxPQUFPLEVBQUUsUUFBUSxFQUFFLE1BQU0sZUFBZSxDQUFDO0FBR3pDLHdCQUFzQixjQUFjLENBQ2xDLFVBQVUsRUFBRSxVQUFVLEVBQ3RCLFFBQVEsRUFBRSxhQUFhLEVBQ3ZCLEdBQUcsRUFBRSxTQUFTLEVBQ2QsTUFBTSxFQUFFLGNBQWMsR0FBRyxlQUFlLEdBQUcsYUFBYSxFQUN4RCxNQUFNLHlDQUFnQyxHQUNyQyxPQUFPLENBQUMsUUFBUSxHQUFHLFNBQVMsQ0FBQyxDQVkvQiJ9
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"factory.d.ts","sourceRoot":"","sources":["../../src/sentinel/factory.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAC;AAErD,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;AAE9D,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAC5C,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACzD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,iCAAiC,CAAC;AAErE,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAClD,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AAGzC,wBAAsB,cAAc,CAClC,UAAU,EAAE,UAAU,EACtB,QAAQ,EAAE,aAAa,EACvB,GAAG,EAAE,SAAS,EACd,MAAM,EAAE,cAAc,GAAG,eAAe,GAAG,aAAa,EACxD,MAAM,yCAAgC,GACrC,OAAO,CAAC,QAAQ,GAAG,SAAS,CAAC,
|
|
1
|
+
{"version":3,"file":"factory.d.ts","sourceRoot":"","sources":["../../src/sentinel/factory.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAC;AAErD,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;AAE9D,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAC5C,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACzD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,iCAAiC,CAAC;AAErE,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAClD,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AAGzC,wBAAsB,cAAc,CAClC,UAAU,EAAE,UAAU,EACtB,QAAQ,EAAE,aAAa,EACvB,GAAG,EAAE,SAAS,EACd,MAAM,EAAE,cAAc,GAAG,eAAe,GAAG,aAAa,EACxD,MAAM,yCAAgC,GACrC,OAAO,CAAC,QAAQ,GAAG,SAAS,CAAC,CAY/B"}
|
package/dest/sentinel/factory.js
CHANGED
|
@@ -6,7 +6,7 @@ export async function createSentinel(epochCache, archiver, p2p, config, logger =
|
|
|
6
6
|
if (!config.sentinelEnabled) {
|
|
7
7
|
return undefined;
|
|
8
8
|
}
|
|
9
|
-
const kvStore = await createStore('sentinel', SentinelStore.SCHEMA_VERSION, config,
|
|
9
|
+
const kvStore = await createStore('sentinel', SentinelStore.SCHEMA_VERSION, config, logger.getBindings());
|
|
10
10
|
const storeHistoryLength = config.sentinelHistoryLengthInEpochs * epochCache.getL1Constants().epochDuration;
|
|
11
11
|
const storeHistoricProvenPerformanceLength = config.sentinelHistoricProvenPerformanceLengthInEpochs;
|
|
12
12
|
const sentinelStore = new SentinelStore(kvStore, {
|
package/dest/sentinel/index.d.ts
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
export { Sentinel } from './sentinel.js';
|
|
2
2
|
export type { ValidatorsStats, ValidatorStats, ValidatorStatusHistory, ValidatorStatusInSlot, } from '@aztec/stdlib/validators';
|
|
3
|
-
//# sourceMappingURL=
|
|
3
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW5kZXguZC50cyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uL3NyYy9zZW50aW5lbC9pbmRleC50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxPQUFPLEVBQUUsUUFBUSxFQUFFLE1BQU0sZUFBZSxDQUFDO0FBRXpDLFlBQVksRUFDVixlQUFlLEVBQ2YsY0FBYyxFQUNkLHNCQUFzQixFQUN0QixxQkFBcUIsR0FDdEIsTUFBTSwwQkFBMEIsQ0FBQyJ9
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { EpochCache } from '@aztec/epoch-cache';
|
|
2
|
+
import { CheckpointNumber, EpochNumber, SlotNumber } from '@aztec/foundation/branded-types';
|
|
2
3
|
import { EthAddress } from '@aztec/foundation/eth-address';
|
|
3
4
|
import { RunningPromise } from '@aztec/foundation/running-promise';
|
|
4
5
|
import { type L2TipsStore } from '@aztec/kv-store/stores';
|
|
@@ -19,10 +20,10 @@ export declare class Sentinel extends Sentinel_base implements L2BlockStreamEven
|
|
|
19
20
|
protected runningPromise: RunningPromise;
|
|
20
21
|
protected blockStream: L2BlockStream;
|
|
21
22
|
protected l2TipsStore: L2TipsStore;
|
|
22
|
-
protected initialSlot:
|
|
23
|
-
protected lastProcessedSlot:
|
|
24
|
-
protected
|
|
25
|
-
|
|
23
|
+
protected initialSlot: SlotNumber | undefined;
|
|
24
|
+
protected lastProcessedSlot: SlotNumber | undefined;
|
|
25
|
+
protected slotNumberToCheckpoint: Map<SlotNumber, {
|
|
26
|
+
checkpointNumber: CheckpointNumber;
|
|
26
27
|
archive: string;
|
|
27
28
|
attestors: EthAddress[];
|
|
28
29
|
}>;
|
|
@@ -33,16 +34,17 @@ export declare class Sentinel extends Sentinel_base implements L2BlockStreamEven
|
|
|
33
34
|
protected init(): Promise<void>;
|
|
34
35
|
stop(): Promise<void>;
|
|
35
36
|
handleBlockStreamEvent(event: L2BlockStreamEvent): Promise<void>;
|
|
37
|
+
protected handleCheckpoint(event: L2BlockStreamEvent): void;
|
|
36
38
|
protected handleChainProven(event: L2BlockStreamEvent): Promise<void>;
|
|
37
|
-
protected computeProvenPerformance(epoch:
|
|
39
|
+
protected computeProvenPerformance(epoch: EpochNumber): Promise<ValidatorsEpochPerformance>;
|
|
38
40
|
/**
|
|
39
41
|
* Checks if a validator has been inactive for the specified number of consecutive epochs for which we have data on it.
|
|
40
42
|
* @param validator The validator address to check
|
|
41
43
|
* @param currentEpoch Epochs strictly before the current one are evaluated only
|
|
42
44
|
* @param requiredConsecutiveEpochs Number of consecutive epochs required for slashing
|
|
43
45
|
*/
|
|
44
|
-
protected checkPastInactivity(validator: EthAddress, currentEpoch:
|
|
45
|
-
protected handleProvenPerformance(epoch:
|
|
46
|
+
protected checkPastInactivity(validator: EthAddress, currentEpoch: EpochNumber, requiredConsecutiveEpochs: number): Promise<boolean>;
|
|
47
|
+
protected handleProvenPerformance(epoch: EpochNumber, performance: ValidatorsEpochPerformance): Promise<void>;
|
|
46
48
|
/**
|
|
47
49
|
* Process data for two L2 slots ago.
|
|
48
50
|
* Note that we do not process historical data, since we rely on p2p data for processing,
|
|
@@ -54,38 +56,38 @@ export declare class Sentinel extends Sentinel_base implements L2BlockStreamEven
|
|
|
54
56
|
* We also don't move past the archiver last synced L2 slot, as we don't want to process data that is not yet available.
|
|
55
57
|
* Last, we check the p2p is synced with the archiver, so it has pulled all attestations from it.
|
|
56
58
|
*/
|
|
57
|
-
protected isReadyToProcess(currentSlot:
|
|
59
|
+
protected isReadyToProcess(currentSlot: SlotNumber): Promise<SlotNumber | false>;
|
|
58
60
|
/**
|
|
59
61
|
* Gathers committee and proposer data for a given slot, computes slot stats,
|
|
60
62
|
* and updates overall stats.
|
|
61
63
|
*/
|
|
62
|
-
protected processSlot(slot:
|
|
64
|
+
protected processSlot(slot: SlotNumber): Promise<void>;
|
|
63
65
|
/** Computes activity for a given slot. */
|
|
64
|
-
protected getSlotActivity(slot:
|
|
66
|
+
protected getSlotActivity(slot: SlotNumber, epoch: EpochNumber, proposer: EthAddress, committee: EthAddress[]): Promise<{
|
|
65
67
|
[k: string]: ValidatorStatusInSlot | undefined;
|
|
66
68
|
}>;
|
|
67
69
|
/** Push the status for each slot for each validator. */
|
|
68
|
-
protected updateValidators(slot:
|
|
70
|
+
protected updateValidators(slot: SlotNumber, stats: Record<`0x${string}`, ValidatorStatusInSlot | undefined>): Promise<void>;
|
|
69
71
|
/** Computes stats to be returned based on stored data. */
|
|
70
|
-
computeStats({ fromSlot, toSlot, validators
|
|
71
|
-
fromSlot?:
|
|
72
|
-
toSlot?:
|
|
72
|
+
computeStats({ fromSlot, toSlot, validators }?: {
|
|
73
|
+
fromSlot?: SlotNumber;
|
|
74
|
+
toSlot?: SlotNumber;
|
|
73
75
|
validators?: EthAddress[];
|
|
74
76
|
}): Promise<ValidatorsStats>;
|
|
75
77
|
/** Computes stats for a single validator. */
|
|
76
|
-
getValidatorStats(validatorAddress: EthAddress, fromSlot?:
|
|
77
|
-
protected computeStatsForValidator(address: `0x${string}`, allHistory: ValidatorStatusHistory, fromSlot?:
|
|
78
|
-
protected computeMissed(history: ValidatorStatusHistory,
|
|
78
|
+
getValidatorStats(validatorAddress: EthAddress, fromSlot?: SlotNumber, toSlot?: SlotNumber): Promise<SingleValidatorStats | undefined>;
|
|
79
|
+
protected computeStatsForValidator(address: `0x${string}`, allHistory: ValidatorStatusHistory, fromSlot?: SlotNumber, toSlot?: SlotNumber): ValidatorStats;
|
|
80
|
+
protected computeMissed(history: ValidatorStatusHistory, computeOverCategory: ValidatorStatusType | undefined, filter: ValidatorStatusInSlot[]): {
|
|
79
81
|
currentStreak: number;
|
|
80
82
|
rate: number | undefined;
|
|
81
83
|
count: number;
|
|
82
84
|
total: number;
|
|
83
85
|
};
|
|
84
|
-
protected computeFromSlot(slot:
|
|
86
|
+
protected computeFromSlot(slot: SlotNumber | undefined): {
|
|
85
87
|
timestamp: bigint;
|
|
86
|
-
slot:
|
|
88
|
+
slot: SlotNumber;
|
|
87
89
|
date: string;
|
|
88
90
|
} | undefined;
|
|
89
91
|
}
|
|
90
92
|
export {};
|
|
91
|
-
//# sourceMappingURL=
|
|
93
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoic2VudGluZWwuZC50cyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uL3NyYy9zZW50aW5lbC9zZW50aW5lbC50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxPQUFPLEtBQUssRUFBRSxVQUFVLEVBQUUsTUFBTSxvQkFBb0IsQ0FBQztBQUNyRCxPQUFPLEVBQWUsZ0JBQWdCLEVBQUUsV0FBVyxFQUFFLFVBQVUsRUFBRSxNQUFNLGlDQUFpQyxDQUFDO0FBRXpHLE9BQU8sRUFBRSxVQUFVLEVBQUUsTUFBTSwrQkFBK0IsQ0FBQztBQUUzRCxPQUFPLEVBQUUsY0FBYyxFQUFFLE1BQU0sbUNBQW1DLENBQUM7QUFDbkUsT0FBTyxFQUFxQixLQUFLLFdBQVcsRUFBRSxNQUFNLHdCQUF3QixDQUFDO0FBQzdFLE9BQU8sS0FBSyxFQUFFLFNBQVMsRUFBRSxNQUFNLFlBQVksQ0FBQztBQUM1QyxPQUFPLEVBSUwsS0FBSyxPQUFPLEVBQ1osS0FBSyxjQUFjLEVBQ3BCLE1BQU0sZ0JBQWdCLENBQUM7QUFDeEIsT0FBTyxLQUFLLEVBQUUsYUFBYSxFQUFFLE1BQU0sdUJBQXVCLENBQUM7QUFDM0QsT0FBTyxFQUNMLEtBQUssYUFBYSxFQUNsQixhQUFhLEVBQ2IsS0FBSyxrQkFBa0IsRUFDdkIsS0FBSyx5QkFBeUIsRUFFL0IsTUFBTSxxQkFBcUIsQ0FBQztBQUU3QixPQUFPLEtBQUssRUFDVixvQkFBb0IsRUFDcEIsY0FBYyxFQUNkLHNCQUFzQixFQUN0QixxQkFBcUIsRUFDckIsbUJBQW1CLEVBQ25CLDBCQUEwQixFQUMxQixlQUFlLEVBQ2hCLE1BQU0sMEJBQTBCLENBQUM7QUFJbEMsT0FBTyxFQUFFLGFBQWEsRUFBRSxNQUFNLFlBQVksQ0FBQzs7QUFhM0MscUJBQWEsUUFBUyxTQUFRLGFBQTJDLFlBQVcseUJBQXlCLEVBQUUsT0FBTztJQWNsSCxTQUFTLENBQUMsVUFBVSxFQUFFLFVBQVU7SUFDaEMsU0FBUyxDQUFDLFFBQVEsRUFBRSxhQUFhO0lBQ2pDLFNBQVMsQ0FBQyxHQUFHLEVBQUUsU0FBUztJQUN4QixTQUFTLENBQUMsS0FBSyxFQUFFLGFBQWE7SUFDOUIsU0FBUyxDQUFDLE1BQU0sRUFBRSxJQUFJLENBQ3BCLGFBQWEsRUFDYixpQ0FBaUMsR0FBRyx3QkFBd0IsR0FBRywwQ0FBMEMsQ0FDMUc7SUFDRCxTQUFTLENBQUMsTUFBTTtJQXJCbEIsU0FBUyxDQUFDLGNBQWMsRUFBRSxjQUFjLENBQUM7SUFDekMsU0FBUyxDQUFDLFdBQVcsRUFBRyxhQUFhLENBQUM7SUFDdEMsU0FBUyxDQUFDLFdBQVcsRUFBRSxXQUFXLENBQUM7SUFFbkMsU0FBUyxDQUFDLFdBQVcsRUFBRSxVQUFVLEdBQUcsU0FBUyxDQUFDO0lBQzlDLFNBQVMsQ0FBQyxpQkFBaUIsRUFBRSxVQUFVLEdBQUcsU0FBUyxDQUFDO0lBRXBELFNBQVMsQ0FBQyxzQkFBc0IsRUFBRSxHQUFHLENBQ25DLFVBQVUsRUFDVjtRQUFFLGdCQUFnQixFQUFFLGdCQUFnQixDQUFDO1FBQUMsT0FBTyxFQUFFLE1BQU0sQ0FBQztRQUFDLFNBQVMsRUFBRSxVQUFVLEVBQUUsQ0FBQTtLQUFFLENBQ2pGLENBQWE7SUFFZCxZQUNZLFVBQVUsRUFBRSxVQUFVLEVBQ3RCLFFBQVEsRUFBRSxhQUFhLEVBQ3ZCLEdBQUcsRUFBRSxTQUFTLEVBQ2QsS0FBSyxFQUFFLGFBQWEsRUFDcEIsTUFBTSxFQUFFLElBQUksQ0FDcEIsYUFBYSxFQUNiLGlDQUFpQyxHQUFHLHdCQUF3QixHQUFHLDBDQUEwQyxDQUMxRyxFQUNTLE1BQU0seUNBQWdDLEVBTWpEO0lBRU0sWUFBWSxDQUFDLE1BQU0sRUFBRSxPQUFPLENBQUMsYUFBYSxDQUFDLFFBRWpEO0lBRVksS0FBSyxrQkFHakI7SUFFRCxrSEFBa0g7SUFDbEgsVUFBZ0IsSUFBSSxrQkFLbkI7SUFFTSxJQUFJLGtCQUVWO0lBRVksc0JBQXNCLENBQUMsS0FBSyxFQUFFLGtCQUFrQixHQUFHLE9BQU8sQ0FBQyxJQUFJLENBQUMsQ0FPNUU7SUFFRCxTQUFTLENBQUMsZ0JBQWdCLENBQUMsS0FBSyxFQUFFLGtCQUFrQixRQXlCbkQ7SUFFRCxVQUFnQixpQkFBaUIsQ0FBQyxLQUFLLEVBQUUsa0JBQWtCLGlCQW9CMUQ7SUFFRCxVQUFnQix3QkFBd0IsQ0FBQyxLQUFLLEVBQUUsV0FBVyxHQUFHLE9BQU8sQ0FBQywwQkFBMEIsQ0FBQyxDQTZCaEc7SUFFRDs7Ozs7T0FLRztJQUNILFVBQWdCLG1CQUFtQixDQUNqQyxTQUFTLEVBQUUsVUFBVSxFQUNyQixZQUFZLEVBQUUsV0FBVyxFQUN6Qix5QkFBeUIsRUFBRSxNQUFNLEdBQ2hDLE9BQU8sQ0FBQyxPQUFPLENBQUMsQ0F1QmxCO0lBRUQsVUFBZ0IsdUJBQXVCLENBQUMsS0FBSyxFQUFFLFdBQVcsRUFBRSxXQUFXLEVBQUUsMEJBQTBCLGlCQWtDbEc7SUFFRDs7OztPQUlHO0lBQ1UsSUFBSSxrQkFpQmhCO0lBRUQ7Ozs7T0FJRztJQUNILFVBQWdCLGdCQUFnQixDQUFDLFdBQVcsRUFBRSxVQUFVLEdBQUcsT0FBTyxDQUFDLFVBQVUsR0FBRyxLQUFLLENBQUMsQ0FxQ3JGO0lBRUQ7OztPQUdHO0lBQ0gsVUFBZ0IsV0FBVyxDQUFDLElBQUksRUFBRSxVQUFVLGlCQWtCM0M7SUFFRCwwQ0FBMEM7SUFDMUMsVUFBZ0IsZUFBZSxDQUFDLElBQUksRUFBRSxVQUFVLEVBQUUsS0FBSyxFQUFFLFdBQVcsRUFBRSxRQUFRLEVBQUUsVUFBVSxFQUFFLFNBQVMsRUFBRSxVQUFVLEVBQUU7O09Bb0VsSDtJQUVELHdEQUF3RDtJQUN4RCxTQUFTLENBQUMsZ0JBQWdCLENBQUMsSUFBSSxFQUFFLFVBQVUsRUFBRSxLQUFLLEVBQUUsTUFBTSxDQUFDLEtBQUssTUFBTSxFQUFFLEVBQUUscUJBQXFCLEdBQUcsU0FBUyxDQUFDLGlCQUUzRztJQUVELDBEQUEwRDtJQUM3QyxZQUFZLENBQUMsRUFDeEIsUUFBUSxFQUNSLE1BQU0sRUFDTixVQUFVLEVBQ1gsR0FBRTtRQUFFLFFBQVEsQ0FBQyxFQUFFLFVBQVUsQ0FBQztRQUFDLE1BQU0sQ0FBQyxFQUFFLFVBQVUsQ0FBQztRQUFDLFVBQVUsQ0FBQyxFQUFFLFVBQVUsRUFBRSxDQUFBO0tBQU8sR0FBRyxPQUFPLENBQUMsZUFBZSxDQUFDLENBbUIzRztJQUVELDZDQUE2QztJQUNoQyxpQkFBaUIsQ0FDNUIsZ0JBQWdCLEVBQUUsVUFBVSxFQUM1QixRQUFRLENBQUMsRUFBRSxVQUFVLEVBQ3JCLE1BQU0sQ0FBQyxFQUFFLFVBQVUsR0FDbEIsT0FBTyxDQUFDLG9CQUFvQixHQUFHLFNBQVMsQ0FBQyxDQWtDM0M7SUFFRCxTQUFTLENBQUMsd0JBQXdCLENBQ2hDLE9BQU8sRUFBRSxLQUFLLE1BQU0sRUFBRSxFQUN0QixVQUFVLEVBQUUsc0JBQXNCLEVBQ2xDLFFBQVEsQ0FBQyxFQUFFLFVBQVUsRUFDckIsTUFBTSxDQUFDLEVBQUUsVUFBVSxHQUNsQixjQUFjLENBZ0JoQjtJQUVELFNBQVMsQ0FBQyxhQUFhLENBQ3JCLE9BQU8sRUFBRSxzQkFBc0IsRUFDL0IsbUJBQW1CLEVBQUUsbUJBQW1CLEdBQUcsU0FBUyxFQUNwRCxNQUFNLEVBQUUscUJBQXFCLEVBQUU7Ozs7O01BWWhDO0lBRUQsU0FBUyxDQUFDLGVBQWUsQ0FBQyxJQUFJLEVBQUUsVUFBVSxHQUFHLFNBQVM7Ozs7a0JBTXJEO0NBQ0YifQ==
|
|
@@ -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;
|
|
1
|
+
{"version":3,"file":"sentinel.d.ts","sourceRoot":"","sources":["../../src/sentinel/sentinel.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,12 +1,22 @@
|
|
|
1
|
+
import { BlockNumber, SlotNumber } from '@aztec/foundation/branded-types';
|
|
1
2
|
import { countWhile, filterAsync, fromEntries, getEntries, mapValues } from '@aztec/foundation/collection';
|
|
2
3
|
import { EthAddress } from '@aztec/foundation/eth-address';
|
|
3
4
|
import { createLogger } from '@aztec/foundation/log';
|
|
4
5
|
import { RunningPromise } from '@aztec/foundation/running-promise';
|
|
5
6
|
import { L2TipsMemoryStore } from '@aztec/kv-store/stores';
|
|
6
7
|
import { OffenseType, WANT_TO_SLASH_EVENT } from '@aztec/slasher';
|
|
7
|
-
import { L2BlockStream,
|
|
8
|
+
import { L2BlockStream, getAttestationInfoFromPublishedCheckpoint } from '@aztec/stdlib/block';
|
|
8
9
|
import { getEpochAtSlot, getSlotRangeForEpoch, getTimestampForSlot } from '@aztec/stdlib/epoch-helpers';
|
|
9
10
|
import EventEmitter from 'node:events';
|
|
11
|
+
/** Maps a validator status to its category: proposer or attestation. */ function statusToCategory(status) {
|
|
12
|
+
switch(status){
|
|
13
|
+
case 'attestation-sent':
|
|
14
|
+
case 'attestation-missed':
|
|
15
|
+
return 'attestation';
|
|
16
|
+
default:
|
|
17
|
+
return 'proposer';
|
|
18
|
+
}
|
|
19
|
+
}
|
|
10
20
|
export class Sentinel extends EventEmitter {
|
|
11
21
|
epochCache;
|
|
12
22
|
archiver;
|
|
@@ -19,9 +29,10 @@ export class Sentinel extends EventEmitter {
|
|
|
19
29
|
l2TipsStore;
|
|
20
30
|
initialSlot;
|
|
21
31
|
lastProcessedSlot;
|
|
22
|
-
|
|
32
|
+
// eslint-disable-next-line aztec-custom/no-non-primitive-in-collections
|
|
33
|
+
slotNumberToCheckpoint;
|
|
23
34
|
constructor(epochCache, archiver, p2p, store, config, logger = createLogger('node:sentinel')){
|
|
24
|
-
super(), this.epochCache = epochCache, this.archiver = archiver, this.p2p = p2p, this.store = store, this.config = config, this.logger = logger, this.
|
|
35
|
+
super(), this.epochCache = epochCache, this.archiver = archiver, this.p2p = p2p, this.store = store, this.config = config, this.logger = logger, this.slotNumberToCheckpoint = new Map();
|
|
25
36
|
this.l2TipsStore = new L2TipsMemoryStore();
|
|
26
37
|
const interval = epochCache.getL1Constants().ethereumSlotDuration * 1000 / 4;
|
|
27
38
|
this.runningPromise = new RunningPromise(this.work.bind(this), logger, interval);
|
|
@@ -38,7 +49,7 @@ export class Sentinel extends EventEmitter {
|
|
|
38
49
|
}
|
|
39
50
|
/** Loads initial slot and initializes blockstream. We will not process anything at or before the initial slot. */ async init() {
|
|
40
51
|
this.initialSlot = this.epochCache.getEpochAndSlotNow().slot;
|
|
41
|
-
const startingBlock = await this.archiver.getBlockNumber();
|
|
52
|
+
const startingBlock = BlockNumber(await this.archiver.getBlockNumber());
|
|
42
53
|
this.logger.info(`Starting validator sentinel with initial slot ${this.initialSlot} and block ${startingBlock}`);
|
|
43
54
|
this.blockStream = new L2BlockStream(this.archiver, this.l2TipsStore, this, this.logger, {
|
|
44
55
|
startingBlock
|
|
@@ -49,42 +60,45 @@ export class Sentinel extends EventEmitter {
|
|
|
49
60
|
}
|
|
50
61
|
async handleBlockStreamEvent(event) {
|
|
51
62
|
await this.l2TipsStore.handleBlockStreamEvent(event);
|
|
52
|
-
if (event.type === '
|
|
53
|
-
|
|
54
|
-
for (const block of event.blocks){
|
|
55
|
-
this.slotNumberToBlock.set(block.block.header.getSlot(), {
|
|
56
|
-
blockNumber: block.block.number,
|
|
57
|
-
archive: block.block.archive.root.toString(),
|
|
58
|
-
attestors: getAttestationInfoFromPublishedL2Block(block).filter((a)=>a.status === 'recovered-from-signature').map((a)=>a.address)
|
|
59
|
-
});
|
|
60
|
-
}
|
|
61
|
-
// Prune the archive map to only keep at most N entries
|
|
62
|
-
const historyLength = this.store.getHistoryLength();
|
|
63
|
-
if (this.slotNumberToBlock.size > historyLength) {
|
|
64
|
-
const toDelete = Array.from(this.slotNumberToBlock.keys()).sort((a, b)=>Number(a - b)).slice(0, this.slotNumberToBlock.size - historyLength);
|
|
65
|
-
for (const key of toDelete){
|
|
66
|
-
this.slotNumberToBlock.delete(key);
|
|
67
|
-
}
|
|
68
|
-
}
|
|
63
|
+
if (event.type === 'chain-checkpointed') {
|
|
64
|
+
this.handleCheckpoint(event);
|
|
69
65
|
} else if (event.type === 'chain-proven') {
|
|
70
66
|
await this.handleChainProven(event);
|
|
71
67
|
}
|
|
72
68
|
}
|
|
69
|
+
handleCheckpoint(event) {
|
|
70
|
+
if (event.type !== 'chain-checkpointed') {
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
const checkpoint = event.checkpoint;
|
|
74
|
+
// Store mapping from slot to archive, checkpoint number, and attestors
|
|
75
|
+
this.slotNumberToCheckpoint.set(checkpoint.checkpoint.header.slotNumber, {
|
|
76
|
+
checkpointNumber: checkpoint.checkpoint.number,
|
|
77
|
+
archive: checkpoint.checkpoint.archive.root.toString(),
|
|
78
|
+
attestors: getAttestationInfoFromPublishedCheckpoint(checkpoint).filter((a)=>a.status === 'recovered-from-signature').map((a)=>a.address)
|
|
79
|
+
});
|
|
80
|
+
// Prune the archive map to only keep at most N entries
|
|
81
|
+
const historyLength = this.store.getHistoryLength();
|
|
82
|
+
if (this.slotNumberToCheckpoint.size > historyLength) {
|
|
83
|
+
const toDelete = Array.from(this.slotNumberToCheckpoint.keys()).sort((a, b)=>Number(a - b)).slice(0, this.slotNumberToCheckpoint.size - historyLength);
|
|
84
|
+
for (const key of toDelete){
|
|
85
|
+
this.slotNumberToCheckpoint.delete(key);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
73
89
|
async handleChainProven(event) {
|
|
74
90
|
if (event.type !== 'chain-proven') {
|
|
75
91
|
return;
|
|
76
92
|
}
|
|
77
93
|
const blockNumber = event.block.number;
|
|
78
|
-
const
|
|
79
|
-
if (!
|
|
80
|
-
this.logger.error(`Failed to get block ${blockNumber}
|
|
81
|
-
block
|
|
82
|
-
});
|
|
94
|
+
const header = await this.archiver.getBlockHeader(blockNumber);
|
|
95
|
+
if (!header) {
|
|
96
|
+
this.logger.error(`Failed to get block header ${blockNumber}`);
|
|
83
97
|
return;
|
|
84
98
|
}
|
|
85
99
|
// TODO(palla/slash): We should only be computing proven performance if this is
|
|
86
100
|
// a full proof epoch and not a partial one, otherwise we'll end up with skewed stats.
|
|
87
|
-
const epoch = getEpochAtSlot(
|
|
101
|
+
const epoch = getEpochAtSlot(header.getSlot(), this.epochCache.getL1Constants());
|
|
88
102
|
this.logger.debug(`Computing proven performance for epoch ${epoch}`);
|
|
89
103
|
const performance = await this.computeProvenPerformance(epoch);
|
|
90
104
|
this.logger.info(`Computed proven performance for epoch ${epoch}`, performance);
|
|
@@ -93,7 +107,11 @@ export class Sentinel extends EventEmitter {
|
|
|
93
107
|
}
|
|
94
108
|
async computeProvenPerformance(epoch) {
|
|
95
109
|
const [fromSlot, toSlot] = getSlotRangeForEpoch(epoch, this.epochCache.getL1Constants());
|
|
96
|
-
const { committee } = await this.epochCache.getCommittee(fromSlot);
|
|
110
|
+
const { committee, isEscapeHatchOpen } = await this.epochCache.getCommittee(fromSlot);
|
|
111
|
+
if (isEscapeHatchOpen) {
|
|
112
|
+
this.logger.info(`Skipping proven performance for epoch ${epoch} - escape hatch is open`);
|
|
113
|
+
return {};
|
|
114
|
+
}
|
|
97
115
|
if (!committee) {
|
|
98
116
|
this.logger.trace(`No committee found for slot ${fromSlot}`);
|
|
99
117
|
return {};
|
|
@@ -131,13 +149,15 @@ export class Sentinel extends EventEmitter {
|
|
|
131
149
|
}
|
|
132
150
|
// Get all historical performance for this validator
|
|
133
151
|
const allPerformance = await this.store.getProvenPerformance(validator);
|
|
152
|
+
// Sort by epoch descending to get most recent first, keep only epochs strictly before the current one, and get the first N
|
|
153
|
+
const pastEpochs = allPerformance.sort((a, b)=>Number(b.epoch - a.epoch)).filter((p)=>p.epoch < currentEpoch);
|
|
134
154
|
// If we don't have enough historical data, don't slash
|
|
135
|
-
if (
|
|
155
|
+
if (pastEpochs.length < requiredConsecutiveEpochs) {
|
|
136
156
|
this.logger.debug(`Not enough historical data for slashing ${validator} for inactivity (${allPerformance.length} epochs < ${requiredConsecutiveEpochs} required)`);
|
|
137
157
|
return false;
|
|
138
158
|
}
|
|
139
|
-
//
|
|
140
|
-
return
|
|
159
|
+
// Check that we have at least requiredConsecutiveEpochs and that all of them are above the inactivity threshold
|
|
160
|
+
return pastEpochs.slice(0, requiredConsecutiveEpochs).every((p)=>p.missed / p.total >= this.config.slashInactivityTargetPercentage);
|
|
141
161
|
}
|
|
142
162
|
async handleProvenPerformance(epoch, performance) {
|
|
143
163
|
if (this.config.slashInactivityPenalty === 0n) {
|
|
@@ -155,7 +175,7 @@ export class Sentinel extends EventEmitter {
|
|
|
155
175
|
validator: EthAddress.fromString(address),
|
|
156
176
|
amount: this.config.slashInactivityPenalty,
|
|
157
177
|
offenseType: OffenseType.INACTIVITY,
|
|
158
|
-
epochOrSlot: epoch
|
|
178
|
+
epochOrSlot: BigInt(epoch)
|
|
159
179
|
}));
|
|
160
180
|
if (criminals.length > 0) {
|
|
161
181
|
this.logger.verbose(`Identified ${criminals.length} validators to slash due to inactivity in at least ${epochThreshold} consecutive epochs`, {
|
|
@@ -190,7 +210,11 @@ export class Sentinel extends EventEmitter {
|
|
|
190
210
|
* We also don't move past the archiver last synced L2 slot, as we don't want to process data that is not yet available.
|
|
191
211
|
* Last, we check the p2p is synced with the archiver, so it has pulled all attestations from it.
|
|
192
212
|
*/ async isReadyToProcess(currentSlot) {
|
|
193
|
-
|
|
213
|
+
if (currentSlot < 2) {
|
|
214
|
+
this.logger.trace(`Current slot ${currentSlot} too early.`);
|
|
215
|
+
return false;
|
|
216
|
+
}
|
|
217
|
+
const targetSlot = SlotNumber(currentSlot - 2);
|
|
194
218
|
if (this.lastProcessedSlot && this.lastProcessedSlot >= targetSlot) {
|
|
195
219
|
this.logger.trace(`Already processed slot ${targetSlot}`, {
|
|
196
220
|
lastProcessedSlot: this.lastProcessedSlot
|
|
@@ -213,8 +237,8 @@ export class Sentinel extends EventEmitter {
|
|
|
213
237
|
});
|
|
214
238
|
return false;
|
|
215
239
|
}
|
|
216
|
-
const archiverLastBlockHash = await this.l2TipsStore.getL2Tips().then((tip)=>tip.
|
|
217
|
-
const p2pLastBlockHash = await this.p2p.getL2Tips().then((tips)=>tips.
|
|
240
|
+
const archiverLastBlockHash = await this.l2TipsStore.getL2Tips().then((tip)=>tip.proposed.hash);
|
|
241
|
+
const p2pLastBlockHash = await this.p2p.getL2Tips().then((tips)=>tips.proposed.hash);
|
|
218
242
|
const isP2pSynced = archiverLastBlockHash === p2pLastBlockHash;
|
|
219
243
|
if (!isP2pSynced) {
|
|
220
244
|
this.logger.debug(`Waiting for P2P client to sync with archiver`, {
|
|
@@ -229,7 +253,12 @@ export class Sentinel extends EventEmitter {
|
|
|
229
253
|
* Gathers committee and proposer data for a given slot, computes slot stats,
|
|
230
254
|
* and updates overall stats.
|
|
231
255
|
*/ async processSlot(slot) {
|
|
232
|
-
const { epoch, seed, committee } = await this.epochCache.getCommittee(slot);
|
|
256
|
+
const { epoch, seed, committee, isEscapeHatchOpen } = await this.epochCache.getCommittee(slot);
|
|
257
|
+
if (isEscapeHatchOpen) {
|
|
258
|
+
this.logger.info(`Skipping slot ${slot} at epoch ${epoch} - escape hatch is open`);
|
|
259
|
+
this.lastProcessedSlot = slot;
|
|
260
|
+
return;
|
|
261
|
+
}
|
|
233
262
|
if (!committee || committee.length === 0) {
|
|
234
263
|
this.logger.trace(`No committee found for slot ${slot} at epoch ${epoch}`);
|
|
235
264
|
this.lastProcessedSlot = slot;
|
|
@@ -250,17 +279,17 @@ export class Sentinel extends EventEmitter {
|
|
|
250
279
|
committee
|
|
251
280
|
});
|
|
252
281
|
// Check if there is an L2 block in L1 for this L2 slot
|
|
253
|
-
// Here we get all attestations for the
|
|
254
|
-
// or all attestations for all proposals in the slot if no
|
|
282
|
+
// Here we get all checkpoint attestations for the checkpoint at the given slot,
|
|
283
|
+
// or all checkpoint attestations for all proposals in the slot if no checkpoint was mined.
|
|
255
284
|
// We gather from both p2p (contains the ones seen on the p2p layer) and archiver
|
|
256
|
-
// (contains the ones synced from mined
|
|
257
|
-
const
|
|
258
|
-
const p2pAttested = await this.p2p.
|
|
285
|
+
// (contains the ones synced from mined checkpoints, which we may have missed from p2p).
|
|
286
|
+
const checkpoint = this.slotNumberToCheckpoint.get(slot);
|
|
287
|
+
const p2pAttested = await this.p2p.getCheckpointAttestationsForSlot(slot, checkpoint?.archive);
|
|
259
288
|
// Filter out attestations with invalid signatures
|
|
260
289
|
const p2pAttestors = p2pAttested.map((a)=>a.getSender()).filter((s)=>s !== undefined);
|
|
261
290
|
const attestors = new Set([
|
|
262
291
|
...p2pAttestors.map((a)=>a.toString()),
|
|
263
|
-
...
|
|
292
|
+
...checkpoint?.attestors.map((a)=>a.toString()) ?? []
|
|
264
293
|
].filter((addr)=>proposer.toString() !== addr));
|
|
265
294
|
// We assume that there was a block proposal if at least one of the validators (other than the proposer) attested to it.
|
|
266
295
|
// It could be the case that every single validator failed, and we could differentiate it by having
|
|
@@ -268,17 +297,26 @@ export class Sentinel extends EventEmitter {
|
|
|
268
297
|
// But we'll leave that corner case out to reduce pressure on the node.
|
|
269
298
|
// TODO(palla/slash): This breaks if a given node has more than one validator in the current committee,
|
|
270
299
|
// since they will attest to their own proposal it even if it's not re-executable.
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
300
|
+
let status;
|
|
301
|
+
if (checkpoint) {
|
|
302
|
+
status = 'checkpoint-mined';
|
|
303
|
+
} else if (attestors.size > 0) {
|
|
304
|
+
status = 'checkpoint-proposed';
|
|
305
|
+
} else {
|
|
306
|
+
// No checkpoint on L1 and no checkpoint attestations seen. Check if block proposals were sent for this slot.
|
|
307
|
+
const hasBlockProposals = await this.p2p.hasBlockProposalsForSlot(slot);
|
|
308
|
+
status = hasBlockProposals ? 'checkpoint-missed' : 'blocks-missed';
|
|
309
|
+
}
|
|
310
|
+
this.logger.debug(`Checkpoint status for slot ${slot}: ${status}`, {
|
|
311
|
+
...checkpoint,
|
|
274
312
|
slot
|
|
275
313
|
});
|
|
276
|
-
// Get attestors that failed their
|
|
277
|
-
const missedAttestors = new Set(
|
|
314
|
+
// Get attestors that failed their checkpoint attestation duties, but only if there was a checkpoint proposed or mined
|
|
315
|
+
const missedAttestors = new Set(status === 'blocks-missed' || status === 'checkpoint-missed' ? [] : committee.filter((v)=>!attestors.has(v.toString()) && !proposer.equals(v)).map((v)=>v.toString()));
|
|
278
316
|
this.logger.debug(`Retrieved ${attestors.size} attestors out of ${committee.length} for slot ${slot}`, {
|
|
279
|
-
|
|
317
|
+
status,
|
|
280
318
|
proposer: proposer.toString(),
|
|
281
|
-
...
|
|
319
|
+
...checkpoint,
|
|
282
320
|
slot,
|
|
283
321
|
attestors: [
|
|
284
322
|
...attestors
|
|
@@ -291,7 +329,7 @@ export class Sentinel extends EventEmitter {
|
|
|
291
329
|
// Compute the status for each validator in the committee
|
|
292
330
|
const statusFor = (who)=>{
|
|
293
331
|
if (who === proposer.toString()) {
|
|
294
|
-
return
|
|
332
|
+
return status;
|
|
295
333
|
} else if (attestors.has(who)) {
|
|
296
334
|
return 'attestation-sent';
|
|
297
335
|
} else if (missedAttestors.has(who)) {
|
|
@@ -314,7 +352,7 @@ export class Sentinel extends EventEmitter {
|
|
|
314
352
|
await this.store.getHistory(v)
|
|
315
353
|
]))) : await this.store.getHistories();
|
|
316
354
|
const slotNow = this.epochCache.getEpochAndSlotNow().slot;
|
|
317
|
-
fromSlot ??= (this.lastProcessedSlot ?? slotNow) -
|
|
355
|
+
fromSlot ??= SlotNumber(Math.max((this.lastProcessedSlot ?? slotNow) - this.store.getHistoryLength(), 0));
|
|
318
356
|
toSlot ??= this.lastProcessedSlot ?? slotNow;
|
|
319
357
|
const stats = mapValues(histories, (history, address)=>this.computeStatsForValidator(address, history ?? [], fromSlot, toSlot));
|
|
320
358
|
return {
|
|
@@ -330,34 +368,34 @@ export class Sentinel extends EventEmitter {
|
|
|
330
368
|
return undefined;
|
|
331
369
|
}
|
|
332
370
|
const slotNow = this.epochCache.getEpochAndSlotNow().slot;
|
|
333
|
-
const effectiveFromSlot = fromSlot ?? (this.lastProcessedSlot ?? slotNow) -
|
|
371
|
+
const effectiveFromSlot = fromSlot ?? SlotNumber(Math.max((this.lastProcessedSlot ?? slotNow) - this.store.getHistoryLength(), 0));
|
|
334
372
|
const effectiveToSlot = toSlot ?? this.lastProcessedSlot ?? slotNow;
|
|
335
373
|
const historyLength = BigInt(this.store.getHistoryLength());
|
|
336
|
-
if (effectiveToSlot - effectiveFromSlot > historyLength) {
|
|
337
|
-
throw new Error(`Slot range (${effectiveToSlot - effectiveFromSlot}) exceeds history length (${historyLength}). ` + `Requested range: ${effectiveFromSlot} to ${effectiveToSlot}.`);
|
|
374
|
+
if (BigInt(effectiveToSlot) - BigInt(effectiveFromSlot) > historyLength) {
|
|
375
|
+
throw new Error(`Slot range (${BigInt(effectiveToSlot) - BigInt(effectiveFromSlot)}) exceeds history length (${historyLength}). ` + `Requested range: ${effectiveFromSlot} to ${effectiveToSlot}.`);
|
|
338
376
|
}
|
|
339
377
|
const validator = this.computeStatsForValidator(validatorAddress.toString(), history, effectiveFromSlot, effectiveToSlot);
|
|
340
|
-
const allTimeProvenPerformance = await this.store.getProvenPerformance(validatorAddress);
|
|
341
378
|
return {
|
|
342
379
|
validator,
|
|
343
|
-
allTimeProvenPerformance,
|
|
380
|
+
allTimeProvenPerformance: await this.store.getProvenPerformance(validatorAddress),
|
|
344
381
|
lastProcessedSlot: this.lastProcessedSlot,
|
|
345
382
|
initialSlot: this.initialSlot,
|
|
346
383
|
slotWindow: this.store.getHistoryLength()
|
|
347
384
|
};
|
|
348
385
|
}
|
|
349
386
|
computeStatsForValidator(address, allHistory, fromSlot, toSlot) {
|
|
350
|
-
let history = fromSlot ? allHistory.filter((h)=>h.slot >= fromSlot) : allHistory;
|
|
351
|
-
history = toSlot ? history.filter((h)=>h.slot <= toSlot) : history;
|
|
352
|
-
const lastProposal = history.filter((h)=>h.status === '
|
|
387
|
+
let history = fromSlot ? allHistory.filter((h)=>BigInt(h.slot) >= fromSlot) : allHistory;
|
|
388
|
+
history = toSlot ? history.filter((h)=>BigInt(h.slot) <= toSlot) : history;
|
|
389
|
+
const lastProposal = history.filter((h)=>h.status === 'checkpoint-proposed' || h.status === 'checkpoint-mined').at(-1);
|
|
353
390
|
const lastAttestation = history.filter((h)=>h.status === 'attestation-sent').at(-1);
|
|
354
391
|
return {
|
|
355
392
|
address: EthAddress.fromString(address),
|
|
356
393
|
lastProposal: this.computeFromSlot(lastProposal?.slot),
|
|
357
394
|
lastAttestation: this.computeFromSlot(lastAttestation?.slot),
|
|
358
395
|
totalSlots: history.length,
|
|
359
|
-
missedProposals: this.computeMissed(history, '
|
|
360
|
-
'
|
|
396
|
+
missedProposals: this.computeMissed(history, 'proposer', [
|
|
397
|
+
'checkpoint-missed',
|
|
398
|
+
'blocks-missed'
|
|
361
399
|
]),
|
|
362
400
|
missedAttestations: this.computeMissed(history, 'attestation', [
|
|
363
401
|
'attestation-missed'
|
|
@@ -365,8 +403,8 @@ export class Sentinel extends EventEmitter {
|
|
|
365
403
|
history
|
|
366
404
|
};
|
|
367
405
|
}
|
|
368
|
-
computeMissed(history,
|
|
369
|
-
const relevantHistory = history.filter((h)=>!
|
|
406
|
+
computeMissed(history, computeOverCategory, filter) {
|
|
407
|
+
const relevantHistory = history.filter((h)=>!computeOverCategory || statusToCategory(h.status) === computeOverCategory);
|
|
370
408
|
const filteredHistory = relevantHistory.filter((h)=>filter.includes(h.status));
|
|
371
409
|
return {
|
|
372
410
|
currentStreak: countWhile([
|
package/dest/sentinel/store.d.ts
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
|
+
import { EpochNumber, SlotNumber } from '@aztec/foundation/branded-types';
|
|
1
2
|
import { EthAddress } from '@aztec/foundation/eth-address';
|
|
2
3
|
import type { AztecAsyncKVStore } from '@aztec/kv-store';
|
|
3
4
|
import type { ValidatorStatusHistory, ValidatorStatusInSlot, ValidatorsEpochPerformance } from '@aztec/stdlib/validators';
|
|
4
5
|
export declare class SentinelStore {
|
|
5
6
|
private store;
|
|
6
7
|
private config;
|
|
7
|
-
static readonly SCHEMA_VERSION =
|
|
8
|
+
static readonly SCHEMA_VERSION = 3;
|
|
8
9
|
private readonly historyMap;
|
|
9
10
|
private readonly provenMap;
|
|
10
11
|
constructor(store: AztecAsyncKVStore, config: {
|
|
@@ -13,14 +14,14 @@ export declare class SentinelStore {
|
|
|
13
14
|
});
|
|
14
15
|
getHistoryLength(): number;
|
|
15
16
|
getHistoricProvenPerformanceLength(): number;
|
|
16
|
-
updateProvenPerformance(epoch:
|
|
17
|
+
updateProvenPerformance(epoch: EpochNumber, performance: ValidatorsEpochPerformance): Promise<void>;
|
|
17
18
|
getProvenPerformance(who: EthAddress): Promise<{
|
|
18
19
|
missed: number;
|
|
19
20
|
total: number;
|
|
20
|
-
epoch:
|
|
21
|
+
epoch: EpochNumber;
|
|
21
22
|
}[]>;
|
|
22
23
|
private pushValidatorProvenPerformanceForEpoch;
|
|
23
|
-
updateValidators(slot:
|
|
24
|
+
updateValidators(slot: SlotNumber, statuses: Record<`0x${string}`, ValidatorStatusInSlot | undefined>): Promise<void>;
|
|
24
25
|
private pushValidatorStatusForSlot;
|
|
25
26
|
getHistories(): Promise<Record<`0x${string}`, ValidatorStatusHistory>>;
|
|
26
27
|
getHistory(address: EthAddress): Promise<ValidatorStatusHistory | undefined>;
|
|
@@ -31,4 +32,4 @@ export declare class SentinelStore {
|
|
|
31
32
|
private statusToNumber;
|
|
32
33
|
private statusFromNumber;
|
|
33
34
|
}
|
|
34
|
-
//# sourceMappingURL=
|
|
35
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoic3RvcmUuZC50cyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uL3NyYy9zZW50aW5lbC9zdG9yZS50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxPQUFPLEVBQUUsV0FBVyxFQUFFLFVBQVUsRUFBRSxNQUFNLGlDQUFpQyxDQUFDO0FBQzFFLE9BQU8sRUFBRSxVQUFVLEVBQUUsTUFBTSwrQkFBK0IsQ0FBQztBQUUzRCxPQUFPLEtBQUssRUFBRSxpQkFBaUIsRUFBaUIsTUFBTSxpQkFBaUIsQ0FBQztBQUN4RSxPQUFPLEtBQUssRUFDVixzQkFBc0IsRUFDdEIscUJBQXFCLEVBQ3JCLDBCQUEwQixFQUMzQixNQUFNLDBCQUEwQixDQUFDO0FBRWxDLHFCQUFhLGFBQWE7SUFXdEIsT0FBTyxDQUFDLEtBQUs7SUFDYixPQUFPLENBQUMsTUFBTTtJQVhoQixnQkFBdUIsY0FBYyxLQUFLO0lBRzFDLE9BQU8sQ0FBQyxRQUFRLENBQUMsVUFBVSxDQUF1QztJQUlsRSxPQUFPLENBQUMsUUFBUSxDQUFDLFNBQVMsQ0FBdUM7SUFFakUsWUFDVSxLQUFLLEVBQUUsaUJBQWlCLEVBQ3hCLE1BQU0sRUFBRTtRQUFFLGFBQWEsRUFBRSxNQUFNLENBQUM7UUFBQywrQkFBK0IsRUFBRSxNQUFNLENBQUE7S0FBRSxFQUluRjtJQUVNLGdCQUFnQixXQUV0QjtJQUVNLGtDQUFrQyxXQUV4QztJQUVZLHVCQUF1QixDQUFDLEtBQUssRUFBRSxXQUFXLEVBQUUsV0FBVyxFQUFFLDBCQUEwQixpQkFNL0Y7SUFFWSxvQkFBb0IsQ0FBQyxHQUFHLEVBQUUsVUFBVSxHQUFHLE9BQU8sQ0FBQztRQUFFLE1BQU0sRUFBRSxNQUFNLENBQUM7UUFBQyxLQUFLLEVBQUUsTUFBTSxDQUFDO1FBQUMsS0FBSyxFQUFFLFdBQVcsQ0FBQTtLQUFFLEVBQUUsQ0FBQyxDQUduSDtZQUVhLHNDQUFzQztJQTZCdkMsZ0JBQWdCLENBQUMsSUFBSSxFQUFFLFVBQVUsRUFBRSxRQUFRLEVBQUUsTUFBTSxDQUFDLEtBQUssTUFBTSxFQUFFLEVBQUUscUJBQXFCLEdBQUcsU0FBUyxDQUFDLGlCQVFqSDtZQUVhLDBCQUEwQjtJQVEzQixZQUFZLElBQUksT0FBTyxDQUFDLE1BQU0sQ0FBQyxLQUFLLE1BQU0sRUFBRSxFQUFFLHNCQUFzQixDQUFDLENBQUMsQ0FNbEY7SUFFWSxVQUFVLENBQUMsT0FBTyxFQUFFLFVBQVUsR0FBRyxPQUFPLENBQUMsc0JBQXNCLEdBQUcsU0FBUyxDQUFDLENBR3hGO0lBRUQsT0FBTyxDQUFDLG9CQUFvQjtJQU01QixPQUFPLENBQUMsc0JBQXNCO0lBYTlCLE9BQU8sQ0FBQyxnQkFBZ0I7SUFNeEIsT0FBTyxDQUFDLGtCQUFrQjtJQVcxQixPQUFPLENBQUMsY0FBYztJQXFCdEIsT0FBTyxDQUFDLGdCQUFnQjtDQWtCekIifQ==
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"store.d.ts","sourceRoot":"","sources":["../../src/sentinel/store.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,+BAA+B,CAAC;AAE3D,OAAO,KAAK,EAAE,iBAAiB,EAAiB,MAAM,iBAAiB,CAAC;AACxE,OAAO,KAAK,EACV,sBAAsB,EACtB,qBAAqB,EACrB,0BAA0B,EAC3B,MAAM,0BAA0B,CAAC;AAElC,qBAAa,aAAa;IAWtB,OAAO,CAAC,KAAK;IACb,OAAO,CAAC,MAAM;IAXhB,gBAAuB,cAAc,KAAK;IAG1C,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAuC;IAIlE,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAuC;
|
|
1
|
+
{"version":3,"file":"store.d.ts","sourceRoot":"","sources":["../../src/sentinel/store.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,UAAU,EAAE,MAAM,iCAAiC,CAAC;AAC1E,OAAO,EAAE,UAAU,EAAE,MAAM,+BAA+B,CAAC;AAE3D,OAAO,KAAK,EAAE,iBAAiB,EAAiB,MAAM,iBAAiB,CAAC;AACxE,OAAO,KAAK,EACV,sBAAsB,EACtB,qBAAqB,EACrB,0BAA0B,EAC3B,MAAM,0BAA0B,CAAC;AAElC,qBAAa,aAAa;IAWtB,OAAO,CAAC,KAAK;IACb,OAAO,CAAC,MAAM;IAXhB,gBAAuB,cAAc,KAAK;IAG1C,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAuC;IAIlE,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAuC;IAEjE,YACU,KAAK,EAAE,iBAAiB,EACxB,MAAM,EAAE;QAAE,aAAa,EAAE,MAAM,CAAC;QAAC,+BAA+B,EAAE,MAAM,CAAA;KAAE,EAInF;IAEM,gBAAgB,WAEtB;IAEM,kCAAkC,WAExC;IAEY,uBAAuB,CAAC,KAAK,EAAE,WAAW,EAAE,WAAW,EAAE,0BAA0B,iBAM/F;IAEY,oBAAoB,CAAC,GAAG,EAAE,UAAU,GAAG,OAAO,CAAC;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,WAAW,CAAA;KAAE,EAAE,CAAC,CAGnH;YAEa,sCAAsC;IA6BvC,gBAAgB,CAAC,IAAI,EAAE,UAAU,EAAE,QAAQ,EAAE,MAAM,CAAC,KAAK,MAAM,EAAE,EAAE,qBAAqB,GAAG,SAAS,CAAC,iBAQjH;YAEa,0BAA0B;IAQ3B,YAAY,IAAI,OAAO,CAAC,MAAM,CAAC,KAAK,MAAM,EAAE,EAAE,sBAAsB,CAAC,CAAC,CAMlF;IAEY,UAAU,CAAC,OAAO,EAAE,UAAU,GAAG,OAAO,CAAC,sBAAsB,GAAG,SAAS,CAAC,CAGxF;IAED,OAAO,CAAC,oBAAoB;IAM5B,OAAO,CAAC,sBAAsB;IAa9B,OAAO,CAAC,gBAAgB;IAMxB,OAAO,CAAC,kBAAkB;IAW1B,OAAO,CAAC,cAAc;IAqBtB,OAAO,CAAC,gBAAgB;CAkBzB"}
|
package/dest/sentinel/store.js
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
|
+
import { EpochNumber, SlotNumber } from '@aztec/foundation/branded-types';
|
|
1
2
|
import { EthAddress } from '@aztec/foundation/eth-address';
|
|
2
3
|
import { BufferReader, numToUInt8, numToUInt32BE, serializeToBuffer } from '@aztec/foundation/serialize';
|
|
3
4
|
export class SentinelStore {
|
|
4
5
|
store;
|
|
5
6
|
config;
|
|
6
|
-
static SCHEMA_VERSION =
|
|
7
|
+
static SCHEMA_VERSION = 3;
|
|
7
8
|
// a map from validator address to their ValidatorStatusHistory
|
|
8
9
|
historyMap;
|
|
9
10
|
// a map from validator address to their historical proven epoch performance
|
|
@@ -105,7 +106,7 @@ export class SentinelStore {
|
|
|
105
106
|
const performance = [];
|
|
106
107
|
while(!reader.isEmpty()){
|
|
107
108
|
performance.push({
|
|
108
|
-
epoch:
|
|
109
|
+
epoch: EpochNumber(reader.readNumber()),
|
|
109
110
|
missed: reader.readNumber(),
|
|
110
111
|
total: reader.readNumber()
|
|
111
112
|
});
|
|
@@ -122,7 +123,7 @@ export class SentinelStore {
|
|
|
122
123
|
const reader = new BufferReader(buffer);
|
|
123
124
|
const history = [];
|
|
124
125
|
while(!reader.isEmpty()){
|
|
125
|
-
const slot =
|
|
126
|
+
const slot = SlotNumber(reader.readNumber());
|
|
126
127
|
const status = this.statusFromNumber(reader.readUInt8());
|
|
127
128
|
history.push({
|
|
128
129
|
slot,
|
|
@@ -133,16 +134,18 @@ export class SentinelStore {
|
|
|
133
134
|
}
|
|
134
135
|
statusToNumber(status) {
|
|
135
136
|
switch(status){
|
|
136
|
-
case '
|
|
137
|
+
case 'checkpoint-mined':
|
|
137
138
|
return 1;
|
|
138
|
-
case '
|
|
139
|
+
case 'checkpoint-proposed':
|
|
139
140
|
return 2;
|
|
140
|
-
case '
|
|
141
|
+
case 'checkpoint-missed':
|
|
141
142
|
return 3;
|
|
142
143
|
case 'attestation-sent':
|
|
143
144
|
return 4;
|
|
144
145
|
case 'attestation-missed':
|
|
145
146
|
return 5;
|
|
147
|
+
case 'blocks-missed':
|
|
148
|
+
return 6;
|
|
146
149
|
default:
|
|
147
150
|
{
|
|
148
151
|
const _exhaustive = status;
|
|
@@ -153,15 +156,17 @@ export class SentinelStore {
|
|
|
153
156
|
statusFromNumber(status) {
|
|
154
157
|
switch(status){
|
|
155
158
|
case 1:
|
|
156
|
-
return '
|
|
159
|
+
return 'checkpoint-mined';
|
|
157
160
|
case 2:
|
|
158
|
-
return '
|
|
161
|
+
return 'checkpoint-proposed';
|
|
159
162
|
case 3:
|
|
160
|
-
return '
|
|
163
|
+
return 'checkpoint-missed';
|
|
161
164
|
case 4:
|
|
162
165
|
return 'attestation-sent';
|
|
163
166
|
case 5:
|
|
164
167
|
return 'attestation-missed';
|
|
168
|
+
case 6:
|
|
169
|
+
return 'blocks-missed';
|
|
165
170
|
default:
|
|
166
171
|
throw new Error(`Unknown status: ${status}`);
|
|
167
172
|
}
|