@marinade.finance/scoring 1.0.1 → 1.0.3
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/dist/computing/cluster.d.ts +10 -0
- package/dist/computing/cluster.js +95 -0
- package/dist/computing/cluster.js.map +1 -0
- package/dist/computing/eligibility.d.ts +19 -0
- package/dist/computing/eligibility.js +166 -0
- package/dist/computing/eligibility.js.map +1 -0
- package/dist/computing/score.d.ts +20 -0
- package/dist/computing/score.js +174 -0
- package/dist/computing/score.js.map +1 -0
- package/dist/computing/stake.d.ts +26 -0
- package/dist/computing/stake.js +197 -0
- package/dist/computing/stake.js.map +1 -0
- package/dist/computing/validators.d.ts +16 -0
- package/dist/computing/validators.js +123 -0
- package/dist/computing/validators.js.map +1 -0
- package/dist/constants/marinade.json +38 -0
- package/dist/dto/bonds.dto.d.ts +16 -0
- package/dist/dto/bonds.dto.js +3 -0
- package/dist/dto/bonds.dto.js.map +1 -0
- package/dist/dto/cluster.dto.d.ts +12 -0
- package/dist/dto/cluster.dto.js +3 -0
- package/dist/dto/cluster.dto.js.map +1 -0
- package/{dto/eligibility.dto.ts → dist/dto/eligibility.dto.d.ts} +15 -15
- package/dist/dto/eligibility.dto.js +3 -0
- package/dist/dto/eligibility.dto.js.map +1 -0
- package/dist/dto/jito.dto.d.ts +9 -0
- package/dist/dto/jito.dto.js +10 -0
- package/dist/dto/jito.dto.js.map +1 -0
- package/dist/dto/marinade.dto.d.ts +18 -0
- package/dist/dto/marinade.dto.js +3 -0
- package/dist/dto/marinade.dto.js.map +1 -0
- package/dist/dto/rewards.dto.d.ts +17 -0
- package/dist/dto/rewards.dto.js +18 -0
- package/dist/dto/rewards.dto.js.map +1 -0
- package/dist/dto/scoring.dto.d.ts +101 -0
- package/dist/dto/scoring.dto.js +3 -0
- package/dist/dto/scoring.dto.js.map +1 -0
- package/dist/dto/snapshots.dto.d.ts +15 -0
- package/dist/dto/snapshots.dto.js +13 -0
- package/dist/dto/snapshots.dto.js.map +1 -0
- package/dist/dto/stakes.dto.d.ts +30 -0
- package/dist/dto/stakes.dto.js +3 -0
- package/dist/dto/stakes.dto.js.map +1 -0
- package/dist/dto/validators.dto.d.ts +110 -0
- package/dist/dto/validators.dto.js +19 -0
- package/dist/dto/validators.dto.js.map +1 -0
- package/dist/errors/fetching.d.ts +22 -0
- package/dist/errors/fetching.js +30 -0
- package/dist/errors/fetching.js.map +1 -0
- package/dist/index.d.ts +24 -0
- package/dist/index.js +41 -0
- package/dist/index.js.map +1 -0
- package/dist/interfaces/data-provider.interface.d.ts +16 -0
- package/dist/interfaces/data-provider.interface.js +3 -0
- package/dist/interfaces/data-provider.interface.js.map +1 -0
- package/dist/logging/logger.d.ts +3 -0
- package/dist/logging/logger.js +72 -0
- package/dist/logging/logger.js.map +1 -0
- package/dist/providers/api-data.provider.d.ts +30 -0
- package/dist/providers/api-data.provider.js +227 -0
- package/dist/providers/api-data.provider.js.map +1 -0
- package/dist/providers/file-data.provider.d.ts +29 -0
- package/dist/providers/file-data.provider.js +96 -0
- package/dist/providers/file-data.provider.js.map +1 -0
- package/dist/tsconfig.tsbuildinfo +1 -0
- package/dist/utils/csv.d.ts +2 -0
- package/dist/utils/csv.js +52 -0
- package/dist/utils/csv.js.map +1 -0
- package/dist/utils/maths.d.ts +8 -0
- package/dist/utils/maths.js +56 -0
- package/dist/utils/maths.js.map +1 -0
- package/dist/utils/solana.d.ts +1 -0
- package/dist/utils/solana.js +9 -0
- package/dist/utils/solana.js.map +1 -0
- package/dist/utils/zip.d.ts +2 -0
- package/dist/utils/zip.js +15 -0
- package/dist/utils/zip.js.map +1 -0
- package/package.json +7 -13
- package/computing/cluster.ts +0 -102
- package/computing/eligibility.ts +0 -163
- package/computing/score.ts +0 -164
- package/computing/stake.ts +0 -246
- package/computing/validators.ts +0 -133
- package/constants/marinade.json +0 -38
- package/dto/bonds.dto.ts +0 -12
- package/dto/cluster.dto.ts +0 -12
- package/dto/jito.dto.ts +0 -9
- package/dto/marinade.dto.ts +0 -18
- package/dto/rewards.dto.ts +0 -19
- package/dto/scoring.dto.ts +0 -103
- package/dto/snapshots.dto.ts +0 -16
- package/dto/stakes.dto.ts +0 -31
- package/dto/validators.dto.ts +0 -110
- package/errors/fetching.ts +0 -25
- package/interfaces/data-provider.interface.ts +0 -17
- package/providers/api-data.provider.ts +0 -261
- package/providers/file-data.provider.ts +0 -117
- package/utils/csv.ts +0 -30
- package/utils/maths.ts +0 -75
- package/utils/solana.ts +0 -9
- package/utils/zip.ts +0 -12
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.mathUtilityFunctions = exports.piecewise = exports.stdDev = exports.mean = exports.sum = void 0;
|
|
4
|
+
function sum(amounts) {
|
|
5
|
+
if (!Array.isArray(amounts) || amounts.some(amount => typeof amount !== 'number')) {
|
|
6
|
+
throw new TypeError('Input must be an array of numbers');
|
|
7
|
+
}
|
|
8
|
+
return amounts.reduce((acc, amount) => acc + amount, 0);
|
|
9
|
+
}
|
|
10
|
+
exports.sum = sum;
|
|
11
|
+
function mean(data) {
|
|
12
|
+
if (data.length === 0) {
|
|
13
|
+
throw new Error('Cannot calculate mean for empty data');
|
|
14
|
+
}
|
|
15
|
+
return sum(data) / data.length;
|
|
16
|
+
}
|
|
17
|
+
exports.mean = mean;
|
|
18
|
+
function stdDev(data) {
|
|
19
|
+
if (data.length === 0) {
|
|
20
|
+
throw new Error('Cannot calculate std. dev. for empty data');
|
|
21
|
+
}
|
|
22
|
+
const dataMean = mean(data);
|
|
23
|
+
return Math.sqrt(data.reduce((sum, value) => sum + Math.pow(value - dataMean, 2), 0) / data.length);
|
|
24
|
+
}
|
|
25
|
+
exports.stdDev = stdDev;
|
|
26
|
+
function piecewise(...args) {
|
|
27
|
+
if (args.length === 0) {
|
|
28
|
+
throw new Error('At least one argument must be provided!');
|
|
29
|
+
}
|
|
30
|
+
for (let conditionIndex = 0, valueIndex = 1; valueIndex < args.length; conditionIndex += 2, valueIndex += 2) {
|
|
31
|
+
const condition = args[conditionIndex];
|
|
32
|
+
const value = args[valueIndex];
|
|
33
|
+
if (typeof condition !== 'boolean') {
|
|
34
|
+
throw new Error(`Argument at position ${conditionIndex} must be a boolean!`);
|
|
35
|
+
}
|
|
36
|
+
if (condition) {
|
|
37
|
+
if (value === undefined) {
|
|
38
|
+
throw new Error(`Expected a value corresponding to the condition at position ${conditionIndex}, but got undefined.`);
|
|
39
|
+
}
|
|
40
|
+
return value;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
const defaultValue = args[args.length - 1];
|
|
44
|
+
if (defaultValue === undefined) {
|
|
45
|
+
throw new Error('Unexpected error: Default value is undefined.');
|
|
46
|
+
}
|
|
47
|
+
return defaultValue;
|
|
48
|
+
}
|
|
49
|
+
exports.piecewise = piecewise;
|
|
50
|
+
function mathUtilityFunctions() {
|
|
51
|
+
return {
|
|
52
|
+
piecewise
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
exports.mathUtilityFunctions = mathUtilityFunctions;
|
|
56
|
+
//# sourceMappingURL=maths.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"maths.js","sourceRoot":"","sources":["../../src/utils/maths.ts"],"names":[],"mappings":";;;AAOA,SAAgB,GAAG,CAAE,OAAiB;IACpC,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,OAAO,CAAC,IAAI,OAAO,CAAC,IAAI,CAAC,MAAM,CAAC,EAAE,CAAC,OAAO,MAAM,KAAK,QAAQ,CAAC,EAAE;QACjF,MAAM,IAAI,SAAS,CAAC,mCAAmC,CAAC,CAAC;KAC1D;IACD,OAAO,OAAO,CAAC,MAAM,CAAC,CAAC,GAAG,EAAE,MAAM,EAAE,EAAE,CAAC,GAAG,GAAG,MAAM,EAAE,CAAC,CAAC,CAAC;AAC1D,CAAC;AALD,kBAKC;AAOD,SAAgB,IAAI,CAAE,IAAc;IAClC,IAAI,IAAI,CAAC,MAAM,KAAK,CAAC,EAAE;QACrB,MAAM,IAAI,KAAK,CAAC,sCAAsC,CAAC,CAAC;KACzD;IAED,OAAO,GAAG,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC,MAAM,CAAC;AACjC,CAAC;AAND,oBAMC;AAQD,SAAgB,MAAM,CAAE,IAAc;IACpC,IAAI,IAAI,CAAC,MAAM,KAAK,CAAC,EAAE;QACrB,MAAM,IAAI,KAAK,CAAC,2CAA2C,CAAC,CAAC;KAC9D;IAED,MAAM,QAAQ,GAAG,IAAI,CAAC,IAAI,CAAC,CAAC;IAC5B,OAAO,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,GAAG,EAAE,KAAK,EAAE,EAAE,CAAC,GAAG,GAAG,IAAI,CAAC,GAAG,CAAC,KAAK,GAAG,QAAQ,EAAE,CAAC,CAAC,EAAE,CAAC,CAAC,GAAG,IAAI,CAAC,MAAM,CAAC,CAAC;AACtG,CAAC;AAPD,wBAOC;AAED,SAAgB,SAAS,CAAE,GAAG,IAAa;IACzC,IAAI,IAAI,CAAC,MAAM,KAAK,CAAC,EAAE;QACrB,MAAM,IAAI,KAAK,CAAC,yCAAyC,CAAC,CAAC;KAC5D;IAED,KAAK,IAAI,cAAc,GAAG,CAAC,EAAE,UAAU,GAAG,CAAC,EAAE,UAAU,GAAG,IAAI,CAAC,MAAM,EAAE,cAAc,IAAI,CAAC,EAAE,UAAU,IAAI,CAAC,EAAE;QAC3G,MAAM,SAAS,GAAG,IAAI,CAAC,cAAc,CAAC,CAAC;QACvC,MAAM,KAAK,GAAG,IAAI,CAAC,UAAU,CAAC,CAAC;QAE/B,IAAI,OAAO,SAAS,KAAK,SAAS,EAAE;YAClC,MAAM,IAAI,KAAK,CAAC,wBAAwB,cAAc,qBAAqB,CAAC,CAAC;SAC9E;QAED,IAAI,SAAS,EAAE;YACb,IAAI,KAAK,KAAK,SAAS,EAAE;gBACvB,MAAM,IAAI,KAAK,CAAC,+DAA+D,cAAc,sBAAsB,CAAC,CAAC;aACtH;YACD,OAAO,KAAK,CAAC;SACd;KACF;IAED,MAAM,YAAY,GAAG,IAAI,CAAC,IAAI,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC;IAC3C,IAAI,YAAY,KAAK,SAAS,EAAE;QAC9B,MAAM,IAAI,KAAK,CAAC,+CAA+C,CAAC,CAAC;KAClE;IACD,OAAO,YAAY,CAAC;AACtB,CAAC;AA1BD,8BA0BC;AAED,SAAgB,oBAAoB;IAClC,OAAO;QACL,SAAS;KACV,CAAC;AACJ,CAAC;AAJD,oDAIC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function lamportsToSol(lamports: string): string;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.lamportsToSol = void 0;
|
|
4
|
+
function lamportsToSol(lamports) {
|
|
5
|
+
const paddedLamports = lamports.padStart(10, '0');
|
|
6
|
+
return paddedLamports.replace(/(.{9})$/, '.$1');
|
|
7
|
+
}
|
|
8
|
+
exports.lamportsToSol = lamportsToSol;
|
|
9
|
+
//# sourceMappingURL=solana.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"solana.js","sourceRoot":"","sources":["../../src/utils/solana.ts"],"names":[],"mappings":";;;AAKA,SAAgB,aAAa,CAAE,QAAgB;IAC7C,MAAM,cAAc,GAAG,QAAQ,CAAC,QAAQ,CAAC,EAAE,EAAE,GAAG,CAAC,CAAC;IAClD,OAAO,cAAc,CAAC,OAAO,CAAC,SAAS,EAAE,KAAK,CAAC,CAAC;AAClD,CAAC;AAHD,sCAGC"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.zip = void 0;
|
|
4
|
+
function* zip(...iterables) {
|
|
5
|
+
const iterators = iterables.map((iterable) => iterable[Symbol.iterator]());
|
|
6
|
+
while (true) {
|
|
7
|
+
const results = iterators.map((iter) => iter.next());
|
|
8
|
+
if (results.some((result) => result.done)) {
|
|
9
|
+
return;
|
|
10
|
+
}
|
|
11
|
+
yield results.map((result) => result.value);
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
exports.zip = zip;
|
|
15
|
+
//# sourceMappingURL=zip.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"zip.js","sourceRoot":"","sources":["../../src/utils/zip.ts"],"names":[],"mappings":";;;AAEA,QAAe,CAAC,CAAC,GAAG,CAAU,GAAG,SAA8B;IAC7D,MAAM,SAAS,GAAG,SAAS,CAAC,GAAG,CAAC,CAAC,QAAQ,EAAE,EAAE,CAAC,QAAQ,CAAC,MAAM,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC;IAC3E,OAAO,IAAI,EAAE;QACX,MAAM,OAAO,GAAG,SAAS,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC,CAAC;QACrD,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,MAAM,CAAC,IAAI,CAAC,EAAE;YACzC,OAAO;SACR;QACD,MAAM,OAAO,CAAC,GAAG,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;KAC7C;AACH,CAAC;AATD,kBASC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@marinade.finance/scoring",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.3",
|
|
4
4
|
"description": "Delegation Strategy Scoring",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -10,21 +10,11 @@
|
|
|
10
10
|
"access": "public"
|
|
11
11
|
},
|
|
12
12
|
"files": [
|
|
13
|
-
"
|
|
14
|
-
"constants",
|
|
15
|
-
"dto",
|
|
16
|
-
"errors",
|
|
17
|
-
"interfaces",
|
|
18
|
-
"providers",
|
|
19
|
-
"utils",
|
|
13
|
+
"dist",
|
|
20
14
|
"generated",
|
|
21
15
|
"README.md"
|
|
22
16
|
],
|
|
23
|
-
"main": "index.js",
|
|
24
|
-
"scripts": {
|
|
25
|
-
"build": "tsc",
|
|
26
|
-
"test": "jest"
|
|
27
|
-
},
|
|
17
|
+
"main": "dist/index.js",
|
|
28
18
|
"keywords": [
|
|
29
19
|
"solana",
|
|
30
20
|
"marinade.finance",
|
|
@@ -44,5 +34,9 @@
|
|
|
44
34
|
"@types/node": "^20.12.7",
|
|
45
35
|
"jest": "29.5.0",
|
|
46
36
|
"ts-jest": "29.0.5"
|
|
37
|
+
},
|
|
38
|
+
"scripts": {
|
|
39
|
+
"build": "tsc",
|
|
40
|
+
"test": "jest"
|
|
47
41
|
}
|
|
48
42
|
}
|
package/computing/cluster.ts
DELETED
|
@@ -1,102 +0,0 @@
|
|
|
1
|
-
import { BlockProductionResult, ClusterInfo } from '../dto/cluster.dto';
|
|
2
|
-
import { ValidatorDto } from '../dto/validators.dto';
|
|
3
|
-
import { mean, stdDev } from '../utils/maths';
|
|
4
|
-
import Decimal from 'decimal.js';
|
|
5
|
-
|
|
6
|
-
export function computeBlockProduction (validators: ValidatorDto[], startEpoch: number, endEpoch: number): BlockProductionResult {
|
|
7
|
-
const blockProductions: number[] = [];
|
|
8
|
-
|
|
9
|
-
for (const validator of validators) {
|
|
10
|
-
let leaderSlots = 0;
|
|
11
|
-
let blocksProduced = 0;
|
|
12
|
-
|
|
13
|
-
for (let epoch = startEpoch; epoch <= endEpoch; epoch++) {
|
|
14
|
-
if (!validator.epochStats) {
|
|
15
|
-
continue;
|
|
16
|
-
}
|
|
17
|
-
const epochStat = validator.epochStats[epoch];
|
|
18
|
-
if (epochStat) {
|
|
19
|
-
leaderSlots += epochStat.leader_slots;
|
|
20
|
-
blocksProduced += epochStat.blocks_produced;
|
|
21
|
-
}
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
if (leaderSlots) {
|
|
25
|
-
blockProductions.push(blocksProduced / leaderSlots);
|
|
26
|
-
}
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
return {
|
|
30
|
-
mean: mean(blockProductions),
|
|
31
|
-
stdDev: stdDev(blockProductions),
|
|
32
|
-
};
|
|
33
|
-
}
|
|
34
|
-
export function computeTargetCreditsByEpoch (validators: ValidatorDto[]): Map<number, number> {
|
|
35
|
-
const sumOfWeightedCreditsPerEpoch = new Map<number, Decimal>();
|
|
36
|
-
const sumOfWeightsPerEpoch = new Map<number, Decimal>();
|
|
37
|
-
|
|
38
|
-
validators.forEach(validator => {
|
|
39
|
-
validator.epoch_stats.forEach(epochStat => {
|
|
40
|
-
if (epochStat.epoch_end_at) {
|
|
41
|
-
const activatedStakeDecimal = new Decimal(epochStat.activated_stake);
|
|
42
|
-
const stakeInSol = activatedStakeDecimal.dividedBy(1e9);
|
|
43
|
-
|
|
44
|
-
const prevSumOfWeightedCredits = sumOfWeightedCreditsPerEpoch.get(epochStat.epoch) || new Decimal(0);
|
|
45
|
-
sumOfWeightedCreditsPerEpoch.set(epochStat.epoch, prevSumOfWeightedCredits.plus(new Decimal(epochStat.credits).times(stakeInSol)));
|
|
46
|
-
|
|
47
|
-
const prevSumOfWeightsPerEpoch = sumOfWeightsPerEpoch.get(epochStat.epoch) || new Decimal(0);
|
|
48
|
-
sumOfWeightsPerEpoch.set(epochStat.epoch, prevSumOfWeightsPerEpoch.plus(stakeInSol));
|
|
49
|
-
}
|
|
50
|
-
});
|
|
51
|
-
});
|
|
52
|
-
|
|
53
|
-
const result = new Map<number, number>();
|
|
54
|
-
sumOfWeightsPerEpoch.forEach((sumOfWeights, epoch) => {
|
|
55
|
-
const weightedCredits = sumOfWeightedCreditsPerEpoch.get(epoch) || new Decimal(0);
|
|
56
|
-
result.set(epoch, weightedCredits.dividedBy(sumOfWeights).toDecimalPlaces(0, Decimal.ROUND_HALF_UP).toNumber());
|
|
57
|
-
});
|
|
58
|
-
|
|
59
|
-
return result;
|
|
60
|
-
}
|
|
61
|
-
export function computeConcentrations (validators: ValidatorDto[], epoch: number): { city: Map<string, number>, country: Map<string, number>, aso: Map<string, number> } {
|
|
62
|
-
let total = 0;
|
|
63
|
-
const city = new Map<string, number>();
|
|
64
|
-
const country = new Map<string, number>();
|
|
65
|
-
const aso = new Map<string, number>();
|
|
66
|
-
|
|
67
|
-
for (const validator of validators) {
|
|
68
|
-
const lastEpochStats = validator.epoch_stats[0];
|
|
69
|
-
if (lastEpochStats?.epoch === epoch) {
|
|
70
|
-
const cityKey = validator.dc_full_city ?? '???';
|
|
71
|
-
const countryKey = validator.dc_country ?? '???';
|
|
72
|
-
const asoKey = validator.dc_aso ?? '???';
|
|
73
|
-
|
|
74
|
-
const stake = Number(validator.activated_stake) / 1e9;
|
|
75
|
-
city.set(cityKey, (city.get(cityKey) ?? 0) + stake);
|
|
76
|
-
country.set(countryKey, (country.get(countryKey) ?? 0) + stake);
|
|
77
|
-
aso.set(asoKey, (aso.get(asoKey) ?? 0) + stake);
|
|
78
|
-
|
|
79
|
-
total += stake;
|
|
80
|
-
}
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
if (total > 0) {
|
|
84
|
-
for (const map of [city, country, aso]) {
|
|
85
|
-
for (const [key, value] of map) {
|
|
86
|
-
map.set(key, value / total);
|
|
87
|
-
}
|
|
88
|
-
}
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
return { city, country, aso };
|
|
92
|
-
}
|
|
93
|
-
export function computeClusterInfo (validators: ValidatorDto[], basicEligibilityEpochs: number, lastEpoch: number): ClusterInfo {
|
|
94
|
-
const { mean: meanBlockProduction, stdDev: stdDevBlockProduction } = this.computeBlockProduction(validators, lastEpoch - basicEligibilityEpochs, lastEpoch - 1);
|
|
95
|
-
|
|
96
|
-
return {
|
|
97
|
-
targetCreditsByEpoch: this.computeTargetCreditsByEpoch(validators),
|
|
98
|
-
...this.computeConcentrations(validators, lastEpoch),
|
|
99
|
-
meanBlockProductionOverBasicEligibilityPeriod: meanBlockProduction,
|
|
100
|
-
stdDevBlockProductionOverBasicEligibilityPeriod: stdDevBlockProduction,
|
|
101
|
-
};
|
|
102
|
-
}
|
package/computing/eligibility.ts
DELETED
|
@@ -1,163 +0,0 @@
|
|
|
1
|
-
import { selectExternalStakeMin } from './validators';
|
|
2
|
-
import { Bonds } from '../dto/bonds.dto';
|
|
3
|
-
import { ClusterInfo } from '../dto/cluster.dto';
|
|
4
|
-
import { EligibilityConfig, EvaluationResult, Issue, IssueType, ValidatorEligibility, ValidatorsEligibilities } from '../dto/eligibility.dto';
|
|
5
|
-
import { ScoreDto, Scores, ScoringConfig } from '../dto/scoring.dto';
|
|
6
|
-
import { AggregatedValidator, AggregatedValidators } from '../dto/validators.dto';
|
|
7
|
-
|
|
8
|
-
export function evalLowCreditsIssue(validator: AggregatedValidator, epochIndex: number, eligibilityConfig: EligibilityConfig, clusterInfo: ClusterInfo): Issue | null {
|
|
9
|
-
if (!validator.epochs[epochIndex]) {
|
|
10
|
-
return null;
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
const targetCredits = clusterInfo.targetCreditsByEpoch.get(validator.epochs[epochIndex]!);
|
|
14
|
-
if (!targetCredits) {
|
|
15
|
-
return null;
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
const actualCredits = validator.credits[epochIndex] || 0;
|
|
19
|
-
const creditsPct = Math.round((100 * actualCredits) / targetCredits);
|
|
20
|
-
|
|
21
|
-
if (creditsPct < eligibilityConfig.voteCreditsLow) {
|
|
22
|
-
return { type: IssueType.VOTE_CREDITS_LOW, message: `Credits @ ${creditsPct}%` };
|
|
23
|
-
} else if (creditsPct < eligibilityConfig.voteCreditsWarning) {
|
|
24
|
-
return { type: IssueType.VOTE_CREDITS_WARNING, message: `(Warning) Credits @ ${creditsPct}%` };
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
return null;
|
|
28
|
-
}
|
|
29
|
-
export function evalLowExternalStakeIssue(validator: AggregatedValidator, epochIndex: number, eligibilityConfig: EligibilityConfig): Issue | undefined {
|
|
30
|
-
const externalStake = validator.externalStake[epochIndex];
|
|
31
|
-
|
|
32
|
-
if (!externalStake) {
|
|
33
|
-
return undefined;
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
if (externalStake < eligibilityConfig.minExternalStake) {
|
|
37
|
-
return { type: IssueType.EXTERNAL_STAKE, message: `External stake: ${externalStake}` };
|
|
38
|
-
}
|
|
39
|
-
return undefined;
|
|
40
|
-
}
|
|
41
|
-
export function evalBlacklistIssue(validator: AggregatedValidator): Issue | undefined {
|
|
42
|
-
if (validator.blacklisted) {
|
|
43
|
-
return { type: IssueType.BLACKLIST, message: 'Validator is blacklisted' };
|
|
44
|
-
}
|
|
45
|
-
return undefined;
|
|
46
|
-
}
|
|
47
|
-
export function evalHighCommissionIssue(validator: AggregatedValidator, epochIndex: number, eligibilityConfig: EligibilityConfig): Issue | undefined {
|
|
48
|
-
const commission = validator.commission[epochIndex];
|
|
49
|
-
|
|
50
|
-
if (!commission) {
|
|
51
|
-
return undefined;
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
if (eligibilityConfig.maxCommission < commission!) {
|
|
55
|
-
return { type: IssueType.COMMISSION, message: `Commission: ${commission}%` };
|
|
56
|
-
}
|
|
57
|
-
return undefined;
|
|
58
|
-
}
|
|
59
|
-
export function evalScoreIssue(epochIndex: number, eligibilityConfig: EligibilityConfig, { concentrationScore }: ScoreDto): Issue | undefined {
|
|
60
|
-
if (epochIndex === 0 && concentrationScore < eligibilityConfig.minScore) {
|
|
61
|
-
return { type: IssueType.SCORE, message: `Concentration Score: ${concentrationScore}` };
|
|
62
|
-
}
|
|
63
|
-
return undefined;
|
|
64
|
-
}
|
|
65
|
-
export function evalBondIssue(validator: AggregatedValidator, bonds: Bonds): Issue | undefined {
|
|
66
|
-
if (!(validator.voteAccount in bonds)) {
|
|
67
|
-
return { type: IssueType.NO_BOND, message: 'Validator has no bond' };
|
|
68
|
-
}
|
|
69
|
-
return undefined;
|
|
70
|
-
}
|
|
71
|
-
export function getValidatorIssuesInEpoch(validator: AggregatedValidator, bonds: Bonds, epochIndex: number, eligibilityConfig: EligibilityConfig, clusterInfo: ClusterInfo, score: ScoreDto): Issue[] {
|
|
72
|
-
if (!validator.dataAvailable[epochIndex]) {
|
|
73
|
-
return [{ type: IssueType.NO_DATA, message: `No data for validator in epoch ${validator.epochs[epochIndex]}` }];
|
|
74
|
-
}
|
|
75
|
-
return [
|
|
76
|
-
evalBlacklistIssue(validator),
|
|
77
|
-
evalLowCreditsIssue(validator, epochIndex, eligibilityConfig, clusterInfo),
|
|
78
|
-
evalLowExternalStakeIssue(validator, epochIndex, eligibilityConfig),
|
|
79
|
-
evalHighCommissionIssue(validator, epochIndex, eligibilityConfig),
|
|
80
|
-
evalScoreIssue(epochIndex, eligibilityConfig, score),
|
|
81
|
-
evalBondIssue(validator, bonds),
|
|
82
|
-
].filter(Boolean) as Issue[];
|
|
83
|
-
}
|
|
84
|
-
export function evalIssuesInEpoch(issues: Issue[]): EvaluationResult {
|
|
85
|
-
let criticals = 0;
|
|
86
|
-
let warnings = 0;
|
|
87
|
-
|
|
88
|
-
const criticalTypes = new Set([
|
|
89
|
-
IssueType.NO_BOND,
|
|
90
|
-
IssueType.NO_DATA,
|
|
91
|
-
IssueType.BLACKLIST,
|
|
92
|
-
IssueType.COMMISSION,
|
|
93
|
-
IssueType.EXTERNAL_STAKE,
|
|
94
|
-
IssueType.CENTRALIZATION,
|
|
95
|
-
IssueType.SCORE,
|
|
96
|
-
IssueType.VOTE_CREDITS_LOW,
|
|
97
|
-
]);
|
|
98
|
-
|
|
99
|
-
const warningTypes = new Set([
|
|
100
|
-
IssueType.VOTE_CREDITS_WARNING,
|
|
101
|
-
]);
|
|
102
|
-
|
|
103
|
-
issues.forEach(issue => {
|
|
104
|
-
if (criticalTypes.has(issue.type)) {
|
|
105
|
-
criticals++;
|
|
106
|
-
} else if (warningTypes.has(issue.type)) {
|
|
107
|
-
warnings++;
|
|
108
|
-
} else {
|
|
109
|
-
throw new Error(`Unexpected issue: ${issue.type}`);
|
|
110
|
-
}
|
|
111
|
-
});
|
|
112
|
-
|
|
113
|
-
return { criticals, warnings };
|
|
114
|
-
}
|
|
115
|
-
export function evalIssues(issuesCollection: Issue[][], epochs: number): EvaluationResult {
|
|
116
|
-
let totalCriticals = 0;
|
|
117
|
-
let totalWarnings = 0;
|
|
118
|
-
for (let epochIndex = 0; epochIndex < epochs; epochIndex++) {
|
|
119
|
-
if (!issuesCollection[epochIndex]) {
|
|
120
|
-
continue;
|
|
121
|
-
}
|
|
122
|
-
const { criticals, warnings } = evalIssuesInEpoch(issuesCollection[epochIndex]!);
|
|
123
|
-
totalCriticals += criticals;
|
|
124
|
-
totalWarnings += warnings;
|
|
125
|
-
}
|
|
126
|
-
return { criticals: totalCriticals, warnings: totalWarnings };
|
|
127
|
-
}
|
|
128
|
-
export function isBasicEligible(issuesCollection: Issue[][], eligibilityConfig: EligibilityConfig): boolean {
|
|
129
|
-
const { criticals, warnings } = evalIssues(issuesCollection, eligibilityConfig.basicEligibilityEpochs + 1);
|
|
130
|
-
return criticals === 0 && warnings <= eligibilityConfig.maxWarnings;
|
|
131
|
-
}
|
|
132
|
-
export function isBonusEligible(issuesCollection: Issue[][], eligibilityConfig: EligibilityConfig): boolean {
|
|
133
|
-
const totalEpochs = eligibilityConfig.basicEligibilityEpochs + eligibilityConfig.bonusEligibilityExtraEpochs + 1;
|
|
134
|
-
const { criticals, warnings } = evalIssues(issuesCollection, totalEpochs);
|
|
135
|
-
return criticals === 0 && warnings <= eligibilityConfig.maxWarnings;
|
|
136
|
-
}
|
|
137
|
-
export function getIssuesCollection(clusterInfo: ClusterInfo, aggregatedValidator: AggregatedValidator, bonds: Bonds, score: ScoreDto, eligibilityConfig: EligibilityConfig): Issue[][] {
|
|
138
|
-
return Array.from({ length: eligibilityConfig.basicEligibilityEpochs + eligibilityConfig.bonusEligibilityExtraEpochs + 1 }, (_, epochIndex) =>
|
|
139
|
-
getValidatorIssuesInEpoch(aggregatedValidator, bonds, epochIndex, eligibilityConfig, clusterInfo, score)
|
|
140
|
-
);
|
|
141
|
-
}
|
|
142
|
-
export function computeValidatorEligibility(clusterInfo: ClusterInfo, aggregatedValidator: AggregatedValidator, bonds: Bonds, scores: ScoreDto, eligibilityConfig: EligibilityConfig, scoringConfig: ScoringConfig): ValidatorEligibility {
|
|
143
|
-
const issuesCollection = getIssuesCollection(clusterInfo, aggregatedValidator, bonds, scores, eligibilityConfig);
|
|
144
|
-
const minExternalStake = selectExternalStakeMin(aggregatedValidator, eligibilityConfig.basicEligibilityEpochs);
|
|
145
|
-
|
|
146
|
-
return {
|
|
147
|
-
basicEligibility: isBasicEligible(issuesCollection, eligibilityConfig),
|
|
148
|
-
bonusEligibility: isBonusEligible(issuesCollection, eligibilityConfig),
|
|
149
|
-
issuesCollection,
|
|
150
|
-
capFromBond: scoringConfig.CAP_FROM_BOND,
|
|
151
|
-
capFromExternalStake: minExternalStake * (eligibilityConfig.maxStakeShare / (1 - eligibilityConfig.maxStakeShare)),
|
|
152
|
-
};
|
|
153
|
-
}
|
|
154
|
-
export function computeValidatorsEligibilities(clusterInfo: ClusterInfo, scores: Scores, aggregatedValidators: AggregatedValidators, bonds: Bonds, eligibilityConfig: EligibilityConfig, scoringConfig: ScoringConfig): ValidatorsEligibilities {
|
|
155
|
-
const result: ValidatorsEligibilities = {};
|
|
156
|
-
for (const [voteAccount, validator] of Object.entries(aggregatedValidators)) {
|
|
157
|
-
if (!scores[voteAccount]) {
|
|
158
|
-
continue;
|
|
159
|
-
}
|
|
160
|
-
result[voteAccount] = computeValidatorEligibility(clusterInfo, validator, bonds, scores[voteAccount]!, eligibilityConfig, scoringConfig);
|
|
161
|
-
}
|
|
162
|
-
return result;
|
|
163
|
-
}
|
package/computing/score.ts
DELETED
|
@@ -1,164 +0,0 @@
|
|
|
1
|
-
import { ClusterInfo } from '../dto/cluster.dto';
|
|
2
|
-
import { ScoreDto, ScoreConfig, Scores, Variables } from '../dto/scoring.dto';
|
|
3
|
-
import { AggregatedValidator, AggregatedValidators } from '../dto/validators.dto';
|
|
4
|
-
import { Parser } from 'expr-eval';
|
|
5
|
-
import { mathUtilityFunctions } from '../utils/maths';
|
|
6
|
-
import { zip } from '../utils/zip';
|
|
7
|
-
import { ValidatorsEligibilities } from '../dto/eligibility.dto';
|
|
8
|
-
import { ScoringConfig } from '../dto/scoring.dto';
|
|
9
|
-
import { selectCreditsPctMean, selectBlockProductionMean, selectCommissionInflationMax, selectCommissionMEV, selectCountryStakeConcentration, selectCityStakeConcentration, selectASOStakeConcentration, selectNodeStake } from 'computing/validators';
|
|
10
|
-
import { error } from 'logger';
|
|
11
|
-
|
|
12
|
-
export function selectVariables(clusterInfo: ClusterInfo, aggregatedValidator: AggregatedValidator, scoreConfig: ScoreConfig): Variables {
|
|
13
|
-
return {
|
|
14
|
-
bp_cluster_mean: clusterInfo.meanBlockProductionOverBasicEligibilityPeriod,
|
|
15
|
-
bp_cluster_std_dev: clusterInfo.stdDevBlockProductionOverBasicEligibilityPeriod,
|
|
16
|
-
credits_pct_mean: selectCreditsPctMean(aggregatedValidator, clusterInfo, scoreConfig.epochs),
|
|
17
|
-
bp_mean: selectBlockProductionMean(aggregatedValidator, scoreConfig.epochs),
|
|
18
|
-
commission_inflation_max: selectCommissionInflationMax(aggregatedValidator, scoreConfig.epochs + 1),
|
|
19
|
-
commission_mev: selectCommissionMEV(aggregatedValidator),
|
|
20
|
-
country_stake_concentration_last: selectCountryStakeConcentration(aggregatedValidator, clusterInfo),
|
|
21
|
-
city_stake_concentration_last: selectCityStakeConcentration(aggregatedValidator, clusterInfo),
|
|
22
|
-
aso_stake_concentration_last: selectASOStakeConcentration(aggregatedValidator, clusterInfo),
|
|
23
|
-
node_stake_last: selectNodeStake(aggregatedValidator),
|
|
24
|
-
};
|
|
25
|
-
}
|
|
26
|
-
export function computeValidatorScore(clusterInfo: ClusterInfo, aggregatedValidator: AggregatedValidator, formulas: string[], weights: number[], scoreConfig: ScoreConfig): ScoreDto {
|
|
27
|
-
const scoreErrors: boolean[] = [];
|
|
28
|
-
const scores: number[] = [];
|
|
29
|
-
const values: number[] = [];
|
|
30
|
-
const tooltips: string[] = [];
|
|
31
|
-
let totalWeight = 0;
|
|
32
|
-
let totalScore = 0;
|
|
33
|
-
let totalConcentrationWeight = 0;
|
|
34
|
-
let totalConcentrationScore = 0;
|
|
35
|
-
const variables = { ...selectVariables(clusterInfo, aggregatedValidator, scoreConfig), ...mathUtilityFunctions() };
|
|
36
|
-
|
|
37
|
-
for (const [formula, weight, tooltipBuilder] of zip(formulas, weights, getScoreTooltipBuilders())) {
|
|
38
|
-
let score = 0;
|
|
39
|
-
try {
|
|
40
|
-
values.push(0);
|
|
41
|
-
score = Parser.evaluate(formula, variables);
|
|
42
|
-
scoreErrors.push(false);
|
|
43
|
-
} catch (err) {
|
|
44
|
-
error('Failed to calculate', formula, 'for', aggregatedValidator.voteAccount, 'err:', err);
|
|
45
|
-
scoreErrors.push(true);
|
|
46
|
-
}
|
|
47
|
-
totalWeight += weight;
|
|
48
|
-
totalScore += weight * score;
|
|
49
|
-
if (scoreConfig.concentrationParams.includes(scores.length)) {
|
|
50
|
-
totalConcentrationWeight += weight;
|
|
51
|
-
totalConcentrationScore += weight * score;
|
|
52
|
-
}
|
|
53
|
-
tooltips.push(tooltipBuilder(aggregatedValidator, clusterInfo));
|
|
54
|
-
scores.push(score);
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
return {
|
|
58
|
-
score: totalScore / totalWeight,
|
|
59
|
-
concentrationScore: totalConcentrationScore / totalConcentrationWeight,
|
|
60
|
-
scores,
|
|
61
|
-
values,
|
|
62
|
-
scoreErrors,
|
|
63
|
-
tooltips,
|
|
64
|
-
};
|
|
65
|
-
}
|
|
66
|
-
export function computeValidatorsScores(clusterInfo: ClusterInfo, aggregatedValidators: AggregatedValidators, formulas: string[], weights: number[], scoreConfig: ScoreConfig): Scores {
|
|
67
|
-
const result: Scores = {};
|
|
68
|
-
|
|
69
|
-
for (const [voteAccount, aggregatedValidator] of Object.entries(aggregatedValidators)) {
|
|
70
|
-
result[voteAccount] = computeValidatorScore(clusterInfo, aggregatedValidator, formulas, weights, scoreConfig);
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
return result;
|
|
74
|
-
}
|
|
75
|
-
export function scoreTooltipCredits(validator: AggregatedValidator, clusterInfo: ClusterInfo): string {
|
|
76
|
-
return `Credits: ${validator.credits.slice(1).map((credits, e) => {
|
|
77
|
-
const epoch = validator.epochs[e + 1];
|
|
78
|
-
if (epoch !== undefined) {
|
|
79
|
-
const targetCredits = clusterInfo.targetCreditsByEpoch.get(epoch) || 1;
|
|
80
|
-
return `${(100 * credits / targetCredits).toFixed(2)}%`;
|
|
81
|
-
} else {
|
|
82
|
-
return 'N/A';
|
|
83
|
-
}
|
|
84
|
-
}).join(', ')}`;
|
|
85
|
-
}
|
|
86
|
-
export function scoreTooltipBlockProduction(validator: AggregatedValidator, _clusterInfo: ClusterInfo): string {
|
|
87
|
-
return `Block production: ${validator.blocksProduced.map((blocksProduced, e) => {
|
|
88
|
-
const leaderSlot = validator.leaderSlots[e];
|
|
89
|
-
if (leaderSlot) {
|
|
90
|
-
return `${(100 * blocksProduced / leaderSlot).toFixed(2)}%`;
|
|
91
|
-
} else {
|
|
92
|
-
return '-';
|
|
93
|
-
}
|
|
94
|
-
}).join(', ')}`;
|
|
95
|
-
}
|
|
96
|
-
export function scoreTooltipCommissionInflation(validator: AggregatedValidator, _clusterInfo: ClusterInfo): string {
|
|
97
|
-
return `Inflation commission: ${validator.commission.map(c => `${c}%`).join(', ')}`;
|
|
98
|
-
}
|
|
99
|
-
export function scoreTooltipCommissionMEV(validator: AggregatedValidator, _clusterInfo: ClusterInfo): string {
|
|
100
|
-
return `MEV commission: ${validator.mevCommission} %`;
|
|
101
|
-
}
|
|
102
|
-
export function scoreTooltipCountryStakeConcentration(validator: AggregatedValidator, _clusterInfo: ClusterInfo): string {
|
|
103
|
-
return `Country: ${validator.country}`;
|
|
104
|
-
}
|
|
105
|
-
export function scoreTooltipCityStakeConcentration(validator: AggregatedValidator, _clusterInfo: ClusterInfo): string {
|
|
106
|
-
return `City: ${validator.city}`;
|
|
107
|
-
}
|
|
108
|
-
export function scoreTooltipNodeStake(validator: AggregatedValidator, _clusterInfo: ClusterInfo): string {
|
|
109
|
-
return `Stake of the node: ${Math.round(validator.stake[0] ?? 0).toLocaleString()} SOL`;
|
|
110
|
-
}
|
|
111
|
-
export function scoreTooltipASOStakeConcentration(validator: AggregatedValidator, _clusterInfo: ClusterInfo): string {
|
|
112
|
-
return `ASO: ${validator.aso}`;
|
|
113
|
-
}
|
|
114
|
-
export function getScoreTooltipBuilders(): ((validator: AggregatedValidator, clusterInfo: ClusterInfo) => string)[] {
|
|
115
|
-
return [
|
|
116
|
-
scoreTooltipCredits,
|
|
117
|
-
scoreTooltipBlockProduction,
|
|
118
|
-
scoreTooltipCommissionInflation,
|
|
119
|
-
scoreTooltipCommissionMEV,
|
|
120
|
-
scoreTooltipCountryStakeConcentration,
|
|
121
|
-
scoreTooltipCityStakeConcentration,
|
|
122
|
-
scoreTooltipASOStakeConcentration,
|
|
123
|
-
scoreTooltipNodeStake,
|
|
124
|
-
];
|
|
125
|
-
}
|
|
126
|
-
export function getDefaultFormulas(scoringConfig: ScoringConfig): string[] {
|
|
127
|
-
return [
|
|
128
|
-
scoringConfig.formulaVoteCredits,
|
|
129
|
-
scoringConfig.formulaBlockProduction,
|
|
130
|
-
scoringConfig.formulaInflationCommission,
|
|
131
|
-
scoringConfig.formulaMEVCommission,
|
|
132
|
-
scoringConfig.formulaStakeConcentrationCountry,
|
|
133
|
-
scoringConfig.formulaStakeConcentrationCity,
|
|
134
|
-
scoringConfig.formulaStakeConcentrationASO,
|
|
135
|
-
scoringConfig.formulaStakeConcentrationNode,
|
|
136
|
-
];
|
|
137
|
-
}
|
|
138
|
-
export function getDefaultWeights(scoringConfig: ScoringConfig): number[] {
|
|
139
|
-
return [
|
|
140
|
-
scoringConfig.weightVoteCredits,
|
|
141
|
-
scoringConfig.weightBlockProduction,
|
|
142
|
-
scoringConfig.weightInflationCommission,
|
|
143
|
-
scoringConfig.weightMEVCommission,
|
|
144
|
-
scoringConfig.weightStakeConcentrationCountry,
|
|
145
|
-
scoringConfig.weightStakeConcentrationCity,
|
|
146
|
-
scoringConfig.weightStakeConcentrationASO,
|
|
147
|
-
scoringConfig.weightStakeConcentrationNode,
|
|
148
|
-
];
|
|
149
|
-
}
|
|
150
|
-
export function orderByEligibiltyAndScore(scores: Scores, eligibilities: ValidatorsEligibilities): string[] {
|
|
151
|
-
const voteAccounts = Object.keys(scores);
|
|
152
|
-
voteAccounts.sort((a, b) => {
|
|
153
|
-
const eligibilityOrder = Number(eligibilities[b]?.basicEligibility ?? 0) - Number(eligibilities[a]?.basicEligibility ?? 0);
|
|
154
|
-
if (eligibilityOrder === 0) {
|
|
155
|
-
const scoreA = scores[a]?.score ?? 0;
|
|
156
|
-
const scoreB = scores[b]?.score ?? 0;
|
|
157
|
-
|
|
158
|
-
return scoreB - scoreA;
|
|
159
|
-
}
|
|
160
|
-
return eligibilityOrder;
|
|
161
|
-
});
|
|
162
|
-
|
|
163
|
-
return voteAccounts;
|
|
164
|
-
}
|