-
Notifications
You must be signed in to change notification settings - Fork 22
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: Add validators collection job (#1184)
* feat: Add validators endpoint. Add validators collections job with ValidatorService. * feat: Add committee cache and validators/stats endpoint * feat: Add inCommittee flag to IValidatorsResponse * fix: Change stakers field to validators now that is fixed * feat: Add validator stats endpoint * feat: Fix validator stats computation * fix: Ignore eslint unresolved nova SDK import * chore: Ignore more eslint no-unsafe for SDK (CI)
- Loading branch information
Showing
7 changed files
with
209 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
/* eslint-disable import/no-unresolved */ | ||
/* eslint-disable @typescript-eslint/no-unsafe-argument */ | ||
import { IResponse } from "./IResponse"; | ||
|
||
export interface IValidatorStatsResponse extends IResponse { | ||
/** | ||
* totalPoolStake (BigInt) | ||
*/ | ||
totalPoolStake?: string; | ||
/** | ||
* totalValidatorStake (BigInt) | ||
*/ | ||
totalValidatorStake?: string; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
/* eslint-disable import/no-unresolved */ | ||
/* eslint-disable @typescript-eslint/no-unsafe-argument */ | ||
import { ValidatorResponse } from "@iota/sdk-nova"; | ||
import { IResponse } from "./IResponse"; | ||
|
||
export interface IValidator { | ||
validator: ValidatorResponse; | ||
inCommittee: boolean; | ||
} | ||
|
||
export interface IValidatorsResponse extends IResponse { | ||
validators?: IValidator[]; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
/* eslint-disable @typescript-eslint/no-unsafe-return */ | ||
import { ServiceFactory } from "../../../factories/serviceFactory"; | ||
import { INetworkBoundGetRequest } from "../../../models/api/INetworkBoundGetRequest"; | ||
import { IValidator, IValidatorsResponse } from "../../../models/api/nova/IValidatorsResponse"; | ||
import { IConfiguration } from "../../../models/configuration/IConfiguration"; | ||
import { NOVA } from "../../../models/db/protocolVersion"; | ||
import { NetworkService } from "../../../services/networkService"; | ||
import { ValidatorService } from "../../../services/nova/validatorService"; | ||
import { ValidationHelper } from "../../../utils/validationHelper"; | ||
|
||
/** | ||
* Fetch the current validators (cached). | ||
* @param _ The configuration. | ||
* @param request The request. | ||
* @returns The response. | ||
*/ | ||
export async function get(_: IConfiguration, request: INetworkBoundGetRequest): Promise<IValidatorsResponse> { | ||
const networkService = ServiceFactory.get<NetworkService>("network"); | ||
const networks = networkService.networkNames(); | ||
ValidationHelper.oneOf(request.network, networks, "network"); | ||
|
||
const networkConfig = networkService.get(request.network); | ||
|
||
if (networkConfig.protocolVersion !== NOVA) { | ||
return {}; | ||
} | ||
|
||
const validatorService = ServiceFactory.get<ValidatorService>(`validator-service-${networkConfig.network}`); | ||
|
||
const committeeAddresses = validatorService.committee?.committee.map((member) => member.address) ?? []; | ||
const validators: IValidator[] = validatorService?.validators.map((validator) => ({ | ||
validator, | ||
inCommittee: committeeAddresses.includes(validator.address), | ||
})); | ||
|
||
return { validators }; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,41 @@ | ||
/* eslint-disable @typescript-eslint/no-unsafe-return */ | ||
/* eslint-disable @typescript-eslint/no-unsafe-argument */ | ||
import { ServiceFactory } from "../../../../factories/serviceFactory"; | ||
import { INetworkBoundGetRequest } from "../../../../models/api/INetworkBoundGetRequest"; | ||
import { IValidatorStatsResponse } from "../../../../models/api/nova/IValidatorStatsResponse"; | ||
import { IConfiguration } from "../../../../models/configuration/IConfiguration"; | ||
import { NOVA } from "../../../../models/db/protocolVersion"; | ||
import { NetworkService } from "../../../../services/networkService"; | ||
import { ValidatorService } from "../../../../services/nova/validatorService"; | ||
import { ValidationHelper } from "../../../../utils/validationHelper"; | ||
|
||
/** | ||
* Fetch the current validator stats (cached). | ||
* @param _ The configuration. | ||
* @param request The request. | ||
* @returns The response. | ||
*/ | ||
export async function get(_: IConfiguration, request: INetworkBoundGetRequest): Promise<IValidatorStatsResponse> { | ||
const networkService = ServiceFactory.get<NetworkService>("network"); | ||
const networks = networkService.networkNames(); | ||
ValidationHelper.oneOf(request.network, networks, "network"); | ||
|
||
const networkConfig = networkService.get(request.network); | ||
|
||
if (networkConfig.protocolVersion !== NOVA) { | ||
return {}; | ||
} | ||
|
||
const validatorService = ServiceFactory.get<ValidatorService>(`validator-service-${networkConfig.network}`); | ||
|
||
const validators = validatorService.validators; | ||
const committee = validatorService.committee; | ||
|
||
const totalPoolStake = validators.reduce((acc, cur) => BigInt(cur.poolStake) + acc, BigInt(0)); | ||
const totalValidatorStake = committee ? BigInt(committee.totalValidatorStake) : undefined; | ||
|
||
return { | ||
totalPoolStake: totalPoolStake.toString(), | ||
totalValidatorStake: totalValidatorStake.toString(), | ||
}; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,97 @@ | ||
/* eslint-disable import/no-unresolved */ | ||
/* eslint-disable @typescript-eslint/no-unsafe-return */ | ||
/* eslint-disable @typescript-eslint/no-redundant-type-constituents */ | ||
import { Client, ValidatorResponse, CommitteeResponse } from "@iota/sdk-nova"; | ||
import cron from "node-cron"; | ||
import { ServiceFactory } from "../../factories/serviceFactory"; | ||
import logger from "../../logger"; | ||
import { INetwork } from "../../models/db/INetwork"; | ||
|
||
/** | ||
* The collect validators interval cron expression. | ||
* Every hour at 55 min 55 sec | ||
*/ | ||
const COLLECT_VALIDATORS_CRON = "55 55 * * * *"; | ||
|
||
export class ValidatorService { | ||
/** | ||
* The current validators cache state. | ||
*/ | ||
protected _validatorsCache: ValidatorResponse[] = []; | ||
|
||
/** | ||
* The current committee cache state. | ||
*/ | ||
protected _committeeCache: CommitteeResponse | null = null; | ||
|
||
/** | ||
* The network in context for this client. | ||
*/ | ||
private readonly _network: INetwork; | ||
|
||
/** | ||
* The client to use for requests. | ||
*/ | ||
private readonly client: Client; | ||
|
||
/** | ||
* Create a new instance of ValidatorService. | ||
* @param network The network configuration. | ||
*/ | ||
constructor(network: INetwork) { | ||
this._network = network; | ||
this.client = ServiceFactory.get<Client>(`client-${network.network}`); | ||
} | ||
|
||
public get validators(): ValidatorResponse[] { | ||
return this._validatorsCache; | ||
} | ||
|
||
public get committee(): CommitteeResponse | null { | ||
return this._committeeCache; | ||
} | ||
|
||
public setupValidatorsCollection() { | ||
const network = this._network.network; | ||
logger.verbose(`[ValidatorService] Setting up validator collection for (${network})]`); | ||
|
||
// eslint-disable-next-line no-void | ||
void this.updateValidatorsCache(); | ||
// eslint-disable-next-line no-void | ||
void this.updateCommitteeCache(); | ||
|
||
cron.schedule(COLLECT_VALIDATORS_CRON, async () => { | ||
logger.verbose(`[ValidatorService] Collecting validators for "${this._network.network}"`); | ||
// eslint-disable-next-line no-void | ||
void this.updateValidatorsCache(); | ||
// eslint-disable-next-line no-void | ||
void this.updateCommitteeCache(); | ||
}); | ||
} | ||
|
||
private async updateValidatorsCache() { | ||
let cursor: string | undefined; | ||
let validators: ValidatorResponse[] = []; | ||
|
||
do { | ||
try { | ||
const validatorsResponse = await this.client.getValidators(undefined, cursor); | ||
|
||
validators = validators.concat(validatorsResponse.validators); | ||
cursor = validatorsResponse.cursor; | ||
} catch (e) { | ||
logger.error(`Fetching validators failed. Cause: ${e}`); | ||
} | ||
} while (cursor); | ||
|
||
this._validatorsCache = validators; | ||
} | ||
|
||
private async updateCommitteeCache() { | ||
try { | ||
this._committeeCache = await this.client.getCommittee(); | ||
} catch (e) { | ||
logger.error(`Fetching committee failed. Cause: ${e}`); | ||
} | ||
} | ||
} |