Skip to content

Commit

Permalink
fix: moved code from staking-router-service
Browse files Browse the repository at this point in the history
  • Loading branch information
Amuhar committed Aug 7, 2023
1 parent 16911c8 commit 065b319
Show file tree
Hide file tree
Showing 9 changed files with 314 additions and 516 deletions.
44 changes: 40 additions & 4 deletions src/http/sr-modules-keys/sr-modules-keys.service.ts
Original file line number Diff line number Diff line change
@@ -1,24 +1,44 @@
import { Inject, Injectable, LoggerService } from '@nestjs/common';
import { ConfigService } from 'common/config';
import { GroupedByModuleKeyListResponse, SRModuleKeyListResponse } from './entities';
import { SRModuleKeyListResponse } from './entities';
import { ModuleId, KeyQuery, Key, ELBlockSnapshot, SRModule } from 'http/common/entities';
import { LOGGER_PROVIDER } from '@lido-nestjs/logger';
import { StakingRouterService } from 'staking-router-modules/staking-router.service';
import { KeyEntity } from 'staking-router-modules/interfaces/staking-module.interface';
// TODO: maybe moved it from staking-router-modules/interfaces/filters
import { KeyField } from 'staking-router-modules/interfaces/filters';
import { EntityManager } from '@mikro-orm/knex';
import { IsolationLevel } from '@mikro-orm/core';

