Skip to content

Commit

Permalink
Merge pull request #152 from lidofinance/feat/val-456-vetted-key-dupl…
Browse files Browse the repository at this point in the history
…icates

feat: duplicated vetted keys checks
  • Loading branch information
Amuhar authored Dec 26, 2023
2 parents cfdb713 + 483f6f4 commit d8e7dfb
Show file tree
Hide file tree
Showing 45 changed files with 3,422 additions and 290 deletions.
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -294,6 +294,8 @@ $ yarn test:e2e
$ yarn test:cov
```

To run e2e tests, ensure the RPC_URL environment variable is set to the Goerli provider's endpoint, and generate private keys, which should be subsequently set in the WALLET_PRIVATE_KEY variable.

## Release flow

To create a new release:
Expand Down
4 changes: 3 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@
"@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",
Expand All @@ -56,7 +57,8 @@
"rimraf": "^3.0.2",
"rxjs": "^7.2.0",
"winston": "^3.3.3",
"ws": "^8.10.0"
"ws": "^8.10.0",
"lru-cache": "^9.1.1"
},
"devDependencies": {
"@nestjs/cli": "^8.2.5",
Expand Down
10 changes: 10 additions & 0 deletions src/common/custom-errors.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
export class InconsistentLastChangedBlockHash extends Error {
constructor(
message = 'Since the last request, data in Kapi has been updated. This may result in inconsistencies between the data from two separate requests.',
) {
super(message);
this.name = 'InconsistentLastChangedBlockHash';

Object.setPrototypeOf(this, InconsistentLastChangedBlockHash.prototype);
}
}
6 changes: 6 additions & 0 deletions src/common/prometheus/prometheus.constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,3 +21,9 @@ export const METRIC_DEPOSITED_KEYS_TOTAL = `${METRICS_PREFIX}deposited_keys_tota
export const METRIC_OPERATORS_KEYS_TOTAL = `${METRICS_PREFIX}operators_keys_total`;

export const METRIC_KEYS_API_REQUEST_DURATION = `${METRICS_PREFIX}keys_api_requests_duration_seconds`;

export const METRIC_DUPLICATED_VETTED_UNUSED_KEYS_EVENT_COUNTER = `${METRICS_PREFIX}vetted_unused_keys_event_total`;

export const METRIC_DUPLICATED_USED_KEYS_EVENT_COUNTER = `${METRICS_PREFIX}used_keys_event_total`;

export const METRIC_INVALID_KEYS_EVENT_COUNTER = `${METRICS_PREFIX}invalid_keys_event_total`;
6 changes: 6 additions & 0 deletions src/common/prometheus/prometheus.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@ import {
PrometheusDepositedKeysProvider,
PrometheusOperatorsKeysProvider,
PrometheusKeysApiRequestsProvider,
PrometheusVettedUnusedKeysEventProvider,
PrometheusUsedKeysEventProvider,
PrometheusInvalidKeysEventProvider,
} from './prometheus.provider';
import { METRICS_PREFIX, METRICS_URL } from './prometheus.constants';

Expand All @@ -38,6 +41,9 @@ const providers = [
PrometheusDepositedKeysProvider,
PrometheusOperatorsKeysProvider,
PrometheusKeysApiRequestsProvider,
PrometheusVettedUnusedKeysEventProvider,
PrometheusUsedKeysEventProvider,
PrometheusInvalidKeysEventProvider,
];

PrometheusModule.global = true;
Expand Down
21 changes: 21 additions & 0 deletions src/common/prometheus/prometheus.provider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,9 @@ import {
METRIC_DEPOSITED_KEYS_TOTAL,
METRIC_OPERATORS_KEYS_TOTAL,
METRIC_KEYS_API_REQUEST_DURATION,
METRIC_DUPLICATED_VETTED_UNUSED_KEYS_EVENT_COUNTER,
METRIC_DUPLICATED_USED_KEYS_EVENT_COUNTER,
METRIC_INVALID_KEYS_EVENT_COUNTER,
} from './prometheus.constants';

