Skip to content

Commit 64bccae

Browse files
authored
Merge pull request #244 from lidofinance/feature/si-1580-add-validator-info-log-and-endpoint
Feature/si 1580 add validator info log and endpoint
2 parents c50af3e + c878f0d commit 64bccae

File tree

13 files changed

+153
-16
lines changed

13 files changed

+153
-16
lines changed

src/common/prometheus/prometheus.service.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,12 @@ export class PrometheusService {
3535
labelNames: ENV_KEYS,
3636
});
3737

38+
public validatorsState = this.getOrCreateMetric('Gauge', {
39+
name: METRICS_PREFIX + 'validators_state',
40+
help: 'balances of Lido validators with withdrawable_epoch by frames',
41+
labelNames: ['frame', 'balance'],
42+
});
43+
3844
public clApiRequestDuration = this.getOrCreateMetric('Histogram', {
3945
name: METRICS_PREFIX + 'cl_api_requests_duration_seconds',
4046
help: 'CL API request duration',
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
import { BigNumber } from '@ethersproject/bignumber';
2+
3+
export function stringifyFrameBalances(frameBalances: Record<string, BigNumber>) {
4+
return JSON.stringify(
5+
Object.keys(frameBalances).reduce((acc, key) => {
6+
return { ...acc, [key]: frameBalances[key].toString() };
7+
}, {}),
8+
);
9+
}

src/http/http.constants.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
export const HTTP_PATHS = {
22
1: {
33
nft: 'nft',
4+
'validators-info': 'validators-info',
45
'request-time': 'request-time',
56
'estimate-gas': 'estimate-gas',
67
},

src/http/http.module.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,10 @@ import { CacheModule, CacheControlHeadersInterceptor } from './common/cache';
1212
import { RequestTimeModule } from './request-time';
1313
import { NFTModule } from './nft';
1414
import { EstimateModule } from './estimate';
15+
import { ValidatorsModule } from './validators';
1516

1617
@Module({
17-
imports: [RequestTimeModule, NFTModule, EstimateModule, CacheModule, ThrottlerModule],
18+
imports: [RequestTimeModule, NFTModule, EstimateModule, ValidatorsModule, CacheModule, ThrottlerModule],
1819
providers: [
1920
{ provide: APP_GUARD, useClass: ThrottlerBehindProxyGuard },
2021
{ provide: APP_INTERCEPTOR, useClass: CacheControlHeadersInterceptor },

src/http/validators/dto/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export * from './validators.dto';
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
import { ApiProperty } from '@nestjs/swagger';
2+
3+
export class ValidatorsDto {
4+
@ApiProperty({
5+
example: 1658650005,
6+
description: 'ms time when data was last updated at',
7+
})
8+
lastUpdatedAt: number;
9+
10+
@ApiProperty({
11+
example: 1724856617,
12+
description: 'max exit epoch over all CL network',
13+
})
14+
maxExitEpoch: number;
15+
16+
@ApiProperty({
17+
example: '{}',
18+
description: 'sum of balances Lido validators with withdrawable_epoch by frame',
19+
})
20+
frameBalances: Record<string, string>;
21+
22+
@ApiProperty({
23+
example: 100000,
24+
description: 'total number of validators in network',
25+
})
26+
totalValidators: number;
27+
28+
@ApiProperty({
29+
example: 100000,
30+
description: 'current frame',
31+
})
32+
currentFrame: number;
33+
}

src/http/validators/index.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
export * from './validators.controller';
2+
export * from './validators.module';
3+
export * from './validators.service';
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
import {
2+
ClassSerializerInterceptor,
3+
Controller,
4+
Get,
5+
HttpStatus,
6+
UseInterceptors,
7+
Version,
8+
CacheTTL,
9+
} from '@nestjs/common';
10+
import { ApiResponse, ApiTags } from '@nestjs/swagger';
11+
import { HTTP_PATHS } from 'http/http.constants';
12+
import { ValidatorsService } from './validators.service';
13+
import { ValidatorsDto } from './dto';
14+
15+
@Controller()
16+
@ApiTags('Validators')
17+
@UseInterceptors(ClassSerializerInterceptor)
18+
export class ValidatorsController {
19+
constructor(protected readonly validatorsService: ValidatorsService) {}
20+
21+
@Version('1')
22+
@Get(HTTP_PATHS[1]['validators-info'])
23+
@CacheTTL(20 * 1000)
24+
@ApiResponse({ status: HttpStatus.OK, type: ValidatorsDto })
25+
async validatorsV1(): Promise<ValidatorsDto> {
26+
return this.validatorsService.getValidatorsInfo();
27+
}
28+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import { Module } from '@nestjs/common';
2+
import { ConfigModule } from 'common/config';
3+
import { ValidatorsController } from './validators.controller';
4+
import { ValidatorsService } from './validators.service';
5+
import { ValidatorsStorageModule } from '../../storage';
6+
import { GenesisTimeModule } from '../../common/genesis-time';
7+
8+
@Module({
9+
imports: [ConfigModule, ValidatorsStorageModule, GenesisTimeModule],
10+
controllers: [ValidatorsController],
11+
providers: [ValidatorsService],
12+
})
13+
export class ValidatorsModule {}
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
import { Injectable } from '@nestjs/common';
2+
import { ConfigService } from 'common/config';
3+
import { ValidatorsStorageService } from '../../storage';
4+
import { GenesisTimeService } from '../../common/genesis-time';
5+
6+
@Injectable()
7+
export class ValidatorsService {
8+
constructor(
9+
protected readonly configService: ConfigService,
10+
protected readonly validatorsServiceStorage: ValidatorsStorageService,
11+
protected readonly genesisTimeService: GenesisTimeService,
12+
) {}
13+
14+
getValidatorsInfo() {
15+
const lastUpdatedAt = this.validatorsServiceStorage.getLastUpdate();
16+
const maxExitEpoch = Number(this.validatorsServiceStorage.getMaxExitEpoch());
17+
const frameBalancesBigNumber = this.validatorsServiceStorage.getFrameBalances();
18+
const totalValidators = this.validatorsServiceStorage.getTotal();
19+
const currentFrame = this.genesisTimeService.getFrameOfEpoch(this.genesisTimeService.getCurrentEpoch());
20+
21+
const frameBalances = Object.keys(frameBalancesBigNumber).reduce((acc, item) => {
22+
acc[item] = frameBalancesBigNumber[item].toString();
23+
return acc;
24+
}, {} as Record<string, string>);
25+
26+
return {
27+
lastUpdatedAt,
28+
maxExitEpoch,
29+
frameBalances,
30+
totalValidators,
31+
currentFrame,
32+
};
33+
}
34+
}

src/jobs/validators/validators.service.ts

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,13 +16,16 @@ import { ResponseValidatorsData, Validator } from './validators.types';
1616
import { parseGweiToWei } from '../../common/utils/parse-gwei-to-big-number';
1717
import { ValidatorsCacheService } from 'storage/validators/validators-cache.service';
1818
import { CronExpression } from '@nestjs/schedule';
19+
import { PrometheusService } from '../../common/prometheus';
20+
import { stringifyFrameBalances } from '../../common/validators/strigify-frame-balances';
1921

2022
export class ValidatorsService {
2123
static SERVICE_LOG_NAME = 'validators';
2224

2325
constructor(
2426
@Inject(LOGGER_PROVIDER) protected readonly logger: LoggerService,
2527

28+
protected readonly prometheusService: PrometheusService,
2629
protected readonly consensusProviderService: ConsensusProviderService,
2730
protected readonly configService: ConfigService,
2831
protected readonly jobService: JobService,
@@ -78,23 +81,36 @@ export class ValidatorsService {
7881

7982
await unblock();
8083
}
81-
await this.setLidoValidatorsWithdrawableBalances(data);
8284
this.validatorsStorageService.setTotal(totalValidators);
8385
this.validatorsStorageService.setMaxExitEpoch(latestEpoch);
8486
this.validatorsStorageService.setLastUpdate(Math.floor(Date.now() / 1000));
8587

88+
const frameBalances = await this.getLidoValidatorsWithdrawableBalances(data);
89+
this.validatorsStorageService.setFrameBalances(frameBalances);
8690
await this.validatorsCacheService.saveDataToCache();
8791

92+
const currentFrame = this.genesisTimeService.getFrameOfEpoch(this.genesisTimeService.getCurrentEpoch());
8893
this.logger.log('End update validators', {
8994
service: ValidatorsService.SERVICE_LOG_NAME,
9095
totalValidators,
9196
latestEpoch,
97+
frameBalances: stringifyFrameBalances(frameBalances),
98+
currentFrame,
99+
});
100+
101+
Object.keys(frameBalances).forEach((frame) => {
102+
this.prometheusService.validatorsState
103+
.labels({
104+
frame,
105+
balance: frameBalances[frame],
106+
})
107+
.inc();
92108
});
93109
},
94110
);
95111
}
96112

97-
protected async setLidoValidatorsWithdrawableBalances(validators: Validator[]) {
113+
protected async getLidoValidatorsWithdrawableBalances(validators: Validator[]) {
98114
const keysData = await this.lidoKeys.fetchLidoKeysData();
99115
const lidoValidators = await this.lidoKeys.getLidoValidatorsByKeys(keysData.data, validators);
100116

@@ -111,6 +127,6 @@ export class ValidatorsService {
111127
await unblock();
112128
}
113129

114-
this.validatorsStorageService.setFrameBalances(frameBalances);
130+
return frameBalances;
115131
}
116132
}

src/storage/validators/validators-cache.service.ts

Lines changed: 3 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import * as path from 'path';
44
import { LOGGER_PROVIDER, LoggerService } from '../../common/logger';
55
import { ValidatorsStorageService } from './validators.service';
66
import { BigNumber } from '@ethersproject/bignumber';
7-
import { parseEther } from '@ethersproject/units';
7+
import { stringifyFrameBalances } from '../../common/validators/strigify-frame-balances';
88

99
@Injectable()
1010
export class ValidatorsCacheService {
@@ -76,7 +76,7 @@ export class ValidatorsCacheService {
7676
this.validatorsStorage.getTotal(),
7777
this.validatorsStorage.getMaxExitEpoch(),
7878
this.validatorsStorage.getLastUpdate(),
79-
this.stringifyFrameBalances(this.validatorsStorage.getFrameBalances()),
79+
stringifyFrameBalances(this.validatorsStorage.getFrameBalances()),
8080
].join(ValidatorsCacheService.CACHE_DATA_DIVIDER);
8181
await writeFile(cacheFileName, data);
8282
this.logger.log(`success save to file ${cacheFileName}`, { service: ValidatorsCacheService.SERVICE_LOG_NAME });
@@ -86,18 +86,10 @@ export class ValidatorsCacheService {
8686
return path.join(ValidatorsCacheService.CACHE_DIR, ValidatorsCacheService.CACHE_FILE_NAME);
8787
};
8888

89-
protected stringifyFrameBalances(frameBalances: Record<string, BigNumber>) {
90-
return JSON.stringify(
91-
Object.keys(frameBalances).reduce((acc, key) => {
92-
return { ...acc, [key]: frameBalances[key].toString() };
93-
}, {}),
94-
);
95-
}
96-
9789
protected parseFrameBalances(frameBalancesStr: string) {
9890
const frameBalances = JSON.parse(frameBalancesStr);
9991
return Object.keys(frameBalances).reduce((acc, key) => {
100-
return { ...acc, [key]: parseEther(frameBalances[key]) };
92+
return { ...acc, [key]: BigNumber.from(frameBalances[key]) };
10193
}, {});
10294
}
10395
}

src/waiting-time/utils/calculate-frame-by-validator-balances.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ export const calculateFrameByValidatorBalances = (args: calculateFrameByValidato
1717
let lastFrame = BigNumber.from(currentFrame);
1818

1919
const frames = Object.keys(frameBalances);
20-
let result = null;
20+
let result: BigNumber = null;
2121

2222
for (let i = 0; i < frames.length; i++) {
2323
const frame = frames[i];

0 commit comments

Comments
 (0)