From 62f674ef85f6249e18cb3e5cc870af20341a0079 Mon Sep 17 00:00:00 2001 From: Anna Mukharram Date: Wed, 10 Jan 2024 16:18:01 +0400 Subject: [PATCH] feat: test that duplicates in one module will not stop deposits for other --- .../staking-module-guard.service.ts | 17 ++- test/manifest.e2e-spec.ts | 137 ++++++++++++++++-- 2 files changed, 134 insertions(+), 20 deletions(-) diff --git a/src/guardian/staking-module-guard/staking-module-guard.service.ts b/src/guardian/staking-module-guard/staking-module-guard.service.ts index 50096241..a301b98c 100644 --- a/src/guardian/staking-module-guard/staking-module-guard.service.ts +++ b/src/guardian/staking-module-guard/staking-module-guard.service.ts @@ -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; } @@ -252,6 +255,7 @@ export class StakingModuleGuardService { return []; } + // TODO: add staking module id this.logger.log( 'Found intersections with lido credentials, need to check used duplicated keys', ); @@ -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; } @@ -379,6 +383,7 @@ export class StakingModuleGuardService { }; this.logger.log('No problems found', { + stakingModuleId, blockHash, lastState: lastContractsState, newState: currentContractState, @@ -404,7 +409,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] = { @@ -431,7 +436,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 @@ -459,17 +464,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, }); diff --git a/test/manifest.e2e-spec.ts b/test/manifest.e2e-spec.ts index 611d5ccd..e28af0e1 100644 --- a/test/manifest.e2e-spec.ts +++ b/test/manifest.e2e-spec.ts @@ -71,7 +71,7 @@ import { RepositoryModule } from '../src/contracts/repository'; import { DepositService } from '../src/contracts/deposit'; import { DepositModule } from '../src/contracts/deposit'; -import { SecurityModule } from '../src/contracts/security'; +import { SecurityModule, SecurityService } from '../src/contracts/security'; import { LidoService } from '../src/contracts/lido'; import { LidoModule } from '../src/contracts/lido'; @@ -109,6 +109,8 @@ describe('ganache e2e tests', () => { let keyValidator: KeyValidatorInterface; let validateKeys: jest.SpyInstance; + let securityService: SecurityService; + beforeEach(async () => { server = makeServer(FORK_BLOCK, CHAIN_ID, UNLOCKED_ACCOUNTS); await server.listen(GANACHE_PORT); @@ -158,6 +160,7 @@ describe('ganache e2e tests', () => { depositService = moduleRef.get(DepositService); guardianMessageService = moduleRef.get(GuardianMessageService); keyValidator = moduleRef.get(KeyValidatorInterface); + securityService = moduleRef.get(SecurityService); // Initializing needed service instead of the whole app blsService = moduleRef.get(BlsService); @@ -872,12 +875,15 @@ describe('ganache e2e tests', () => { // mocked curated module const stakingModule = mockedModule(currentBlock, currentBlock.hash); + const stakingDvtModule = mockedModuleDvt(currentBlock, currentBlock.hash); const meta = mockedMeta(currentBlock, currentBlock.hash); - mockedKeysApiOperators( + mockedKeysApiOperatorsMany( keysApiService, - mockedOperators, - stakingModule, + [ + { operators: mockedOperators, module: stakingModule }, + { operators: mockedDvtOperators, module: stakingDvtModule }, + ], meta, ); @@ -901,6 +907,15 @@ describe('ganache e2e tests', () => { index: 1, moduleAddress: NOP_REGISTRY, }, + { + key: '0xb3c90525010a5710d43acbea46047fc37ed55306d032527fa15dd7e8cd8a9a5fa490347cc5fce59936fb8300683cd9f3', + depositSignature: + '0x8a77d9411781360cc107344a99f6660b206d2c708ae7fa35565b76ec661a0b86b6c78f5b5691d2cf469c27d0655dfc6311451a9e0501f3c19c6f7e35a770d1a908bfec7cba2e07339dc633b8b6626216ce76ec0fa48ee56aaaf2f9dc7ccb2fe2', + operatorIndex: 0, + used: false, + moduleAddress: FAKE_SIMPLE_DVT, + index: 0, + }, ]; mockedKeysApiUnusedKeys(keysApiService, unusedKeys, meta); @@ -915,10 +930,34 @@ describe('ganache e2e tests', () => { ); expect(isOnPause).toBe(false); + const originalIsDepositsPaused = securityService.isDepositsPaused; + + // as we have faked simple dvt + jest + .spyOn(securityService, 'isDepositsPaused') + .mockImplementation((stakingModuleId, blockTag) => { + if (stakingModuleId === stakingDvtModule.id) { + return Promise.resolve(false); + } + return originalIsDepositsPaused.call( + securityService, + stakingModuleId, + blockTag, + ); + }); + await guardianService.handleNewBlock(); // just skip on this iteration deposit for staking module - expect(sendDepositMessage).toBeCalledTimes(0); + expect(sendDepositMessage).toBeCalledTimes(1); + expect(sendDepositMessage).toHaveBeenCalledWith( + expect.objectContaining({ + blockNumber: currentBlock.number, + guardianAddress: wallet.address, + guardianIndex: 9, + stakingModuleId: 2, + }), + ); expect(sendPauseMessage).toBeCalledTimes(0); // after deleting duplicates in staking module, @@ -933,16 +972,28 @@ describe('ganache e2e tests', () => { index: 0, moduleAddress: NOP_REGISTRY, }, + { + key: '0xb3c90525010a5710d43acbea46047fc37ed55306d032527fa15dd7e8cd8a9a5fa490347cc5fce59936fb8300683cd9f3', + depositSignature: + '0x8a77d9411781360cc107344a99f6660b206d2c708ae7fa35565b76ec661a0b86b6c78f5b5691d2cf469c27d0655dfc6311451a9e0501f3c19c6f7e35a770d1a908bfec7cba2e07339dc633b8b6626216ce76ec0fa48ee56aaaf2f9dc7ccb2fe2', + operatorIndex: 0, + used: false, + moduleAddress: FAKE_SIMPLE_DVT, + index: 0, + }, ]; const newBlock = await tempProvider.getBlock('latest'); const newMeta = mockedMeta(newBlock, newBlock.hash); - const newStakingModule = mockedModule(currentBlock, newBlock.hash); + const newStakingModule = mockedModule(newBlock, newBlock.hash); + const newStakingDvtModule = mockedModuleDvt(newBlock, newBlock.hash); - mockedKeysApiOperators( + mockedKeysApiOperatorsMany( keysApiService, - mockedOperators, - newStakingModule, + [ + { operators: mockedOperators, module: newStakingModule }, + { operators: mockedDvtOperators, module: newStakingDvtModule }, + ], newMeta, ); @@ -952,9 +1003,11 @@ describe('ganache e2e tests', () => { newMeta, ); + sendDepositMessage.mockReset(); + await guardianService.handleNewBlock(); - expect(sendDepositMessage).toBeCalledTimes(1); + expect(sendDepositMessage).toBeCalledTimes(2); expect(sendDepositMessage).toHaveBeenLastCalledWith( expect.objectContaining({ @@ -964,6 +1017,17 @@ describe('ganache e2e tests', () => { stakingModuleId: 1, }), ); + + expect(sendDepositMessage).toHaveBeenCalledWith( + expect.objectContaining({ + blockNumber: newBlock.number, + guardianAddress: wallet.address, + guardianIndex: 9, + stakingModuleId: 2, + }), + ); + + jest.spyOn(securityService, 'isDepositsPaused').mockRestore(); }, TESTS_TIMEOUT, ); @@ -1083,16 +1147,28 @@ describe('ganache e2e tests', () => { index: 0, moduleAddress: NOP_REGISTRY, }, + { + key: '0xb3c90525010a5710d43acbea46047fc37ed55306d032527fa15dd7e8cd8a9a5fa490347cc5fce59936fb8300683cd9f3', + depositSignature: + '0x8a77d9411781360cc107344a99f6660b206d2c708ae7fa35565b76ec661a0b86b6c78f5b5691d2cf469c27d0655dfc6311451a9e0501f3c19c6f7e35a770d1a908bfec7cba2e07339dc633b8b6626216ce76ec0fa48ee56aaaf2f9dc7ccb2fe2', + operatorIndex: 0, + used: false, + moduleAddress: FAKE_SIMPLE_DVT, + index: 0, + }, ]; const newBlock = await tempProvider.getBlock('latest'); const newMeta = mockedMeta(newBlock, newBlock.hash); - const newStakingModule = mockedModule(currentBlock, newBlock.hash); + const newStakingModule = mockedModule(newBlock, newBlock.hash); + const newStakingDvtModule = mockedModuleDvt(newBlock, newBlock.hash); - mockedKeysApiOperators( + mockedKeysApiOperatorsMany( keysApiService, - mockedOperators, - newStakingModule, + [ + { operators: mockedOperators, module: newStakingModule }, + { operators: mockedDvtOperators, module: newStakingDvtModule }, + ], newMeta, ); @@ -1102,11 +1178,29 @@ describe('ganache e2e tests', () => { newMeta, ); + const originalIsDepositsPaused = securityService.isDepositsPaused; + + // as we have faked simple dvt + jest + .spyOn(securityService, 'isDepositsPaused') + .mockImplementation((stakingModuleId, blockTag) => { + if (stakingModuleId === newStakingDvtModule.id) { + return Promise.resolve(false); + } + return originalIsDepositsPaused.call( + securityService, + stakingModuleId, + blockTag, + ); + }); + + sendDepositMessage.mockReset(); + await guardianService.handleNewBlock(); - expect(sendDepositMessage).toBeCalledTimes(1); + expect(sendDepositMessage).toBeCalledTimes(2); - expect(sendDepositMessage).toHaveBeenLastCalledWith( + expect(sendDepositMessage).toHaveBeenCalledWith( expect.objectContaining({ blockNumber: newBlock.number, guardianAddress: wallet.address, @@ -1114,6 +1208,17 @@ describe('ganache e2e tests', () => { stakingModuleId: 1, }), ); + + expect(sendDepositMessage).toHaveBeenCalledWith( + expect.objectContaining({ + blockNumber: newBlock.number, + guardianAddress: wallet.address, + guardianIndex: 9, + stakingModuleId: 2, + }), + ); + + jest.spyOn(securityService, 'isDepositsPaused').mockRestore(); }, TESTS_TIMEOUT, );