export const PrometheusTransportMessageCounterProvider = makeCounterProvider({
Expand Down Expand Up @@ -93,3 +96,21 @@ export const PrometheusKeysApiRequestsProvider = makeHistogramProvider({
buckets: [0.1, 0.2, 0.3, 0.6, 1, 1.5, 2, 5],
labelNames: ['result', 'status'] as const,
});

export const PrometheusVettedUnusedKeysEventProvider = makeCounterProvider({
name: METRIC_DUPLICATED_VETTED_UNUSED_KEYS_EVENT_COUNTER,
help: 'Number of duplicated vetted unused keys events',
labelNames: ['stakingModuleId'] as const,
});

export const PrometheusUsedKeysEventProvider = makeCounterProvider({
name: METRIC_DUPLICATED_USED_KEYS_EVENT_COUNTER,
help: 'Number of duplicated used keys events',
labelNames: ['stakingModuleId'] as const,
});

export const PrometheusInvalidKeysEventProvider = makeGaugeProvider({
name: METRIC_INVALID_KEYS_EVENT_COUNTER,
help: 'Number of invalid keys',
labelNames: ['stakingModuleId'] as const,
});
2 changes: 0 additions & 2 deletions src/contracts/deposit/deposit.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@ import {
VerifiedDepositEventsCacheHeaders,
VerifiedDepositEventGroup,
} from './interfaces';
import { OneAtTime } from 'common/decorators';
import { RepositoryService } from 'contracts/repository';
import { CacheService } from 'cache';
import { BlockTag } from 'provider';
Expand All @@ -36,7 +35,6 @@ export class DepositService {
private blsService: BlsService,
) {}

@OneAtTime()
public async handleNewBlock(blockNumber: number): Promise<void> {
if (blockNumber % DEPOSIT_EVENTS_CACHE_UPDATE_BLOCK_RATE !== 0) return;

Expand Down
3 changes: 2 additions & 1 deletion src/guardian/block-guard/block-guard.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,10 @@ import { Module } from '@nestjs/common';
import { DepositModule } from 'contracts/deposit';
import { SecurityModule } from 'contracts/security';
import { BlockGuardService } from './block-guard.service';
import { LidoModule } from 'contracts/lido';

@Module({
imports: [DepositModule, SecurityModule],
imports: [LidoModule, DepositModule, SecurityModule],
providers: [BlockGuardService],
exports: [BlockGuardService],
})
Expand Down
15 changes: 10 additions & 5 deletions src/guardian/block-guard/block-guard.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import {
METRIC_BLOCK_DATA_REQUEST_ERRORS,
} from 'common/prometheus';
import { Counter, Histogram } from 'prom-client';
import { LidoService } from 'contracts/lido';

@Injectable()
export class BlockGuardService {
Expand All @@ -29,6 +30,7 @@ export class BlockGuardService {

private depositService: DepositService,
private securityService: SecurityService,
private lidoService: LidoService,
) {}

public isNeedToProcessNewState(newMeta: {
Expand Down Expand Up @@ -67,11 +69,13 @@ export class BlockGuardService {
try {
const guardianAddress = this.securityService.getGuardianAddress();

const [depositRoot, depositedEvents, guardianIndex] = await Promise.all([
this.depositService.getDepositRoot({ blockHash }),
this.depositService.getAllDepositedEvents(blockNumber, blockHash),
this.securityService.getGuardianIndex({ blockHash }),
]);
const [depositRoot, depositedEvents, guardianIndex, lidoWC] =
await Promise.all([
this.depositService.getDepositRoot({ blockHash }),
this.depositService.getAllDepositedEvents(blockNumber, blockHash),
this.securityService.getGuardianIndex({ blockHash }),
this.lidoService.getWithdrawalCredentials({ blockHash }),
]);

return {
blockNumber,
Expand All @@ -80,6 +84,7 @@ export class BlockGuardService {
depositedEvents,
guardianAddress,
guardianIndex,
lidoWC,
};
} catch (error) {
this.blockErrorsCounter.inc();
Expand Down
3 changes: 0 additions & 3 deletions src/guardian/guardian-message/guardian-message.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -84,12 +84,9 @@ export class GuardianMessageService {
this.logger.warn(
'Your address is not in the Guardian List. The message will not be sent',
);

return;
}

const messageWithMeta = this.addMessageMetaData(messageData);

this.logger.log('Sending a message to broker', messageData);
await this.messagesService.sendMessage(messageWithMeta);
}
Expand Down
35 changes: 34 additions & 1 deletion src/guardian/guardian-metrics/guardian-metrics.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,11 @@ import {
METRIC_DEPOSITED_KEYS_TOTAL,
METRIC_OPERATORS_KEYS_TOTAL,
METRIC_INTERSECTIONS_TOTAL,
METRIC_DUPLICATED_USED_KEYS_EVENT_COUNTER,
METRIC_INVALID_KEYS_EVENT_COUNTER,
METRIC_DUPLICATED_VETTED_UNUSED_KEYS_EVENT_COUNTER,
} from 'common/prometheus';
import { Gauge } from 'prom-client';
import { Counter, Gauge } from 'prom-client';

@Injectable()
export class GuardianMetricsService {
Expand All @@ -24,6 +27,15 @@ export class GuardianMetricsService {

@InjectMetric(METRIC_INTERSECTIONS_TOTAL)
private intersectionsCounter: Gauge<string>,

@InjectMetric(METRIC_DUPLICATED_USED_KEYS_EVENT_COUNTER)
private duplicatedUsedKeysEventCounter: Counter<string>,

@InjectMetric(METRIC_DUPLICATED_VETTED_UNUSED_KEYS_EVENT_COUNTER)
private duplicatedVettedUnusedKeysEventCounter: Counter<string>,

@InjectMetric(METRIC_INVALID_KEYS_EVENT_COUNTER)
private invalidKeysEventCounter: Counter<string>,
) {}

/**
Expand Down Expand Up @@ -117,4 +129,25 @@ export class GuardianMetricsService {
filtered.length,
);
}

/**
* increment duplicated vetted unused keys event counter
*/
public incrDuplicatedVettedUnusedKeysEventCounter() {
this.duplicatedVettedUnusedKeysEventCounter.inc();
}

/**
* increment duplicated used keys event counter
*/
public incrDuplicatedUsedKeysEventCounter() {
this.duplicatedUsedKeysEventCounter.inc();
}

/**
* increment invalid keys event counter
*/
public incrInvalidKeysEventCounter() {
this.invalidKeysEventCounter.inc();
}
}
109 changes: 86 additions & 23 deletions src/guardian/guardian.service.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,29 +24,94 @@ import { mockRepository } from 'contracts/repository/repository.mock';

jest.mock('../transport/stomp/stomp.client');

const TEST_MODULE_ID = 1;
const vettedKeys = [
{
key: '0x9948d2becf42e9f76922bc6f664545e6f50401050af95785a984802d32a95c4c61f8e3de312b78167f86e047f83a7796',
depositSignature:
'0x8bf4401a354de243a3716ee2efc0bde1ded56a40e2943ac7c50290bec37e935d6170b21e7c0872f203199386143ef12612a1488a8e9f1cdf1229c382f29c326bcbf6ed6a87d8fbfe0df87dacec6632fc4709d9d338f4cf81e861d942c23bba1e',
operatorIndex: 0,
used: false,
moduleAddress: '0x595F64Ddc3856a3b5Ff4f4CC1d1fb4B46cFd2bAC',
index: 100,
},
{
key: '0x911dd3091cfb1b42c960e4f343ea98d9ee6a1dc8ef215afa976fb557bd627a901717c0008bc33a0bfea15f0dfe9c5d01',
depositSignature:
'0x898ac7072aa26d983f9ece384c4037966dde614b75dddf982f6a415f3107cb2569b96f6d1c44e608a250ac4bbe908df51473f0de2cf732d283b07d88f3786893124967b8697a8b93d31976e7ac49ab1e568f98db0bbb13384477e8357b6d7e9b',
operatorIndex: 0,
used: false,
moduleAddress: '0x595F64Ddc3856a3b5Ff4f4CC1d1fb4B46cFd2bAC',
index: 101,
},
{
key: '0x84e85db03bee714dbecf01914460d9576b7f7226030bdbeae9ee923bf5f8e01eec4f7dfe54aa7eca6f4bccce59a0bf42',
depositSignature:
'0xb024b67a2f6c579213529e143bd4ebb81c5a2dc385cb526de4a816c8fe0317ebfb38369b08622e9f27e62cce2811679a13a459d4e9a8d7bd00080c36b359c1ca03bdcf4a0fcbbc2e18fe9923d8c4edb503ade58bdefe690760611e3738d5e64f',
operatorIndex: 28,
used: false,
moduleAddress: '0x11a93807078f8BB880c1BD0ee4C387537de4b4b6',
index: 5,
},
];

const stakingModuleResponse = {
data: [
const vettedKeysResponse = {
blockHash: 'some_hash',
blockNumber: 1,
vettedKeys,
stakingModulesData: [
{
blockHash: 'some_hash',
lastChangedBlockHash: 'some_hash',
unusedKeys: [
'0x9948d2becf42e9f76922bc6f664545e6f50401050af95785a984802d32a95c4c61f8e3de312b78167f86e047f83a7796',
'0x911dd3091cfb1b42c960e4f343ea98d9ee6a1dc8ef215afa976fb557bd627a901717c0008bc33a0bfea15f0dfe9c5d01',
],
vettedUnusedKeys: [
{
key: '0x9948d2becf42e9f76922bc6f664545e6f50401050af95785a984802d32a95c4c61f8e3de312b78167f86e047f83a7796',
depositSignature:
'0x8bf4401a354de243a3716ee2efc0bde1ded56a40e2943ac7c50290bec37e935d6170b21e7c0872f203199386143ef12612a1488a8e9f1cdf1229c382f29c326bcbf6ed6a87d8fbfe0df87dacec6632fc4709d9d338f4cf81e861d942c23bba1e',
operatorIndex: 0,
used: false,
moduleAddress: '0x595F64Ddc3856a3b5Ff4f4CC1d1fb4B46cFd2bAC',
index: 100,
},
{
key: '0x911dd3091cfb1b42c960e4f343ea98d9ee6a1dc8ef215afa976fb557bd627a901717c0008bc33a0bfea15f0dfe9c5d01',
depositSignature:
'0x898ac7072aa26d983f9ece384c4037966dde614b75dddf982f6a415f3107cb2569b96f6d1c44e608a250ac4bbe908df51473f0de2cf732d283b07d88f3786893124967b8697a8b93d31976e7ac49ab1e568f98db0bbb13384477e8357b6d7e9b',
operatorIndex: 0,
used: false,
moduleAddress: '0x595F64Ddc3856a3b5Ff4f4CC1d1fb4B46cFd2bAC',
index: 101,
},
],
nonce: 0,
stakingModuleId: 2,
stakingModuleAddress: '0x595F64Ddc3856a3b5Ff4f4CC1d1fb4B46cFd2bAC',
},
{
blockHash: 'some_hash',
lastChangedBlockHash: 'some_hash',
unusedKeys: [
'0x84e85db03bee714dbecf01914460d9576b7f7226030bdbeae9ee923bf5f8e01eec4f7dfe54aa7eca6f4bccce59a0bf42',
],
vettedUnusedKeys: [
{
key: '0x84e85db03bee714dbecf01914460d9576b7f7226030bdbeae9ee923bf5f8e01eec4f7dfe54aa7eca6f4bccce59a0bf42',
depositSignature:
'0xb024b67a2f6c579213529e143bd4ebb81c5a2dc385cb526de4a816c8fe0317ebfb38369b08622e9f27e62cce2811679a13a459d4e9a8d7bd00080c36b359c1ca03bdcf4a0fcbbc2e18fe9923d8c4edb503ade58bdefe690760611e3738d5e64f',
operatorIndex: 28,
used: false,
moduleAddress: '0x11a93807078f8BB880c1BD0ee4C387537de4b4b6',
index: 5,
},
],
nonce: 0,
type: 'string',
id: TEST_MODULE_ID,
stakingModuleAddress: 'string',
moduleFee: 0,
treasuryFee: 0,
targetShare: 0,
status: 0,
name: 'string',
lastDepositAt: 0,
lastDepositBlock: 0,
stakingModuleId: 3,
stakingModuleAddress: '0x11a93807078f8BB880c1BD0ee4C387537de4b4b6',
},
],
elBlockSnapshot: {
blockNumber: 0,
blockHash: 'string',
timestamp: 0,
},
};

describe('GuardianService', () => {
Expand All @@ -66,7 +131,6 @@ describe('GuardianService', () => {
MockProviderModule.forRoot(),
LoggerModule,
PrometheusModule,

GuardianModule,
RepositoryModule,
DepositModule,
Expand Down Expand Up @@ -101,9 +165,9 @@ describe('GuardianService', () => {
});

it('should exit if the previous call is not completed', async () => {
const getStakingModulesMock = jest
.spyOn(stakingRouterService, 'getStakingModules')
.mockImplementation(async () => stakingModuleResponse);
jest
.spyOn(stakingRouterService, 'getStakingModulesData')
.mockImplementation(async () => vettedKeysResponse);

const getBlockGuardServiceMock = jest
.spyOn(blockGuardService, 'isNeedToProcessNewState')
Expand All @@ -114,7 +178,6 @@ describe('GuardianService', () => {
guardianService.handleNewBlock(),
]);

expect(getStakingModulesMock).toBeCalledTimes(1);
expect(getBlockGuardServiceMock).toBeCalledTimes(1);
});
});
Loading

0 comments on commit d8e7dfb

Please sign in to comment.