Skip to content

Commit

Permalink
Merge pull request #174 from lidofinance/develop
Browse files Browse the repository at this point in the history
develop -> goerli
  • Loading branch information
infloop authored Jan 19, 2024
2 parents 27ecfe5 + 9298404 commit 72e8c40
Show file tree
Hide file tree
Showing 15 changed files with 1,057 additions and 98 deletions.
9 changes: 5 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "lido-council-daemon",
"version": "1.9.1",
"version": "2.0.0",
"description": "Lido Council Daemon",
"author": "Lido team",
"private": true,
Expand Down Expand Up @@ -33,6 +33,7 @@
"@chainsafe/ssz": "^0.9.2",
"@ethersproject/providers": "^5.4.5",
"@lido-nestjs/fetch": "^1.3.1",
"@lido-nestjs/key-validation": "^7.4.0",
"@lido-nestjs/middleware": "^1.1.1",
"@lido-sdk/constants": "^3.2.1",
"@nestjs/common": "^8.0.0",
Expand All @@ -41,24 +42,24 @@
"@nestjs/platform-express": "^8.1.1",
"@nestjs/schedule": "^2.1.0",
"@nestjs/terminus": "^8.0.1",
"@lido-nestjs/key-validation": "^7.4.0",
"@willsoto/nestjs-prometheus": "^4.0.1",
"app-root-path": "^3.0.0",
"cache-manager": "^3.6.3",
"class-transformer": "^0.5.1",
"class-validator": "^0.14.0",
"compare-versions": "^6.1.0",
"ethers": "^5.4.7",
"glob": "^7.1.2",
"kafkajs": "^1.15.0",
"lru-cache": "^9.1.1",
"nest-winston": "^1.6.1",
"node-abort-controller": "^3.0.1",
"prom-client": "^14.0.0",
"reflect-metadata": "^0.1.13",
"rimraf": "^3.0.2",
"rxjs": "^7.2.0",
"winston": "^3.3.3",
"ws": "^8.10.0",
"lru-cache": "^9.1.1"
"ws": "^8.10.0"
},
"devDependencies": {
"@nestjs/cli": "^8.2.5",
Expand Down
1 change: 1 addition & 0 deletions src/guardian/guardian.constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,4 @@ import { CronExpression } from '@nestjs/schedule';
export const GUARDIAN_DEPOSIT_RESIGNING_BLOCKS = 10;
export const GUARDIAN_DEPOSIT_JOB_NAME = 'guardian-deposit-job';
export const GUARDIAN_DEPOSIT_JOB_DURATION = CronExpression.EVERY_5_SECONDS;
export const MIN_KAPI_VERSION = '0.10.2';
2 changes: 2 additions & 0 deletions src/guardian/guardian.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { BlockGuardModule } from './block-guard/block-guard.module';
import { StakingModuleGuardModule } from './staking-module-guard';
import { GuardianMessageModule } from './guardian-message';
import { GuardianMetricsModule } from './guardian-metrics';
import { KeysApiModule } from 'keys-api/keys-api.module';

@Module({
imports: [
Expand All @@ -23,6 +24,7 @@ import { GuardianMetricsModule } from './guardian-metrics';
StakingModuleGuardModule,
GuardianMessageModule,
GuardianMetricsModule,
KeysApiModule,
],
providers: [GuardianService],
exports: [GuardianService],
Expand Down
30 changes: 30 additions & 0 deletions src/guardian/guardian.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import {
LoggerService,
OnModuleInit,
} from '@nestjs/common';
import { compare } from 'compare-versions';
import { WINSTON_MODULE_NEST_PROVIDER } from 'nest-winston';
import { SchedulerRegistry } from '@nestjs/schedule';
import { CronJob } from 'cron';
Expand All @@ -22,6 +23,9 @@ import { StakingModuleGuardService } from './staking-module-guard';
import { GuardianMessageService } from './guardian-message';
import { GuardianMetricsService } from './guardian-metrics';
import { StakingModuleData } from './interfaces';
import { ProviderService } from 'provider';
import { KeysApiService } from 'keys-api/keys-api.service';
import { MIN_KAPI_VERSION } from './guardian.constants';

@Injectable()
export class GuardianService implements OnModuleInit {
Expand All @@ -42,6 +46,9 @@ export class GuardianService implements OnModuleInit {
private stakingModuleGuardService: StakingModuleGuardService,
private guardianMessageService: GuardianMessageService,
private guardianMetricsService: GuardianMetricsService,

private providerService: ProviderService,
private keysApiService: KeysApiService,
) {}

public async onModuleInit(): Promise<void> {
Expand All @@ -57,6 +64,29 @@ export class GuardianService implements OnModuleInit {
this.securityService.initialize({ blockHash }),
]);

const chainId = await this.providerService.getChainId();
const keysApiStatus = await this.keysApiService.getKeysApiStatus();

if (chainId !== keysApiStatus.chainId) {
this.logger.warn('Wrong KAPI chainId', {
chainId,
keysApiChainId: keysApiStatus.chainId,
});
throw new Error(
'The ChainId in KeysAPI must match the ChainId in EL Node',
);
}

if (!compare(keysApiStatus.appVersion, MIN_KAPI_VERSION, '>=')) {
this.logger.warn('Wrong KAPI version', {
minKAPIVersion: MIN_KAPI_VERSION,
keysApiVersion: keysApiStatus.appVersion,
});
throw new Error(
`The KAPI version must be greater than or equal to ${MIN_KAPI_VERSION}`,
);
}

// The event cache is stored with an N block lag to avoid caching data from uncle blocks
// so we don't worry about blockHash here
await this.depositService.updateEventsCache();
Expand Down
58 changes: 11 additions & 47 deletions src/guardian/keys-validation/keys-validation.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,49 +43,8 @@ describe('KeysValidationService', () => {
validateKeysFun = jest.spyOn(keysValidator, 'validateKeys');
});

it('add new key in empty cache', async () => {
// add a new keys
const result = await keysValidationService.findInvalidKeys(
[...validKeys, invalidKey],
wc,
);

const expected = [invalidKey].map((key) => ({
key: key.key,
depositSignature: key.depositSignature,
}));

const fork = GENESIS_FORK_VERSION_BY_CHAIN_ID[5];

const depositData = [...validKeys, invalidKey].map((key) => ({
key: key.key,
depositSignature: key.depositSignature,
withdrawalCredentials: bufferFromHexString(wc),
genesisForkVersion: Buffer.from(fork.buffer),
}));

expect(validateKeysFun).toBeCalledTimes(1);
expect(validateKeysFun).toBeCalledWith(depositData);
expect(result).toEqual(expect.arrayContaining(expected));
expect(result.length).toEqual(1);

validateKeysFun.mockClear();

const newResult = await keysValidationService.findInvalidKeys(
[...validKeys, invalidKey],
wc,
);

expect(validateKeysFun).toBeCalledTimes(1);
expect(validateKeysFun).toBeCalledWith([]);
// will be read from cache
expect(newResult).toEqual(expect.arrayContaining(expected));
expect(result.length).toEqual(1);
});

it('add new key in non empty cache', async () => {
// will include in result only invalid keys from actual list
// add a new keys
it('should find and return invalid keys from the provided list', async () => {
// Test scenario where new invalid keys are added to the list
const result = await keysValidationService.findInvalidKeys(
[...validKeys, invalidKey, invalidKey2],
wc,
Expand All @@ -110,8 +69,8 @@ describe('KeysValidationService', () => {
expect(result).toEqual(expect.arrayContaining(expected));
expect(result.length).toEqual(expected.length);

// one invalid key was deleted
validateKeysFun.mockClear();
// Test scenario where one invalid key was removed from request's list
const newResult = await keysValidationService.findInvalidKeys(
[...validKeys, invalidKey],
wc,
Expand All @@ -122,16 +81,21 @@ describe('KeysValidationService', () => {
depositSignature: key.depositSignature,
}));

expect(keysValidationService['keysCache'].get(invalidKey2.key)).toEqual({
isValid: false,
signature: invalidKey2.depositSignature,
});

expect(validateKeysFun).toBeCalledTimes(1);
expect(validateKeysFun).toBeCalledWith([]);
expect(newResult).toEqual(expect.arrayContaining(newExpected));
expect(newResult.length).toEqual(newExpected.length);
});

it('validate key again if signature was changed', async () => {
it('should validate key again if signature was changed', async () => {
// if signature was changed we need to repeat validation
// invalid key could become valid and visa versa
// add a new keys
// Test scenario where new invalid keys are added to the list
const result = await keysValidationService.findInvalidKeys(
[...validKeys, invalidKey, invalidKey2],
wc,
Expand All @@ -156,8 +120,8 @@ describe('KeysValidationService', () => {
expect(result).toEqual(expect.arrayContaining(expected));
expect(result.length).toEqual(expected.length);

// repeat and check that cache will be used
validateKeysFun.mockClear();
// Test scenario where one invalid key was changed
const newResult = await keysValidationService.findInvalidKeys(
[
...validKeys,
Expand Down
20 changes: 15 additions & 5 deletions src/guardian/staking-module-guard/staking-module-guard.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -160,7 +160,10 @@ export class StakingModuleGuardService {

// if found used keys, Lido already made deposit on this keys
if (usedKeys.length) {
this.logger.log('Found that we already deposited on these keys');
this.logger.log('Found that we already deposited on these keys', {
blockHash,
stakingModuleId,
});
this.guardianMetricsService.incrDuplicatedUsedKeysEventCounter();
return;
}
Expand Down Expand Up @@ -252,11 +255,12 @@ export class StakingModuleGuardService {
return [];
}

// TODO: add staking module id
this.logger.log(
'Found intersections with lido credentials, need to check used duplicated keys',
);

const { data, meta } = await this.stakingRouterService.findKeysEntires(
const { data, meta } = await this.stakingRouterService.getKeysByPubkeys(
depositedPubkeys,
);

Expand Down Expand Up @@ -355,7 +359,7 @@ export class StakingModuleGuardService {

// need to check invalidKeysFound
if (isSameContractsState) {
this.logger.log("Contract states didn't change");
this.logger.log("Contract states didn't change", { stakingModuleId });
return;
}

Expand All @@ -379,6 +383,7 @@ export class StakingModuleGuardService {
};

this.logger.log('No problems found', {
stakingModuleId,
blockHash,
lastState: lastContractsState,
newState: currentContractState,
Expand All @@ -391,6 +396,7 @@ export class StakingModuleGuardService {
stakingModuleData: StakingModuleData,
blockData: BlockData,
): Promise<boolean> {
// TODO: consider change state on upper level
const { blockNumber, depositRoot } = blockData;
const { nonce, stakingModuleId, lastChangedBlockHash } = stakingModuleData;
const lastContractsState =
Expand All @@ -404,7 +410,7 @@ export class StakingModuleGuardService {
// if found invalid keys on previous iteration and lastChangedBlockHash returned by kapi was not changed
// we dont need to validate again, but we still need to skip deposits until problem will not be solved
this.logger.error(
'LastChangedBlockHash was not changed and on previous iteration we found invalid keys, skip until solving problem ',
`LastChangedBlockHash was not changed and on previous iteration we found invalid keys, skip until solving problem, stakingModuleId: ${stakingModuleId}`,
);

this.lastContractsStateByModuleId[stakingModuleId] = {
Expand All @@ -431,7 +437,7 @@ export class StakingModuleGuardService {
// if found invalid keys, update state and exit
if (invalidKeys.length) {
this.logger.error(
'Found invalid keys, will skip deposits until solving problem',
`Found invalid keys, will skip deposits until solving problem, stakingModuleId: ${stakingModuleId}`,
);
this.guardianMetricsService.incrInvalidKeysEventCounter();
// save info about invalid keys in cache
Expand Down Expand Up @@ -459,17 +465,21 @@ export class StakingModuleGuardService {
): Promise<{ key: string; depositSignature: string }[]> {
this.logger.log('Start keys validation', {
keysCount: stakingModuleData.vettedUnusedKeys.length,
stakingModuleId: stakingModuleData.stakingModuleId,
});
const validationTimeStart = performance.now();

const invalidKeysList = await this.keysValidationService.findInvalidKeys(
stakingModuleData.vettedUnusedKeys,
blockData.lidoWC,
);

const validationTimeEnd = performance.now();
const validationTime =
Math.ceil(validationTimeEnd - validationTimeStart) / 1000;

this.logger.log('Keys validated', {
stakingModuleId: stakingModuleData.stakingModuleId,
invalidKeysList,
validationTime,
});
Expand Down
Loading

0 comments on commit 72e8c40

Please sign in to comment.