@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.
Files changed (101) hide show
  1. package/dist/computing/cluster.d.ts +10 -0
  2. package/dist/computing/cluster.js +95 -0
  3. package/dist/computing/cluster.js.map +1 -0
  4. package/dist/computing/eligibility.d.ts +19 -0
  5. package/dist/computing/eligibility.js +166 -0
  6. package/dist/computing/eligibility.js.map +1 -0
  7. package/dist/computing/score.d.ts +20 -0
  8. package/dist/computing/score.js +174 -0
  9. package/dist/computing/score.js.map +1 -0
  10. package/dist/computing/stake.d.ts +26 -0
  11. package/dist/computing/stake.js +197 -0
  12. package/dist/computing/stake.js.map +1 -0
  13. package/dist/computing/validators.d.ts +16 -0
  14. package/dist/computing/validators.js +123 -0
  15. package/dist/computing/validators.js.map +1 -0
  16. package/dist/constants/marinade.json +38 -0
  17. package/dist/dto/bonds.dto.d.ts +16 -0
  18. package/dist/dto/bonds.dto.js +3 -0
  19. package/dist/dto/bonds.dto.js.map +1 -0
  20. package/dist/dto/cluster.dto.d.ts +12 -0
  21. package/dist/dto/cluster.dto.js +3 -0
  22. package/dist/dto/cluster.dto.js.map +1 -0
  23. package/{dto/eligibility.dto.ts → dist/dto/eligibility.dto.d.ts} +15 -15
  24. package/dist/dto/eligibility.dto.js +3 -0
  25. package/dist/dto/eligibility.dto.js.map +1 -0
  26. package/dist/dto/jito.dto.d.ts +9 -0
  27. package/dist/dto/jito.dto.js +10 -0
  28. package/dist/dto/jito.dto.js.map +1 -0
  29. package/dist/dto/marinade.dto.d.ts +18 -0
  30. package/dist/dto/marinade.dto.js +3 -0
  31. package/dist/dto/marinade.dto.js.map +1 -0
  32. package/dist/dto/rewards.dto.d.ts +17 -0
  33. package/dist/dto/rewards.dto.js +18 -0
  34. package/dist/dto/rewards.dto.js.map +1 -0
  35. package/dist/dto/scoring.dto.d.ts +101 -0
  36. package/dist/dto/scoring.dto.js +3 -0
  37. package/dist/dto/scoring.dto.js.map +1 -0
  38. package/dist/dto/snapshots.dto.d.ts +15 -0
  39. package/dist/dto/snapshots.dto.js +13 -0
  40. package/dist/dto/snapshots.dto.js.map +1 -0
  41. package/dist/dto/stakes.dto.d.ts +30 -0
  42. package/dist/dto/stakes.dto.js +3 -0
  43. package/dist/dto/stakes.dto.js.map +1 -0
  44. package/dist/dto/validators.dto.d.ts +110 -0
  45. package/dist/dto/validators.dto.js +19 -0
  46. package/dist/dto/validators.dto.js.map +1 -0
  47. package/dist/errors/fetching.d.ts +22 -0
  48. package/dist/errors/fetching.js +30 -0
  49. package/dist/errors/fetching.js.map +1 -0
  50. package/dist/index.d.ts +24 -0
  51. package/dist/index.js +41 -0
  52. package/dist/index.js.map +1 -0
  53. package/dist/interfaces/data-provider.interface.d.ts +16 -0
  54. package/dist/interfaces/data-provider.interface.js +3 -0
  55. package/dist/interfaces/data-provider.interface.js.map +1 -0
  56. package/dist/logging/logger.d.ts +3 -0
  57. package/dist/logging/logger.js +72 -0
  58. package/dist/logging/logger.js.map +1 -0
  59. package/dist/providers/api-data.provider.d.ts +30 -0
  60. package/dist/providers/api-data.provider.js +227 -0
  61. package/dist/providers/api-data.provider.js.map +1 -0
  62. package/dist/providers/file-data.provider.d.ts +29 -0
  63. package/dist/providers/file-data.provider.js +96 -0
  64. package/dist/providers/file-data.provider.js.map +1 -0
  65. package/dist/tsconfig.tsbuildinfo +1 -0
  66. package/dist/utils/csv.d.ts +2 -0
  67. package/dist/utils/csv.js +52 -0
  68. package/dist/utils/csv.js.map +1 -0
  69. package/dist/utils/maths.d.ts +8 -0
  70. package/dist/utils/maths.js +56 -0
  71. package/dist/utils/maths.js.map +1 -0
  72. package/dist/utils/solana.d.ts +1 -0
  73. package/dist/utils/solana.js +9 -0
  74. package/dist/utils/solana.js.map +1 -0
  75. package/dist/utils/zip.d.ts +2 -0
  76. package/dist/utils/zip.js +15 -0
  77. package/dist/utils/zip.js.map +1 -0
  78. package/package.json +7 -13
  79. package/computing/cluster.ts +0 -102
  80. package/computing/eligibility.ts +0 -163
  81. package/computing/score.ts +0 -164
  82. package/computing/stake.ts +0 -246
  83. package/computing/validators.ts +0 -133
  84. package/constants/marinade.json +0 -38
  85. package/dto/bonds.dto.ts +0 -12
  86. package/dto/cluster.dto.ts +0 -12
  87. package/dto/jito.dto.ts +0 -9
  88. package/dto/marinade.dto.ts +0 -18
  89. package/dto/rewards.dto.ts +0 -19
  90. package/dto/scoring.dto.ts +0 -103
  91. package/dto/snapshots.dto.ts +0 -16
  92. package/dto/stakes.dto.ts +0 -31
  93. package/dto/validators.dto.ts +0 -110
  94. package/errors/fetching.ts +0 -25
  95. package/interfaces/data-provider.interface.ts +0 -17
  96. package/providers/api-data.provider.ts +0 -261
  97. package/providers/file-data.provider.ts +0 -117
  98. package/utils/csv.ts +0 -30
  99. package/utils/maths.ts +0 -75
  100. package/utils/solana.ts +0 -9
  101. 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,2 @@
1
+ export declare function zip<T1, T2, T3>(...iterables: [Iterable<T1>, Iterable<T2>, Iterable<T3>]): Generator<[T1, T2, T3]>;
2
+ export declare function zip<T1, T2>(...iterables: [Iterable<T1>, Iterable<T2>]): Generator<[T1, T2]>;
@@ -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.1",
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
- "computing",
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
  }
@@ -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
- }
@@ -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
- }
@@ -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
- }