@Injectable()
export class SRModulesKeysService {
constructor(
@Inject(LOGGER_PROVIDER) protected readonly logger: LoggerService,
protected configService: ConfigService,
protected stakingRouterService: StakingRouterService,
protected readonly entityManager: EntityManager,
) {}

async getGroupedByModuleKeys(filters: KeyQuery): Promise<{
keysGeneratorsByModules: { keysGenerator: AsyncGenerator<Key>; module: SRModule }[];
meta: { elBlockSnapshot: ELBlockSnapshot };
}> {
const { keysGeneratorsByModules, elBlockSnapshot } = await this.stakingRouterService.getKeysByModules(filters);
const { stakingModules, elBlockSnapshot } = await this.stakingRouterService.getStakingModulesAndMeta();
const keysGeneratorsByModules: { keysGenerator: any; module: SRModule }[] = [];

for (const module of stakingModules) {
// read from config name of module that implement functions to fetch and store keys for type
// TODO: check what will happen if implementation is not a provider of StakingRouterModule
const moduleInstance = this.stakingRouterService.getStakingRouterModuleImpl(module.type);
const fields: KeyField[] = ['key', 'depositSignature', 'operatorIndex', 'used'];
const keysGenerator: AsyncGenerator<Key> = await moduleInstance.getKeysStream(
module.stakingModuleAddress,
filters,
fields,
);

keysGeneratorsByModules.push({ keysGenerator, module: new SRModule(module) });
}

return { keysGeneratorsByModules, meta: { elBlockSnapshot } };
}
Expand All @@ -31,13 +51,29 @@ export class SRModulesKeysService {
module: SRModule;
meta: { elBlockSnapshot: ELBlockSnapshot };
}> {
const { keysGenerator, module, elBlockSnapshot } = await this.stakingRouterService.getModuleKeys(moduleId, filters);
const { module, elBlockSnapshot } = await this.stakingRouterService.getStakingModuleAndMeta(moduleId);
const moduleInstance = this.stakingRouterService.getStakingRouterModuleImpl(module.type);

const keysGenerator: AsyncGenerator<KeyEntity> = await moduleInstance.getKeysStream(
module.stakingModuleAddress,
filters,
);

return { keysGenerator, module, meta: { elBlockSnapshot } };
}

async getModuleKeysByPubKeys(moduleId: ModuleId, pubKeys: string[]): Promise<SRModuleKeyListResponse> {
const { keys, module, elBlockSnapshot } = await this.stakingRouterService.getModuleKeysByPubKeys(moduleId, pubKeys);
const { keys, module, elBlockSnapshot } = await this.entityManager.transactional(
async () => {
const { module, elBlockSnapshot } = await this.stakingRouterService.getStakingModuleAndMeta(moduleId);
const moduleInstance = this.stakingRouterService.getStakingRouterModuleImpl(module.type);
const keys: KeyEntity[] = await moduleInstance.getKeysByPubKeys(module.stakingModuleAddress, pubKeys);

return { keys, module, elBlockSnapshot };
},
{ isolationLevel: IsolationLevel.REPEATABLE_READ },
);

return {
data: { keys, module },
meta: { elBlockSnapshot },
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,18 @@ import { Inject, Injectable, LoggerService } from '@nestjs/common';
import { ELBlockSnapshot, ModuleId, SRModule } from 'http/common/entities';
import { KeyQuery } from 'http/common/entities';
import { ConfigService } from 'common/config';
import { SRModuleOperatorsKeysResponse } from './entities';
import { LOGGER_PROVIDER } from '@lido-nestjs/logger';
import { StakingRouterService } from 'staking-router-modules/staking-router.service';
import { KeyEntity, OperatorEntity } from 'staking-router-modules/interfaces/staking-module.interface';
import { EntityManager } from '@mikro-orm/knex';

@Injectable()
export class SRModulesOperatorsKeysService {
constructor(
@Inject(LOGGER_PROVIDER) protected readonly logger: LoggerService,
protected readonly configService: ConfigService,
protected stakingRouterService: StakingRouterService,
protected readonly entityManager: EntityManager,
) {}

public async get(
Expand All @@ -24,8 +25,17 @@ export class SRModulesOperatorsKeysService {
module: SRModule;
meta: { elBlockSnapshot: ELBlockSnapshot };
}> {
const { operators, keysGenerator, module, elBlockSnapshot } =
await this.stakingRouterService.getModuleOperatorsAndKeys(moduleId, filters);
const { module, elBlockSnapshot } = await this.stakingRouterService.getStakingModuleAndMeta(moduleId);

const moduleInstance = this.stakingRouterService.getStakingRouterModuleImpl(module.type);

const keysGenerator: AsyncGenerator<KeyEntity> = await moduleInstance.getKeysStream(
module.stakingModuleAddress,
filters,
);
const operatorsFilter = filters.operatorIndex ? { index: filters.operatorIndex } : {};
const operators: OperatorEntity[] = await moduleInstance.getOperators(module.stakingModuleAddress, operatorsFilter);

return { operators, keysGenerator, module, meta: { elBlockSnapshot } };
}
}
63 changes: 56 additions & 7 deletions src/http/sr-modules-operators/sr-modules-operators.service.ts
Original file line number Diff line number Diff line change
@@ -1,30 +1,63 @@
import { Inject, Injectable, LoggerService } from '@nestjs/common';
import { Inject, Injectable, LoggerService, NotFoundException } from '@nestjs/common';
import { ConfigService } from 'common/config';
import { ModuleId } from 'http/common/entities/';
import { ModuleId, SRModule } from 'http/common/entities/';
import {
GroupedByModuleOperatorListResponse,
SRModuleOperatorListResponse,
SRModuleOperatorResponse,
} from './entities';
import { LOGGER_PROVIDER } from '@lido-nestjs/logger';
import { StakingRouterService } from 'staking-router-modules/staking-router.service';
import { EntityManager } from '@mikro-orm/knex';
import { OperatorEntity } from 'staking-router-modules/interfaces/staking-module.interface';
import { IsolationLevel } from '@mikro-orm/core';

@Injectable()
export class SRModulesOperatorsService {
constructor(
@Inject(LOGGER_PROVIDER) protected readonly logger: LoggerService,
protected configService: ConfigService,
protected stakingRouterService: StakingRouterService,
protected readonly entityManager: EntityManager,
) {}

public async getAll(): Promise<GroupedByModuleOperatorListResponse> {
const { operatorsByModules, elBlockSnapshot } = await this.stakingRouterService.getOperatorsByModules();
const { operatorsByModules, elBlockSnapshot } = await this.entityManager.transactional(
async () => {
const { stakingModules, elBlockSnapshot } = await this.stakingRouterService.getStakingModulesAndMeta();
// TODO: have in http/entities/curated-operator
const operatorsByModules: { operators: OperatorEntity[]; module: SRModule }[] = [];

for (const module of stakingModules) {
const moduleInstance = this.stakingRouterService.getStakingRouterModuleImpl(module.type);
// /v1/operators return these common fields for all modules
const operators: OperatorEntity[] = await moduleInstance.getOperators(module.stakingModuleAddress, {});

operatorsByModules.push({ operators, module: new SRModule(module) });
}

return { operatorsByModules, elBlockSnapshot };
},
{ isolationLevel: IsolationLevel.REPEATABLE_READ },
);

return { data: operatorsByModules, meta: { elBlockSnapshot } };
}

public async getByModule(moduleId: ModuleId): Promise<SRModuleOperatorListResponse> {
const { operators, module, elBlockSnapshot } = await this.stakingRouterService.getModuleOperators(moduleId);
const { operators, module, elBlockSnapshot } = await this.entityManager.transactional(
async () => {
const { module, elBlockSnapshot } = await this.stakingRouterService.getStakingModuleAndMeta(moduleId);

const moduleInstance = this.stakingRouterService.getStakingRouterModuleImpl(module.type);

// /v1/operators return these common fields for all modules
const operators: OperatorEntity[] = await moduleInstance.getOperators(module.stakingModuleAddress, {});

return { operators, module, elBlockSnapshot };
},
{ isolationLevel: IsolationLevel.REPEATABLE_READ },
);
return {
data: {
operators,
Expand All @@ -35,10 +68,26 @@ export class SRModulesOperatorsService {
}

public async getModuleOperator(moduleId: ModuleId, operatorIndex: number): Promise<SRModuleOperatorResponse> {
const { operator, module, elBlockSnapshot } = await this.stakingRouterService.getModuleOperator(
moduleId,
operatorIndex,
const { operator, module, elBlockSnapshot } = await this.entityManager.transactional(
async () => {
const { module, elBlockSnapshot } = await this.stakingRouterService.getStakingModuleAndMeta(moduleId);
const moduleInstance = this.stakingRouterService.getStakingRouterModuleImpl(module.type);

const operator: OperatorEntity | null = await moduleInstance.getOperator(
module.stakingModuleAddress,
operatorIndex,
);

return { operator, module, elBlockSnapshot };
},
{ isolationLevel: IsolationLevel.REPEATABLE_READ },
);

if (!operator) {
throw new NotFoundException(
`Operator with index ${operatorIndex} is not found for module with moduleId ${moduleId}`,
);
}
return {
data: { operator, module },
meta: { elBlockSnapshot },
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import { Module } from '@nestjs/common';
import { LoggerModule } from 'common/logger';
import { StakingRouterModule } from 'staking-router-modules';
import { ValidatorsModule } from 'validators';
import { SRModulesValidatorsController } from './sr-modules-validators.controller';
import { SRModulesValidatorsService } from './sr-modules-validators.service';

@Module({
imports: [LoggerModule, StakingRouterModule],
imports: [LoggerModule, StakingRouterModule, ValidatorsModule],
providers: [SRModulesValidatorsService],
controllers: [SRModulesValidatorsController],
})
Expand Down
82 changes: 69 additions & 13 deletions src/http/sr-modules-validators/sr-modules-validators.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,17 +12,23 @@ import { LOGGER_PROVIDER } from '@lido-nestjs/logger';
import { Validator } from '@lido-nestjs/validators-registry';
import { ValidatorsService } from 'validators';
import { StakingRouterService } from 'staking-router-modules/staking-router.service';

const VALIDATORS_REGISTRY_DISABLED_ERROR = 'Validators Registry is disabled. Check environment variables';
import { EntityManager } from '@mikro-orm/knex';
import {
DEFAULT_EXIT_PERCENT,
VALIDATORS_STATUSES_FOR_EXIT,
VALIDATORS_REGISTRY_DISABLED_ERROR,
} from 'validators/validators.constants';
import { httpExceptionTooEarlyResp } from 'http/common/entities/http-exceptions';
import { IsolationLevel } from '@mikro-orm/core';

@Injectable()
export class SRModulesValidatorsService {
constructor(
protected readonly configService: ConfigService,
// protected readonly curatedService: CuratedModuleService,
protected readonly validatorsService: ValidatorsService,
@Inject(LOGGER_PROVIDER) protected readonly logger: LoggerService,
protected stakingRouterService: StakingRouterService,
protected readonly entityManager: EntityManager,
) {}

async getOldestLidoValidators(
Expand All @@ -35,11 +41,7 @@ export class SRModulesValidatorsService {
throw new InternalServerErrorException(VALIDATORS_REGISTRY_DISABLED_ERROR);
}

const { validators, clBlockSnapshot } = await this.stakingRouterService.getOperatorOldestValidators(
moduleId,
operatorId,
filters,
);
const { validators, clBlockSnapshot } = await this.getOperatorOldestValidators(moduleId, operatorId, filters);
const data = this.createExitValidatorList(validators);

return {
Expand All @@ -60,11 +62,7 @@ export class SRModulesValidatorsService {
throw new InternalServerErrorException(VALIDATORS_REGISTRY_DISABLED_ERROR);
}

const { validators, clBlockSnapshot } = await this.stakingRouterService.getOperatorOldestValidators(
moduleId,
operatorId,
filters,
);
const { validators, clBlockSnapshot } = await this.getOperatorOldestValidators(moduleId, operatorId, filters);
const data = this.createExitPresignMessageList(validators, clBlockSnapshot);

return {
Expand All @@ -86,4 +84,62 @@ export class SRModulesValidatorsService {
private disabledRegistry() {
return !this.configService.get('VALIDATOR_REGISTRY_ENABLE');
}

private async getOperatorOldestValidators(
moduleId: string,
operatorIndex: number,
filters: ValidatorsQuery,
): Promise<{ validators: Validator[]; clBlockSnapshot: CLBlockSnapshot }> {
const { validators, meta } = await this.entityManager.transactional(
async () => {
const { module, elBlockSnapshot } = await this.stakingRouterService.getStakingModuleAndMeta(moduleId);

// read from config name of module that implement functions to fetch and store keys for type
// TODO: check what will happen if implementation is not a provider of StakingRouterModule
const moduleInstance = this.stakingRouterService.getStakingRouterModuleImpl(module.type);
const keys = await moduleInstance.getKeys(module.stakingModuleAddress, { operatorIndex }, ['key']);

const pubkeys = keys.map((pubkey) => pubkey.key);
const percent =
filters?.max_amount == undefined && filters?.percent == undefined ? DEFAULT_EXIT_PERCENT : filters?.percent;

const result = await this.validatorsService.getOldestValidators({
pubkeys,
statuses: VALIDATORS_STATUSES_FOR_EXIT,
max_amount: filters?.max_amount,
percent: percent,
});

if (!result) {
// if result of this method is null it means Validators Registry is disabled
throw new InternalServerErrorException(VALIDATORS_REGISTRY_DISABLED_ERROR);
}

const { validators, meta: clMeta } = result;

// check if clMeta is not null
// if it is null, it means keys db is empty and Updating Validators Job is not finished yet
if (!clMeta) {
this.logger.warn(`CL meta is empty, maybe first Updating Validators Job is not finished yet.`);
throw httpExceptionTooEarlyResp();
}

// We need EL meta always be actual
if (elBlockSnapshot.blockNumber < clMeta.blockNumber) {
this.logger.warn('Last Execution Layer block number in our database older than last Consensus Layer');
// add metric or alert on breaking el > cl condition
// TODO: what answer will be better here?
// TODO: describe in doc
throw new InternalServerErrorException(
'Last Execution Layer block number in our database older than last Consensus Layer',
);
}

return { validators, meta: clMeta };
},
{ isolationLevel: IsolationLevel.REPEATABLE_READ },
);

return { validators, clBlockSnapshot: new CLBlockSnapshot(meta) };
}
}
13 changes: 12 additions & 1 deletion src/jobs/keys-update/keys-update.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,20 @@ import { Module } from '@nestjs/common';
import { KeysUpdateService } from './keys-update.service';
import { LoggerModule } from 'common/logger';
import { JobModule } from 'common/job';
import { StakingRouterFetchModule } from 'staking-router-modules/contracts';
import { ExecutionProviderModule } from 'common/execution-provider';
import { StorageModule } from 'storage/storage.module';
import { PrometheusModule } from 'common/prometheus';

@Module({
imports: [LoggerModule, JobModule],
imports: [
LoggerModule,
JobModule,
StakingRouterFetchModule,
ExecutionProviderModule,
StorageModule,
PrometheusModule,
],
providers: [KeysUpdateService],
exports: [KeysUpdateService],
})
Expand Down
Loading

0 comments on commit 065b319

Please sign in to comment.