Skip to content

Commit 4072905

Browse files
authored
Merge pull request #245 from lidofinance/develop
Dev to main
2 parents da97145 + 64bccae commit 4072905

File tree

17 files changed

+221
-40
lines changed

17 files changed

+221
-40
lines changed

src/app/app.service.ts

Lines changed: 27 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
11
import { Inject, Injectable, LoggerService, OnModuleInit } from '@nestjs/common';
22
import { LOGGER_PROVIDER } from '@lido-nestjs/logger';
33

4-
import { ConfigService } from 'common/config';
4+
import { ConfigService, ENV_KEYS, EnvironmentVariables } from 'common/config';
55
import { PrometheusService } from 'common/prometheus';
66
import { ConsensusProviderService } from 'common/consensus-provider';
77
import { ExecutionProviderService } from 'common/execution-provider';
88
import { APP_NAME, APP_VERSION } from './app.constants';
9+
import { commonPatterns, satanizer } from '@lidofinance/satanizer';
910

1011
@Injectable()
1112
export class AppService implements OnModuleInit {
@@ -20,14 +21,8 @@ export class AppService implements OnModuleInit {
2021

2122
public async onModuleInit(): Promise<void> {
2223
await this.validateNetwork();
23-
24-
const network = await this.executionProviderService.getNetworkName();
25-
const env = this.configService.get('NODE_ENV');
26-
const version = APP_VERSION;
27-
const name = APP_NAME;
28-
29-
this.prometheusService.buildInfo.labels({ env, network, name, version }).inc();
30-
this.logger.log('Init app', { env, network, name, version });
24+
await this.prometheusBuildInfoMetrics();
25+
this.prometheusEnvsInfoMetrics();
3126
}
3227

3328
/**
@@ -43,4 +38,27 @@ export class AppService implements OnModuleInit {
4338
throw new Error('Chain ids do not match');
4439
}
4540
}
41+
42+
protected async prometheusBuildInfoMetrics() {
43+
const network = await this.executionProviderService.getNetworkName();
44+
const env = this.configService.get('NODE_ENV');
45+
const version = APP_VERSION;
46+
const name = APP_NAME;
47+
48+
this.prometheusService.buildInfo.labels({ env, network, name, version }).inc();
49+
this.logger.log('Init app', { env, network, name, version });
50+
}
51+
52+
protected prometheusEnvsInfoMetrics() {
53+
const secrets = this.configService.secrets;
54+
const mask = satanizer([...commonPatterns, ...secrets]);
55+
56+
const allConfigEnvs = {};
57+
ENV_KEYS.forEach((key: keyof EnvironmentVariables) => {
58+
allConfigEnvs[key] = mask(this.configService.get(key));
59+
});
60+
61+
this.prometheusService.envsInfo.labels(allConfigEnvs).inc();
62+
this.logger.log('Init app dumping envs', allConfigEnvs);
63+
}
4664
}

src/common/config/env.validation.ts

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ export class EnvironmentVariables {
1818
@IsNumber()
1919
@Min(1)
2020
@Transform(toNumber({ defaultValue: 3000 }))
21-
PORT: number;
21+
PORT: number = 3000;
2222

2323
@IsOptional()
2424
@IsString()
@@ -49,16 +49,16 @@ export class EnvironmentVariables {
4949
@IsOptional()
5050
@IsEnum(LogLevel)
5151
@Transform(({ value }) => value || LogLevel.info)
52-
LOG_LEVEL: LogLevel;
52+
LOG_LEVEL: LogLevel = null;
5353

5454
@IsOptional()
5555
@IsEnum(LogFormat)
5656
@Transform(({ value }) => value || LogFormat.json)
57-
LOG_FORMAT: LogFormat;
57+
LOG_FORMAT: LogFormat = null;
5858

5959
@IsOptional()
6060
@IsString()
61-
JOB_INTERVAL_VALIDATORS;
61+
JOB_INTERVAL_VALIDATORS = null;
6262

6363
@IsOptional()
6464
@IsString()
@@ -71,17 +71,18 @@ export class EnvironmentVariables {
7171
@IsArray()
7272
@ArrayMinSize(1)
7373
@Transform(({ value }) => value.split(','))
74-
CL_API_URLS!: string[];
74+
CL_API_URLS: string[] = null;
7575

7676
@IsArray()
7777
@ArrayMinSize(1)
7878
@Transform(({ value }) => value.split(','))
79-
EL_RPC_URLS!: string[];
79+
EL_RPC_URLS: string[] = null;
8080

8181
@IsNumber()
8282
@Transform(({ value }) => Number(value))
83-
CHAIN_ID!: number;
83+
CHAIN_ID: number = null;
8484
}
85+
export const ENV_KEYS = Object.keys(new EnvironmentVariables());
8586

8687
export function validate(config: Record<string, unknown>) {
8788
const validatedConfig = plainToClass(EnvironmentVariables, config);

src/common/health/health.module.ts

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,34 @@
1-
import { Module } from '@nestjs/common';
1+
import { Inject, LoggerService, Module, OnModuleInit } from '@nestjs/common';
22
import { TerminusModule } from '@nestjs/terminus';
33
import { HealthController } from './health.controller';
44
import { ExecutionProviderHealthIndicator } from './execution-provider.indicator';
55
import { ConsensusProviderIndicator } from './consensus-provider.indicator';
66
import { GenesisTimeModule } from '../genesis-time';
7+
import { LOGGER_PROVIDER } from '@lido-nestjs/logger';
78

89
@Module({
910
providers: [ExecutionProviderHealthIndicator, ConsensusProviderIndicator],
1011
controllers: [HealthController],
1112
imports: [TerminusModule, GenesisTimeModule],
1213
})
13-
export class HealthModule {}
14+
export class HealthModule implements OnModuleInit {
15+
constructor(
16+
@Inject(LOGGER_PROVIDER) protected readonly logger: LoggerService,
17+
protected readonly consensusProviderIndicator: ConsensusProviderIndicator,
18+
protected readonly executionProviderIndicator: ExecutionProviderHealthIndicator,
19+
) {}
20+
21+
async onModuleInit() {
22+
await this.startUpChecks();
23+
}
24+
25+
async startUpChecks() {
26+
try {
27+
await this.consensusProviderIndicator.isHealthy('consensusProvider');
28+
await this.executionProviderIndicator.isHealthy('executionProvider');
29+
this.logger.log(`Start up checks passed successfully`);
30+
} catch (e) {
31+
this.logger.error(`Start up checks failed with error: ${e}`);
32+
}
33+
}
34+
}

src/common/prometheus/prometheus.service.ts

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { getOrCreateMetric } from '@willsoto/nestjs-prometheus';
22
import { Options, Metrics, Metric } from './interfaces';
33
import { METRICS_PREFIX } from './prometheus.constants';
44
import { RequestSourceType } from '../../http/request-time/headers/request-source-type';
5+
import { ENV_KEYS } from '../config';
56

67
export class PrometheusService {
78
protected prefix = METRICS_PREFIX;
@@ -25,7 +26,19 @@ export class PrometheusService {
2526
public buildInfo = this.getOrCreateMetric('Gauge', {
2627
name: 'build_info',
2728
help: 'Build information',
28-
labelNames: ['name', 'version', 'env', 'network', 'startSlot'],
29+
labelNames: ['name', 'version', 'env', 'network'],
30+
});
31+
32+
public envsInfo = this.getOrCreateMetric('Gauge', {
33+
name: METRICS_PREFIX + 'envs_info',
34+
help: 'Environment variables information',
35+
labelNames: ENV_KEYS,
36+
});
37+
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'],
2942
});
3043

3144
public clApiRequestDuration = this.getOrCreateMetric('Histogram', {
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/common/middleware/logger.middleware.ts

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
import { Inject, Injectable, LoggerService, NestMiddleware } from '@nestjs/common';
22
import { WINSTON_MODULE_NEST_PROVIDER } from 'nest-winston';
33
import { Request, Reply } from './interfaces';
4-
import { FastifyRequest } from 'fastify';
54

65
@Injectable()
76
export class LoggerMiddleware implements NestMiddleware {
@@ -10,15 +9,13 @@ export class LoggerMiddleware implements NestMiddleware {
109
private readonly logger: LoggerService,
1110
) {}
1211

13-
use(request: any, reply: Reply, next: () => void) {
12+
use(request: Request, reply: Reply, next: () => void) {
1413
const { ip, method, headers, originalUrl } = request;
1514
const userAgent = headers['user-agent'] ?? '';
1615

17-
const ips = request.ips ? request.ips : [];
18-
1916
reply.on('finish', () => {
2017
const { statusCode } = reply;
21-
const log = { method, originalUrl, statusCode, userAgent, ip, ips };
18+
const log = { method, originalUrl, statusCode, userAgent, ip };
2219

2320
this.logger.log(JSON.stringify(log));
2421
});

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+
}

0 commit comments

Comments
 (0)