diff --git a/src/http/keys/keys.e2e-spec.ts b/src/http/keys/keys.e2e-spec.ts index 9e29af8e..c0f852a4 100644 --- a/src/http/keys/keys.e2e-spec.ts +++ b/src/http/keys/keys.e2e-spec.ts @@ -14,7 +14,6 @@ import { nullTransport, LoggerModule } from '@lido-nestjs/logger'; import * as request from 'supertest'; import { FastifyAdapter, NestFastifyApplication } from '@nestjs/platform-fastify'; -import { hexZeroPad } from 'ethers/lib/utils'; // import { validationOpt } from '../../main'; describe('KeyController (e2e)', () => { @@ -32,34 +31,76 @@ describe('KeyController (e2e)', () => { timestamp: 1691500803, }; - const getKey = (value: string) => { - return { - key: hexZeroPad(value, 98), - depositSignature: hexZeroPad(value, 194), - used: true, - }; - }; - const dvtModuleKeys = [ - { operatorIndex: 1, index: 1, moduleAddress: dvtModule.stakingModuleAddress, ...getKey('0x01'), used: true }, - { operatorIndex: 1, index: 2, moduleAddress: dvtModule.stakingModuleAddress, ...getKey('0x02'), used: false }, + { + operatorIndex: 1, + index: 1, + moduleAddress: dvtModule.stakingModuleAddress, + key: '0xa544bc44d9eacbf4dd6a2d6087b43f4c67fd5618651b97effcb30997bf49e5d7acf0100ef14e5d087cc228bc78d498e6', + depositSignature: + '0x967875a0104d9f674538e2ec0df4be0a61ef08061cdcfa83e5a63a43dadb772d29053368224e5d8e046ba1a78490f5fc0f0186f23af0465d0a82b2db2e7535782fe12e1fd1cd4f6eb77d8dc7a4f7ab0fde31435d5fa98a013e0a716c5e1ef6a2', + used: true, + }, + { + operatorIndex: 1, + index: 2, + moduleAddress: dvtModule.stakingModuleAddress, + key: '0xb3e9f4e915f9fb9ef9c55da1815071f3f728cc6fc434fba2c11e08db5b5fa22b71d5975cec30ef97e7fc901e5a04ee5b', + depositSignature: + '0xb048f4a409d5a0aa638e5ec65c21e936ffde9a8d848e74e6b2f6972a4145620dc78c79db5425ea1a5c6b1dd8d50fc77f0bcec894c0a9446776936f2adf4f1dc7056fb3c4bdf9dbd00981288d4e582875d10b13d780dddc642496e97826abd3c7', + used: false, + }, ]; const keyForOperatorTwo = { operatorIndex: 2, index: 5, moduleAddress: curatedModule.stakingModuleAddress, - ...getKey('0x03'), + key: '0x91024d603575605569c212b00f375c8bad733a697b453fbe054bb996bd24c7d1a5b6034cc58943aeddab05cbdfd40632', + depositSignature: + '0x9990450099816e066c20b5947be6bf089b57fcfacfb2c8285ddfd6c678a44198bf7c013a0d1a6353ed19dd94423eef7b010d25aaa2c3093760c79bf247f5350120e8a74e4586eeba0f1e2bcf17806f705007d7b5862039da5cd93ee659280d77', used: true, }; const keyForOperatorTwoDuplicate = { ...keyForOperatorTwo, index: 6 }; const curatedModuleKeys = [ - { operatorIndex: 1, index: 1, moduleAddress: curatedModule.stakingModuleAddress, ...getKey('0x04'), used: true }, - { operatorIndex: 1, index: 2, moduleAddress: curatedModule.stakingModuleAddress, ...getKey('0x05'), used: true }, - { operatorIndex: 1, index: 3, moduleAddress: curatedModule.stakingModuleAddress, ...getKey('0x06'), used: false }, - { operatorIndex: 2, index: 4, moduleAddress: curatedModule.stakingModuleAddress, ...getKey('0x07'), used: true }, + { + operatorIndex: 1, + index: 1, + moduleAddress: curatedModule.stakingModuleAddress, + key: '0xa554bc44d9eacbf4dd6a2d6087b43f4c67fd5618651b97effcb30997bf49e5d7acf0100ef14e5d087cc228bc78d498e6', + depositSignature: + '0x967875a0104d9f674538e2ec0df4be0a61ef08061cdcfa83e5a63a43dadb772d29053368224e5d8e046ba1a78490f5fc0f0186f23af0465d0a82b2db2e7535782fe12e1fd1cd4f6eb77d8dc7a4f7ab0fde31435d5fa98a013e0a716c5e1ef6a2', + used: true, + }, + { + operatorIndex: 1, + index: 2, + moduleAddress: curatedModule.stakingModuleAddress, + key: '0xb3a9f4e915f9fb9ef9c55da1815071f3f728cc6fc434fba2c11e08db5b5fa22b71d5975cec30ef97e7fc901e5a04ee5b', + depositSignature: + '0xb048f4a409d5a0aa638e5ec65c21e936ffde9a8d848e74e6b2f6972a4145620dc78c79db5425ea1a5c6b1dd8d50fc77f0bcec894c0a9446776936f2adf4f1dc7056fb3c4bdf9dbd00981288d4e582875d10b13d780dddc642496e97826abd3c7', + used: true, + }, + { + operatorIndex: 1, + index: 3, + moduleAddress: curatedModule.stakingModuleAddress, + key: '0x91524d603575605569c212b00f375c8bad733a697b453fbe054bb996bd24c7d1a5b6034cc58943aeddab05cbdfd40632', + depositSignature: + '0x9990450099816e066c20b5947be6bf089b57fcfacfb2c8285ddfd6c678a44198bf7c013a0d1a6353ed19dd94423eef7b010d25aaa2c3093760c79bf247f5350120e8a74e4586eeba0f1e2bcf17806f705007d7b5862039da5cd93ee659280d77', + used: false, + }, + { + operatorIndex: 2, + index: 4, + moduleAddress: curatedModule.stakingModuleAddress, + key: '0xa544bc44d8eacbf4dd6a2d6087b43f4c67fd5618651b97effcb30997bf49e5d7acf0100ef14e5d087cc228bc78d498e6', + depositSignature: + '0x967875a0104d1f674538e2ec0df4be0a61ef08061cdcfa83e5a63a43dadb772d29053368224e5d8e046ba1a78490f5fc0f0186f23af0465d0a82b2db2e7535782fe12e1fd1cd4f6eb77d8dc7a4f7ab0fde31435d5fa98a013e0a716c5e1ef6a2', + used: true, + }, keyForOperatorTwo, keyForOperatorTwoDuplicate, ]; @@ -421,4 +462,76 @@ describe('KeyController (e2e)', () => { }); }); }); + + describe('The /v1/keys/{pubkey} requests', () => { + describe('too early response case', () => { + beforeEach(async () => { + await cleanDB(); + }); + + afterEach(async () => { + await cleanDB(); + }); + + it('should return too early response if there are no modules in database', async () => { + // lets save meta + await elMetaStorageService.update(elMeta); + // lets save keys + await keysStorageService.save(keys); + const resp = await request(app.getHttpServer()).get(`/v1/keys/wrongkey`); + expect(resp.status).toEqual(425); + expect(resp.body).toEqual({ message: 'Too early response', statusCode: 425 }); + }); + + it('should return too early response if there are no meta', async () => { + // lets save keys + await keysStorageService.save(keys); + await moduleStorageService.store(curatedModule, 1); + const resp = await request(app.getHttpServer()).get(`/v1/keys/wrongkey`); + expect(resp.status).toEqual(425); + expect(resp.body).toEqual({ message: 'Too early response', statusCode: 425 }); + }); + }); + + describe('api ready to work', () => { + beforeAll(async () => { + // lets save meta + await elMetaStorageService.update(elMeta); + // lets save keys + await keysStorageService.save(keys); + // lets save modules + await moduleStorageService.store(dvtModule, 1); + await moduleStorageService.store(curatedModule, 1); + }); + + afterAll(async () => { + await cleanDB(); + }); + + it('should return all keys that satisfy the request', async () => { + const resp = await request(app.getHttpServer()).get(`/v1/keys/${keyForOperatorTwoDuplicate.key}`); + expect(resp.status).toEqual(200); + // as pubkeys contains 3 elements and keyForOperatorTwo has a duplicate + expect(resp.body.data.length).toEqual(2); + expect(resp.body.data).toEqual(expect.arrayContaining([keyForOperatorTwo, keyForOperatorTwoDuplicate])); + expect(resp.body.meta).toEqual({ + elBlockSnapshot: { + blockNumber: elMeta.number, + blockHash: elMeta.hash, + timestamp: elMeta.timestamp, + }, + }); + }); + + it('should return 404 if key was not found', async () => { + const resp = await request(app.getHttpServer()).get(`/v1/keys/someunknownkey`); + expect(resp.status).toEqual(404); + expect(resp.body).toEqual({ + error: 'Not Found', + message: 'There are no keys with someunknownkey public key in db.', + statusCode: 404, + }); + }); + }); + }); }); diff --git a/src/http/keys/keys.service.ts b/src/http/keys/keys.service.ts index 98ce37ac..995c8ca3 100644 --- a/src/http/keys/keys.service.ts +++ b/src/http/keys/keys.service.ts @@ -1,4 +1,4 @@ -import { Inject, Injectable, LoggerService } from '@nestjs/common'; +import { Inject, Injectable, LoggerService, NotFoundException } from '@nestjs/common'; import { LOGGER_PROVIDER } from '@lido-nestjs/logger'; import { KeyListResponse, KeyWithModuleAddress } from './entities'; import { StakingRouterService } from '../../staking-router-modules/staking-router.service'; @@ -7,6 +7,8 @@ import { KeyField } from 'staking-router-modules/interfaces/filters'; import { IsolationLevel } from '@mikro-orm/core'; import { EntityManager } from '@mikro-orm/knex'; +type KeyWithModuleAddressFieldT = keyof KeyWithModuleAddress; + @Injectable() export class KeysService { constructor( @@ -43,7 +45,42 @@ export class KeysService { } async getByPubkey(pubkey: string): Promise { - return this.getByPubkeys([pubkey]); + const { keys, elBlockSnapshot } = await this.entityManager.transactional( + async () => { + const { stakingModules, elBlockSnapshot } = await this.stakingRouterService.getStakingModulesAndMeta(); + const collectedKeys: KeyWithModuleAddress[][] = []; + + for (const module of stakingModules) { + const moduleInstance = this.stakingRouterService.getStakingRouterModuleImpl(module.type); + const fields: KeyWithModuleAddressFieldT[] = [ + 'key', + 'depositSignature', + 'operatorIndex', + 'used', + 'moduleAddress', + ]; + const keys: KeyWithModuleAddress[] = await moduleInstance.getKeysByPubkey( + module.stakingModuleAddress, + pubkey, + fields, + ); + + collectedKeys.push(keys); + } + + return { keys: collectedKeys.flat(), elBlockSnapshot }; + }, + { isolationLevel: IsolationLevel.REPEATABLE_READ }, + ); + + if (keys.length == 0) { + throw new NotFoundException(`There are no keys with ${pubkey} public key in db.`); + } + + return { + data: keys, + meta: { elBlockSnapshot }, + }; } async getByPubkeys(pubKeys: string[]): Promise { @@ -54,7 +91,13 @@ export class KeysService { for (const module of stakingModules) { const moduleInstance = this.stakingRouterService.getStakingRouterModuleImpl(module.type); - const fields: KeyField[] = ['key', 'depositSignature', 'operatorIndex', 'used', 'moduleAddress']; + const fields: KeyWithModuleAddressFieldT[] = [ + 'key', + 'depositSignature', + 'operatorIndex', + 'used', + 'moduleAddress', + ]; const keys: KeyWithModuleAddress[] = await moduleInstance.getKeysByPubKeys( module.stakingModuleAddress, pubKeys,