From 1da8b708eee556df44f9a7fceaef1b6d3c43698a Mon Sep 17 00:00:00 2001 From: Anna Mukharram Date: Wed, 13 Dec 2023 01:51:55 +0400 Subject: [PATCH 1/2] feat: e2e test for new checks, fixed existing tests --- README.md | 2 + src/contracts/deposit/deposit.service.ts | 3 + src/guardian/guardian.service.ts | 2 +- .../staking-module-guard.service.ts | 9 +- src/keys-api/keys-api.service.ts | 2 +- test/helpers/mockKeysApi.ts | 111 ++-- test/manifest.e2e-spec.ts | 534 +++++++++++++++++- 7 files changed, 587 insertions(+), 76 deletions(-) diff --git a/README.md b/README.md index d3830471..83f4dc10 100644 --- a/README.md +++ b/README.md @@ -275,6 +275,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: diff --git a/src/contracts/deposit/deposit.service.ts b/src/contracts/deposit/deposit.service.ts index b4103089..686d7215 100644 --- a/src/contracts/deposit/deposit.service.ts +++ b/src/contracts/deposit/deposit.service.ts @@ -243,6 +243,7 @@ export class DepositService { * The last N blocks are not stored, in order to avoid storing reorganized blocks */ public async updateEventsCache(): Promise { + this.logger.log('try to update?'); const fetchTimeStart = performance.now(); const [currentBlock, initialCache] = await Promise.all([ @@ -270,6 +271,8 @@ export class DepositService { chunkToBlock, ); + console.log('chunk?', chunkEventGroup); + updatedCachedEvents.headers.endBlock = chunkEventGroup.endBlock; updatedCachedEvents.data = updatedCachedEvents.data.concat( chunkEventGroup.events, diff --git a/src/guardian/guardian.service.ts b/src/guardian/guardian.service.ts index 1f69979a..6cf67dfc 100644 --- a/src/guardian/guardian.service.ts +++ b/src/guardian/guardian.service.ts @@ -126,6 +126,7 @@ export class GuardianService implements OnModuleInit { await this.depositService.handleNewBlock(blockNumber); + // TODO: e2e test 'node operator deposit frontrun' shows that it is possible to find event and not save in cache const blockData = await this.blockGuardService.getCurrentBlockData({ blockHash, blockNumber, @@ -137,7 +138,6 @@ export class GuardianService implements OnModuleInit { blockHash: blockData.blockHash, }); - // TODO: check only if one of nonce changed const modulesIdWithDuplicateKeys: number[] = this.stakingModuleGuardService.checkVettedKeysDuplicates( stakingModulesData, 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 3f70ada7..82aacba7 100644 --- a/src/guardian/staking-module-guard/staking-module-guard.service.ts +++ b/src/guardian/staking-module-guard/staking-module-guard.service.ts @@ -237,7 +237,14 @@ export class StakingModuleGuardService { intersectionsWithLidoWC: VerifiedDepositEvent[], blockData: BlockData, ) { - const depositedPubkeys = intersectionsWithLidoWC.map( + // should not check invalid + // TODO: fix in prev PR + const validIntersections = intersectionsWithLidoWC.filter( + ({ valid }) => valid, + ); + if (!validIntersections.length) return []; + + const depositedPubkeys = validIntersections.map( (deposit) => deposit.pubkey, ); diff --git a/src/keys-api/keys-api.service.ts b/src/keys-api/keys-api.service.ts index 6cf28664..3cbb990d 100644 --- a/src/keys-api/keys-api.service.ts +++ b/src/keys-api/keys-api.service.ts @@ -63,7 +63,7 @@ export class KeysApiService { /** * - * @param The /v1/keys/find KAPI endpoint returns a key along with its duplicates + * @param The /v1/keys/find API endpoint returns keys along with their duplicates * @returns */ public async getKeysWithDuplicates(pubkeys: string[]) { diff --git a/test/helpers/mockKeysApi.ts b/test/helpers/mockKeysApi.ts index 7eba73b9..056162e0 100644 --- a/test/helpers/mockKeysApi.ts +++ b/test/helpers/mockKeysApi.ts @@ -1,66 +1,52 @@ import ethers from 'ethers'; -import { toHexString } from '@chainsafe/ssz'; import { KeysApiService } from '../../src/keys-api/keys-api.service'; -import { NOP_REGISTRY, pk } from './../constants'; +import { NOP_REGISTRY } from './../constants'; import { RegistryOperator } from 'keys-api/interfaces/RegistryOperator'; +import { SRModule } from 'keys-api/interfaces'; +import { ELBlockSnapshot } from 'keys-api/interfaces/ELBlockSnapshot'; +import { RegistryKey } from 'keys-api/interfaces/RegistryKey'; -export const mockKeysApi = ( - sig: Uint8Array[], - block: ethers.providers.Block, - keysApiService: KeysApiService, - used = false, -) => { - const mockedModule = { - nonce: 6046, - type: 'grouped-onchain-v1', - id: 1, - stakingModuleAddress: NOP_REGISTRY, - moduleFee: 10, - treasuryFee: 10, - targetShare: 10, - status: 1, - name: 'NodeOperatorRegistry', - lastDepositAt: block.timestamp, - lastDepositBlock: block.number, - }; +export const mockedModule = (block: ethers.providers.Block) => ({ + nonce: 6046, + type: 'grouped-onchain-v1', + id: 1, + stakingModuleAddress: NOP_REGISTRY, + moduleFee: 10, + treasuryFee: 10, + targetShare: 10, + status: 1, + name: 'NodeOperatorRegistry', + lastDepositAt: block.timestamp, + lastDepositBlock: block.number, +}); - const mockedMeta = { - blockNumber: block.number, - blockHash: block.hash, - timestamp: block.timestamp, - }; +export const mockedMeta = (block: ethers.providers.Block) => ({ + blockNumber: block.number, + blockHash: block.hash, + timestamp: block.timestamp, +}); - const mockedKeys = sig.map((x) => ({ - key: toHexString(pk), - depositSignature: toHexString(x), - operatorIndex: 0, - used, +export const mockedOperators: RegistryOperator[] = [ + { + name: 'Dev team', + rewardAddress: '0x6D725DAe055287f913661ee0b79dE6B21F12A459', + stakingLimit: 12, + stoppedValidators: 0, + totalSigningKeys: 12, + usedSigningKeys: 10, index: 0, + active: true, moduleAddress: NOP_REGISTRY, - })); - - const mockedOperators: RegistryOperator[] = [ - { - name: 'Dev team', - rewardAddress: '0x6D725DAe055287f913661ee0b79dE6B21F12A459', - stakingLimit: 11, - stoppedValidators: 0, - totalSigningKeys: 11, - usedSigningKeys: 10, - index: 0, - active: true, - moduleAddress: NOP_REGISTRY, - }, - ]; - - jest.spyOn(keysApiService, 'getUnusedKeys').mockImplementation(async () => ({ - data: mockedKeys, - meta: { - elBlockSnapshot: mockedMeta, - }, - })); + }, +]; +export const mockedKeysApiOperators = ( + keysApiService: KeysApiService, + mockedOperators: RegistryOperator[], + mockedModule: SRModule, + mockedMeta: ELBlockSnapshot, +) => { jest .spyOn(keysApiService, 'getOperatorListWithModule') .mockImplementation(async () => ({ @@ -69,7 +55,26 @@ export const mockKeysApi = ( elBlockSnapshot: mockedMeta, }, })); +}; +export const mockedKeysApiUnusedKeys = ( + keysApiService: KeysApiService, + mockedKeys: RegistryKey[], + mockedMeta: ELBlockSnapshot, +) => { + jest.spyOn(keysApiService, 'getUnusedKeys').mockImplementation(async () => ({ + data: mockedKeys, + meta: { + elBlockSnapshot: mockedMeta, + }, + })); +}; + +export const mockedKeysWithDuplicates = ( + keysApiService: KeysApiService, + mockedKeys: RegistryKey[], + mockedMeta: ELBlockSnapshot, +) => { jest .spyOn(keysApiService, 'getKeysWithDuplicates') .mockImplementation(async () => ({ diff --git a/test/manifest.e2e-spec.ts b/test/manifest.e2e-spec.ts index b60b2c9d..712185f7 100644 --- a/test/manifest.e2e-spec.ts +++ b/test/manifest.e2e-spec.ts @@ -5,7 +5,15 @@ import { ethers } from 'ethers'; import { fromHexString, toHexString } from '@chainsafe/ssz'; // Helpers -import { computeRoot, mockKeysApi } from './helpers'; +import { + computeRoot, + mockedKeysApiOperators, + mockedKeysApiUnusedKeys, + mockedKeysWithDuplicates, + mockedMeta, + mockedModule, + mockedOperators, +} from './helpers'; // Constants import { WeiPerEther } from '@ethersproject/constants'; @@ -25,6 +33,7 @@ import { NO_PRIVKEY_MESSAGE, sk, pk, + NOP_REGISTRY, } from './constants'; // Ganache @@ -139,7 +148,7 @@ describe('ganache e2e tests', () => { depositService = moduleRef.get(DepositService); guardianMessageService = moduleRef.get(GuardianMessageService); - // Initialising needed service instead of the whole app + // Initializing needed service instead of the whole app blsService = moduleRef.get(BlsService); await blsService.onModuleInit(); @@ -202,6 +211,7 @@ describe('ganache e2e tests', () => { const forkBlock = await tempProvider.getBlock(FORK_BLOCK); const currentBlock = await tempProvider.getBlock('latest'); + // create correct sign for deposit message for pk const goodDepositMessage = { pubkey: pk, withdrawalCredentials: fromHexString(GOOD_WC), @@ -210,7 +220,29 @@ describe('ganache e2e tests', () => { const goodSigningRoot = computeRoot(goodDepositMessage); const goodSig = sk.sign(goodSigningRoot).toBytes(); - mockKeysApi([goodSig], currentBlock, keysApiService); + const unusedKeys = [ + { + key: toHexString(pk), + depositSignature: toHexString(goodSig), + operatorIndex: 0, + used: false, + index: 0, + moduleAddress: NOP_REGISTRY, + }, + ]; + + const meta = mockedMeta(currentBlock); + const stakingModule = mockedModule(currentBlock); + + mockedKeysApiOperators( + keysApiService, + mockedOperators, + stakingModule, + meta, + ); + + mockedKeysApiUnusedKeys(keysApiService, unusedKeys, meta); + mockedKeysWithDuplicates(keysApiService, unusedKeys, meta); await depositService.setCachedEvents({ data: [ @@ -268,10 +300,21 @@ describe('ganache e2e tests', () => { // Mock Keys API again on new block const newBlock = await providerService.provider.getBlock('latest'); - mockKeysApi([goodSig], newBlock, keysApiService); + + const newMeta = mockedMeta(newBlock); + + mockedKeysApiOperators( + keysApiService, + mockedOperators, + stakingModule, + newMeta, + ); + + mockedKeysApiUnusedKeys(keysApiService, unusedKeys, newMeta); // Run a cycle and wait for possible changes await guardianService.handleNewBlock(); + expect(sendPauseMessage).toHaveBeenCalledWith( expect.objectContaining({ blockNumber: newBlock.number, @@ -303,6 +346,7 @@ describe('ganache e2e tests', () => { ); const currentBlock = await tempProvider.getBlock('latest'); + // mock kapi response const goodDepositMessage = { pubkey: pk, withdrawalCredentials: fromHexString(GOOD_WC), @@ -311,7 +355,28 @@ describe('ganache e2e tests', () => { const goodSigningRoot = computeRoot(goodDepositMessage); const goodSig = sk.sign(goodSigningRoot).toBytes(); - mockKeysApi([goodSig], currentBlock, keysApiService); + const unusedKeys = [ + { + key: toHexString(pk), + depositSignature: toHexString(goodSig), + operatorIndex: 0, + used: false, + index: 0, + moduleAddress: NOP_REGISTRY, + }, + ]; + + const meta = mockedMeta(currentBlock); + const stakingModule = mockedModule(currentBlock); + + mockedKeysApiOperators( + keysApiService, + mockedOperators, + stakingModule, + meta, + ); + + mockedKeysApiUnusedKeys(keysApiService, unusedKeys, meta); await depositService.setCachedEvents({ data: [], @@ -358,7 +423,18 @@ describe('ganache e2e tests', () => { // Mock Keys API again on new block const newBlock = await providerService.provider.getBlock('latest'); - mockKeysApi([goodSig], newBlock, keysApiService); + const newMeta = mockedMeta(newBlock); + + mockedKeysApiOperators( + keysApiService, + mockedOperators, + stakingModule, + newMeta, + ); + mockedKeysApiUnusedKeys(keysApiService, unusedKeys, newMeta); + // we make check that there are no duplicated used keys + // this request return keys along with their duplicates + mockedKeysWithDuplicates(keysApiService, unusedKeys, newMeta); // Run a cycle and wait for possible changes await guardianService.handleNewBlock(); @@ -384,7 +460,6 @@ describe('ganache e2e tests', () => { `http://127.0.0.1:${GANACHE_PORT}`, ); const currentBlock = await tempProvider.getBlock('latest'); - const goodDepositMessage = { pubkey: pk, withdrawalCredentials: fromHexString(GOOD_WC), @@ -393,7 +468,28 @@ describe('ganache e2e tests', () => { const goodSigningRoot = computeRoot(goodDepositMessage); const goodSig = sk.sign(goodSigningRoot).toBytes(); - mockKeysApi([goodSig], currentBlock, keysApiService); + const unusedKeys = [ + { + key: toHexString(pk), + depositSignature: toHexString(goodSig), + operatorIndex: 0, + used: false, + index: 0, + moduleAddress: NOP_REGISTRY, + }, + ]; + + const meta = mockedMeta(currentBlock); + const stakingModule = mockedModule(currentBlock); + + mockedKeysApiOperators( + keysApiService, + mockedOperators, + stakingModule, + meta, + ); + + mockedKeysApiUnusedKeys(keysApiService, unusedKeys, meta); await depositService.setCachedEvents({ data: [], @@ -421,16 +517,13 @@ describe('ganache e2e tests', () => { }; const weirdSigningRoot = computeRoot(weirdDepositMessage); const weirdSig = sk.sign(weirdSigningRoot).toBytes(); - const badDepositData = { ...badDepositMessage, signature: weirdSig, }; const badDepositDataRoot = DepositData.hashTreeRoot(badDepositData); - if (!process.env.WALLET_PRIVATE_KEY) throw new Error(NO_PRIVKEY_MESSAGE); const wallet = new ethers.Wallet(process.env.WALLET_PRIVATE_KEY); - // Make a bad deposit const signer = wallet.connect(providerService.provider); const depositContract = DepositAbi__factory.connect( @@ -444,15 +537,21 @@ describe('ganache e2e tests', () => { badDepositDataRoot, { value: ethers.constants.WeiPerEther.mul(1) }, ); - // Mock Keys API again on new block const newBlock = await providerService.provider.getBlock('latest'); - mockKeysApi([goodSig], newBlock, keysApiService); + const newMeta = mockedMeta(newBlock); + + mockedKeysApiOperators( + keysApiService, + mockedOperators, + stakingModule, + newMeta, + ); + mockedKeysApiUnusedKeys(keysApiService, unusedKeys, newMeta); // Run a cycle and wait for possible changes await guardianService.handleNewBlock(); await new Promise((res) => setTimeout(res, SLEEP_FOR_RESULT)); - // Check if on pause now const routerContract = StakingRouterAbi__factory.connect( STAKING_ROUTER, @@ -474,6 +573,7 @@ describe('ganache e2e tests', () => { ); const currentBlock = await tempProvider.getBlock('latest'); + // no diff const goodDepositMessage = { pubkey: pk, withdrawalCredentials: fromHexString(GOOD_WC), @@ -482,7 +582,28 @@ describe('ganache e2e tests', () => { const goodSigningRoot = computeRoot(goodDepositMessage); const goodSig = sk.sign(goodSigningRoot).toBytes(); - mockKeysApi([goodSig], currentBlock, keysApiService); + const unusedKeys = [ + { + key: toHexString(pk), + depositSignature: toHexString(goodSig), + operatorIndex: 0, + used: false, + index: 0, + moduleAddress: NOP_REGISTRY, + }, + ]; + + const meta = mockedMeta(currentBlock); + const stakingModule = mockedModule(currentBlock); + + mockedKeysApiOperators( + keysApiService, + mockedOperators, + stakingModule, + meta, + ); + + mockedKeysApiUnusedKeys(keysApiService, unusedKeys, meta); const goodDepositData = { ...goodDepositMessage, @@ -521,7 +642,18 @@ describe('ganache e2e tests', () => { // Mock Keys API again on new block const newBlock = await providerService.provider.getBlock('latest'); - mockKeysApi([goodSig], newBlock, keysApiService); + + const newMeta = mockedMeta(newBlock); + + mockedKeysApiOperators( + keysApiService, + mockedOperators, + stakingModule, + newMeta, + ); + + mockedKeysApiUnusedKeys(keysApiService, unusedKeys, newMeta); + mockedKeysWithDuplicates(keysApiService, unusedKeys, newMeta); // Run a cycle and wait for possible changes await guardianService.handleNewBlock(); @@ -553,6 +685,7 @@ describe('ganache e2e tests', () => { test( 'reorganization', async () => { + // TODOL need attention to this test const tempProvider = new ethers.providers.JsonRpcProvider( `http://127.0.0.1:${GANACHE_PORT}`, ); @@ -566,7 +699,28 @@ describe('ganache e2e tests', () => { const goodSigningRoot = computeRoot(goodDepositMessage); const goodSig = sk.sign(goodSigningRoot).toBytes(); - mockKeysApi([goodSig], currentBlock, keysApiService); + const unusedKeys = [ + { + key: toHexString(pk), + depositSignature: toHexString(goodSig), + operatorIndex: 0, + used: false, + index: 0, + moduleAddress: NOP_REGISTRY, + }, + ]; + + const meta = mockedMeta(currentBlock); + const stakingModule = mockedModule(currentBlock); + + mockedKeysApiOperators( + keysApiService, + mockedOperators, + stakingModule, + meta, + ); + + mockedKeysApiUnusedKeys(keysApiService, unusedKeys, meta); const goodDepositData = { ...goodDepositMessage, @@ -616,7 +770,17 @@ describe('ganache e2e tests', () => { // Mock Keys API again on new block, but now mark as used const newBlock = await providerService.provider.getBlock('latest'); - mockKeysApi([goodSig], newBlock, keysApiService, true); + + const newMeta = mockedMeta(newBlock); + + mockedKeysApiOperators( + keysApiService, + mockedOperators, + stakingModule, + newMeta, + ); + + mockedKeysApiUnusedKeys(keysApiService, [], newMeta); // Run a cycle and wait for possible changes await guardianService.handleNewBlock(); @@ -631,8 +795,8 @@ describe('ganache e2e tests', () => { server = makeServer(FORK_BLOCK, CHAIN_ID, UNLOCKED_ACCOUNTS); await server.listen(GANACHE_PORT); - // Changing keys api keys to used=false - mockKeysApi([goodSig], newBlock, keysApiService, false); + mockedKeysApiUnusedKeys(keysApiService, unusedKeys, newMeta); + mockedKeysWithDuplicates(keysApiService, unusedKeys, newMeta); // Check if on pause now const isOnPauseAfter = @@ -641,4 +805,334 @@ describe('ganache e2e tests', () => { }, TESTS_TIMEOUT, ); + + test( + 'skip deposit if find duplicated key', + async () => { + const tempProvider = new ethers.providers.JsonRpcProvider( + `http://127.0.0.1:${GANACHE_PORT}`, + ); + const currentBlock = await tempProvider.getBlock('latest'); + + // this key should be used in kapi + const goodDepositMessage = { + pubkey: pk, + withdrawalCredentials: fromHexString(GOOD_WC), + amount: 32000000000, // gwei! + }; + const goodSigningRoot = computeRoot(goodDepositMessage); + const goodSig = sk.sign(goodSigningRoot).toBytes(); + + const goodDepositData = { + ...goodDepositMessage, + signature: goodSig, + }; + const goodDepositDataRoot = DepositData.hashTreeRoot(goodDepositData); + + if (!process.env.WALLET_PRIVATE_KEY) throw new Error(NO_PRIVKEY_MESSAGE); + const wallet = new ethers.Wallet(process.env.WALLET_PRIVATE_KEY); + + // Make a deposit + const signer = wallet.connect(providerService.provider); + const depositContract = DepositAbi__factory.connect( + DEPOSIT_CONTRACT, + signer, + ); + await depositContract.deposit( + goodDepositData.pubkey, + goodDepositData.withdrawalCredentials, + goodDepositData.signature, + goodDepositDataRoot, + { value: ethers.constants.WeiPerEther.mul(32) }, + ); + + await depositService.setCachedEvents({ + data: [], + headers: { + startBlock: currentBlock.number, + endBlock: currentBlock.number, + version: '1', + }, + }); + + // mocked curated module + const stakingModule = mockedModule(currentBlock); + const meta = mockedMeta(currentBlock); + + mockedKeysApiOperators( + keysApiService, + mockedOperators, + stakingModule, + meta, + ); + + // list of keys for /keys?used=false mock + const unusedKeys = [ + { + key: '0x9948d2becf42e9f76922bc6f664545e6f50401050af95785a984802d32a95c4c61f8e3de312b78167f86e047f83a7796', + depositSignature: + '0x8bf4401a354de243a3716ee2efc0bde1ded56a40e2943ac7c50290bec37e935d6170b21e7c0872f203199386143ef12612a1488a8e9f1cdf1229c382f29c326bcbf6ed6a87d8fbfe0df87dacec6632fc4709d9d338f4cf81e861d942c23bba1e', + operatorIndex: 0, + used: false, + index: 0, + moduleAddress: NOP_REGISTRY, + }, + { + key: '0x9948d2becf42e9f76922bc6f664545e6f50401050af95785a984802d32a95c4c61f8e3de312b78167f86e047f83a7796', + depositSignature: + '0x8bf4401a354de243a3716ee2efc0bde1ded56a40e2943ac7c50290bec37e935d6170b21e7c0872f203199386143ef12612a1488a8e9f1cdf1229c382f29c326bcbf6ed6a87d8fbfe0df87dacec6632fc4709d9d338f4cf81e861d942c23bba1e', + operatorIndex: 0, + used: false, + index: 1, + moduleAddress: NOP_REGISTRY, + }, + ]; + + mockedKeysApiUnusedKeys(keysApiService, unusedKeys, meta); + + // Check that module was not paused + const routerContract = StakingRouterAbi__factory.connect( + STAKING_ROUTER, + providerService.provider, + ); + const isOnPause = await routerContract.getStakingModuleIsDepositsPaused( + 1, + ); + expect(isOnPause).toBe(false); + + await guardianService.handleNewBlock(); + + // just skip on this iteration deposit for staking module + expect(sendDepositMessage).toBeCalledTimes(0); + expect(sendPauseMessage).toBeCalledTimes(0); + + // after deleting duplicates in staking module, + // council will resume deposits to module + const unusedKeysWithoutDuplicates = [ + { + key: '0x9948d2becf42e9f76922bc6f664545e6f50401050af95785a984802d32a95c4c61f8e3de312b78167f86e047f83a7796', + depositSignature: + '0x8bf4401a354de243a3716ee2efc0bde1ded56a40e2943ac7c50290bec37e935d6170b21e7c0872f203199386143ef12612a1488a8e9f1cdf1229c382f29c326bcbf6ed6a87d8fbfe0df87dacec6632fc4709d9d338f4cf81e861d942c23bba1e', + operatorIndex: 0, + used: false, + index: 0, + moduleAddress: NOP_REGISTRY, + }, + ]; + + const newBlock = await tempProvider.getBlock('latest'); + const newMeta = mockedMeta(newBlock); + + mockedKeysApiOperators( + keysApiService, + mockedOperators, + stakingModule, + newMeta, + ); + + mockedKeysApiUnusedKeys( + keysApiService, + unusedKeysWithoutDuplicates, + newMeta, + ); + + await guardianService.handleNewBlock(); + + expect(sendDepositMessage).toBeCalledTimes(1); + + expect(sendDepositMessage).toHaveBeenLastCalledWith( + expect.objectContaining({ + blockNumber: newBlock.number, + guardianAddress: wallet.address, + guardianIndex: 9, + stakingModuleId: 1, + }), + ); + }, + TESTS_TIMEOUT, + ); + + test( + 'added unused keys for that deposit was already made', + async () => { + const tempProvider = new ethers.providers.JsonRpcProvider( + `http://127.0.0.1:${GANACHE_PORT}`, + ); + const currentBlock = await tempProvider.getBlock('latest'); + + // this key should be used in kapi + const goodDepositMessage = { + pubkey: pk, + withdrawalCredentials: fromHexString(GOOD_WC), + amount: 32000000000, // gwei! + }; + const goodSigningRoot = computeRoot(goodDepositMessage); + const goodSig = sk.sign(goodSigningRoot).toBytes(); + + const goodDepositData = { + ...goodDepositMessage, + signature: goodSig, + }; + const goodDepositDataRoot = DepositData.hashTreeRoot(goodDepositData); + + if (!process.env.WALLET_PRIVATE_KEY) throw new Error(NO_PRIVKEY_MESSAGE); + const wallet = new ethers.Wallet(process.env.WALLET_PRIVATE_KEY); + + // Make a deposit + const signer = wallet.connect(providerService.provider); + const depositContract = DepositAbi__factory.connect( + DEPOSIT_CONTRACT, + signer, + ); + await depositContract.deposit( + goodDepositData.pubkey, + goodDepositData.withdrawalCredentials, + goodDepositData.signature, + goodDepositDataRoot, + { value: ethers.constants.WeiPerEther.mul(32) }, + ); + + await depositService.setCachedEvents({ + data: [], + headers: { + startBlock: currentBlock.number, + endBlock: currentBlock.number, + version: '1', + }, + }); + + // mocked curated module + const stakingModule = mockedModule(currentBlock); + const meta = mockedMeta(currentBlock); + + mockedKeysApiOperators( + keysApiService, + mockedOperators, + stakingModule, + meta, + ); + + // list of keys for /keys?used=false mock + const unusedKeys = [ + { + key: toHexString(pk), + depositSignature: toHexString(goodSig), + operatorIndex: 0, + used: false, + index: 0, + moduleAddress: NOP_REGISTRY, + }, + ]; + + const keys = [...unusedKeys, { ...unusedKeys[0], used: true }]; + mockedKeysApiUnusedKeys(keysApiService, unusedKeys, meta); + mockedKeysWithDuplicates(keysApiService, keys, meta); + + // Check that module was not paused + const routerContract = StakingRouterAbi__factory.connect( + STAKING_ROUTER, + providerService.provider, + ); + const isOnPause = await routerContract.getStakingModuleIsDepositsPaused( + 1, + ); + expect(isOnPause).toBe(false); + + await guardianService.handleNewBlock(); + + // just skip on this iteration deposit for staking module + expect(sendDepositMessage).toBeCalledTimes(0); + expect(sendPauseMessage).toBeCalledTimes(0); + + // after deleting duplicates in staking module, + // council will resume deposits to module + + const newBlock = await tempProvider.getBlock('latest'); + const newMeta = mockedMeta(newBlock); + + mockedKeysApiOperators( + keysApiService, + mockedOperators, + stakingModule, + newMeta, + ); + + mockedKeysApiUnusedKeys(keysApiService, [], newMeta); + mockedKeysWithDuplicates(keysApiService, [], meta); + + await guardianService.handleNewBlock(); + + expect(sendDepositMessage).toBeCalledTimes(1); + + expect(sendDepositMessage).toHaveBeenLastCalledWith( + expect.objectContaining({ + blockNumber: newBlock.number, + guardianAddress: wallet.address, + guardianIndex: 9, + stakingModuleId: 1, + }), + ); + }, + TESTS_TIMEOUT, + ); + + test( + 'invalid unused keys', + async () => { + const tempProvider = new ethers.providers.JsonRpcProvider( + `http://127.0.0.1:${GANACHE_PORT}`, + ); + const currentBlock = await tempProvider.getBlock('latest'); + + await depositService.setCachedEvents({ + data: [], + headers: { + startBlock: currentBlock.number, + endBlock: currentBlock.number, + version: '1', + }, + }); + + // mocked curated module + const stakingModule = mockedModule(currentBlock); + const meta = mockedMeta(currentBlock); + + mockedKeysApiOperators( + keysApiService, + mockedOperators, + stakingModule, + meta, + ); + + // list of keys for /keys?used=false mock + const keyWithWrongSign = { + key: toHexString(pk), + depositSignature: + '0x8bf4401a354de243a3716ee2efc0bde1ded56a40e2943ac7c50290bec37e935d6170b21e7c0872f203199386143ef12612a1488a8e9f1cdf1229c382f29c326bcbf6ed6a87d8fbfe0df87dacec6632fc4709d9d338f4cf81e861d942c23bba1e', + operatorIndex: 0, + used: false, + index: 0, + moduleAddress: NOP_REGISTRY, + }; + mockedKeysApiUnusedKeys(keysApiService, [keyWithWrongSign], meta); + + if (!process.env.WALLET_PRIVATE_KEY) throw new Error(NO_PRIVKEY_MESSAGE); + const wallet = new ethers.Wallet(process.env.WALLET_PRIVATE_KEY); + + await guardianService.handleNewBlock(); + + // just skip on this iteration deposit for staking module + expect(sendDepositMessage).toBeCalledTimes(1); + + expect(sendDepositMessage).toHaveBeenLastCalledWith( + expect.objectContaining({ + blockNumber: currentBlock.number, + guardianAddress: wallet.address, + guardianIndex: 9, + stakingModuleId: 1, + }), + ); + }, + TESTS_TIMEOUT, + ); }); From 2bff3122dbad3c252bc548ad0cc9ebd28f6d0841 Mon Sep 17 00:00:00 2001 From: Anna Mukharram Date: Wed, 13 Dec 2023 02:05:58 +0400 Subject: [PATCH 2/2] fix: unit tests --- .../staking-module-guard.spec.ts | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/guardian/staking-module-guard/staking-module-guard.spec.ts b/src/guardian/staking-module-guard/staking-module-guard.spec.ts index 3110f4ca..f046c0cd 100644 --- a/src/guardian/staking-module-guard/staking-module-guard.spec.ts +++ b/src/guardian/staking-module-guard/staking-module-guard.spec.ts @@ -665,9 +665,9 @@ describe('StakingModuleGuardService', () => { const pubkeyWithUsedKey2 = '0x3478'; const lidoWC = '0x12'; const intersectionsWithLidoWC = [ - { pubkey: pubkeyWithUsedKey1, wc: lidoWC } as any, - { pubkey: pubkeyWithoutUsedKey, wc: lidoWC } as any, - { pubkey: pubkeyWithUsedKey2, wc: lidoWC } as any, + { pubkey: pubkeyWithUsedKey1, wc: lidoWC, valid: true } as any, + { pubkey: pubkeyWithoutUsedKey, wc: lidoWC, valid: true } as any, + { pubkey: pubkeyWithUsedKey2, wc: lidoWC, valid: true } as any, ]; // function that return list from kapi that match keys in parameter const mockSendMessageFromGuardian = jest @@ -763,9 +763,9 @@ describe('StakingModuleGuardService', () => { const pubkey3 = '0x3478'; const lidoWC = '0x12'; const intersectionsWithLidoWC = [ - { pubkey: pubkey1, wc: lidoWC } as any, - { pubkey: pubkey2, wc: lidoWC } as any, - { pubkey: pubkey3, wc: lidoWC } as any, + { pubkey: pubkey1, wc: lidoWC, valid: true } as any, + { pubkey: pubkey2, wc: lidoWC, valid: true } as any, + { pubkey: pubkey3, wc: lidoWC, valid: true } as any, ]; // function that return list from kapi that match keys in parameter const mockSendMessageFromGuardian = jest @@ -825,9 +825,9 @@ describe('StakingModuleGuardService', () => { const pubkey3 = '0x3478'; const lidoWC = '0x12'; const intersectionsWithLidoWC = [ - { pubkey: pubkey1, wc: lidoWC } as any, - { pubkey: pubkey2, wc: lidoWC } as any, - { pubkey: pubkey3, wc: lidoWC } as any, + { pubkey: pubkey1, wc: lidoWC, valid: true } as any, + { pubkey: pubkey2, wc: lidoWC, valid: true } as any, + { pubkey: pubkey3, wc: lidoWC, valid: true } as any, ]; // function that return list from kapi that match keys in parameter const mockSendMessageFromGuardian = jest