From dbfee63574edd8ba7c457cb70f1f554850d438a0 Mon Sep 17 00:00:00 2001 From: Alexander Lukin Date: Sat, 25 Nov 2023 16:40:33 +0400 Subject: [PATCH 01/48] fix: add missing `middleware` package Add the missing `@lido-nextjs/middleware` package to the "dependencies" section of the `package.json` file. The `MiddlewareService` symbol from this package is used in the `src/common/consensus-provider/consensus-fetch.service.ts` file but was not explicitly listed in dependencies. Now it is fixed. --- package.json | 1 + yarn.lock | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index 695de50a..a887459f 100644 --- a/package.json +++ b/package.json @@ -51,6 +51,7 @@ "@lido-nestjs/execution": "^1.11.0", "@lido-nestjs/fetch": "^1.4.0", "@lido-nestjs/logger": "^1.3.2", + "@lido-nestjs/middleware": "^1.2.0", "@lido-nestjs/validators-registry": "^1.3.0", "@mikro-orm/cli": "^5.5.3", "@mikro-orm/core": "^5.5.3", diff --git a/yarn.lock b/yarn.lock index d44b1ee1..e0c68a6c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1319,7 +1319,7 @@ traverse "^0.6.7" winston "^3.4.0" -"@lido-nestjs/middleware@1.2.0": +"@lido-nestjs/middleware@1.2.0", "@lido-nestjs/middleware@^1.2.0": version "1.2.0" resolved "https://registry.yarnpkg.com/@lido-nestjs/middleware/-/middleware-1.2.0.tgz#0e64b69149ef1f7e4594896fd08d6e0bd9ef5382" integrity sha512-0rkXWKXEKJPRbq1W6cN+yelhc+CruqhUqmdkNKwl4vTk631fpG9yfSfxxQFDAs3LhsTORqqgKUBpdK+epRYjIQ== From ed05fa801f6326081bc8f15f16e3ba3c04375097 Mon Sep 17 00:00:00 2001 From: Anna Mukharram Date: Mon, 11 Dec 2023 23:49:28 +0400 Subject: [PATCH 02/48] fix: log --- src/common/registry/main/abstract-registry.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/common/registry/main/abstract-registry.ts b/src/common/registry/main/abstract-registry.ts index 4eeb5fbc..1c8e14b9 100644 --- a/src/common/registry/main/abstract-registry.ts +++ b/src/common/registry/main/abstract-registry.ts @@ -138,8 +138,6 @@ export abstract class AbstractRegistryService { this.logger.log('Keys saved', { operatorIndex }); } - - console.timeEnd('FETCH_OPERATORS'); } /** storage */ From d5247d523855a5233175dcabdfbff6db635308cb Mon Sep 17 00:00:00 2001 From: Eddort Date: Mon, 18 Dec 2023 15:01:52 +0100 Subject: [PATCH 03/48] feat: add finalizedUsedSigningKeys --- .../fetch/interfaces/operator.interface.ts | 1 + src/common/registry/fetch/operator.fetch.ts | 6 +++ src/common/registry/main/abstract-registry.ts | 39 +++++++++++++++++-- 3 files changed, 43 insertions(+), 3 deletions(-) diff --git a/src/common/registry/fetch/interfaces/operator.interface.ts b/src/common/registry/fetch/interfaces/operator.interface.ts index e4d20826..a1392d4a 100644 --- a/src/common/registry/fetch/interfaces/operator.interface.ts +++ b/src/common/registry/fetch/interfaces/operator.interface.ts @@ -8,4 +8,5 @@ export interface RegistryOperator { totalSigningKeys: number; usedSigningKeys: number; moduleAddress: string; + finalizedUsedSigningKeys: number; } diff --git a/src/common/registry/fetch/operator.fetch.ts b/src/common/registry/fetch/operator.fetch.ts index 2e5110f6..673d82f4 100644 --- a/src/common/registry/fetch/operator.fetch.ts +++ b/src/common/registry/fetch/operator.fetch.ts @@ -73,6 +73,9 @@ export class RegistryOperatorFetchService { ): Promise { const fullInfo = true; const operator = await this.getContract(moduleAddress).getNodeOperator(operatorIndex, fullInfo, overrides as any); + const finalizedOperator = await this.getContract(moduleAddress).getNodeOperator(operatorIndex, fullInfo, { + blockTag: 'finalized', + }); const { name, @@ -84,6 +87,8 @@ export class RegistryOperatorFetchService { totalDepositedValidators, } = operator; + const { totalDepositedValidators: finalizedUsedSigningKeys } = finalizedOperator; + return { index: operatorIndex, active, @@ -94,6 +99,7 @@ export class RegistryOperatorFetchService { totalSigningKeys: totalAddedValidators.toNumber(), usedSigningKeys: totalDepositedValidators.toNumber(), moduleAddress, + finalizedUsedSigningKeys: finalizedUsedSigningKeys.toNumber(), }; } diff --git a/src/common/registry/main/abstract-registry.ts b/src/common/registry/main/abstract-registry.ts index 4eeb5fbc..d5abac36 100644 --- a/src/common/registry/main/abstract-registry.ts +++ b/src/common/registry/main/abstract-registry.ts @@ -14,7 +14,7 @@ import { RegistryOperator } from '../storage/operator.entity'; import { compareOperators } from '../utils/operator.utils'; -import { REGISTRY_GLOBAL_OPTIONS_TOKEN } from './constants'; +import { BLOCKS_OVERLAP, REGISTRY_GLOBAL_OPTIONS_TOKEN } from './constants'; import { RegistryOptions } from './interfaces/module.interface'; import { chunk } from '@lido-nestjs/utils'; import { RegistryKeyBatchFetchService } from '../fetch/key-batch.fetch'; @@ -71,10 +71,42 @@ export abstract class AbstractRegistryService { * @returns Check if operators have been changed */ public async operatorsWereChanged( + moduleAddress: string, + { + fromBlockNumber, + toBlockNumber, + }: { + fromBlockNumber?: number; + toBlockNumber: number; + }, + ): Promise { + if (fromBlockNumber === undefined) return true; + + if (fromBlockNumber > toBlockNumber) { + throw new Error(`invalid blocks range: ${fromBlockNumber} (fromBlockNumber) > ${toBlockNumber} (toBlockNumber)`); + } + // check how big the difference between the blocks is, if it exceeds, we should update the state anyway + if (toBlockNumber - fromBlockNumber > BLOCKS_OVERLAP) return true; + + return await this.operatorFetch.operatorsWereChanged(moduleAddress, fromBlockNumber, toBlockNumber); + } + + /** + * + * @param moduleAddress contract address + * @returns Check if operators have been changed + */ + public async keysWereChanged( moduleAddress: string, fromBlockNumber: number, toBlockNumber: number, ): Promise { + if (fromBlockNumber > toBlockNumber) { + throw new Error(`invalid blocks range: ${fromBlockNumber} (fromBlockNumber) > ${toBlockNumber} (toBlockNumber)`); + } + // check how big the difference between the blocks is, if it exceeds, we should update the state anyway + if (toBlockNumber - fromBlockNumber > BLOCKS_OVERLAP) return true; + return await this.operatorFetch.operatorsWereChanged(moduleAddress, fromBlockNumber, toBlockNumber); } @@ -110,7 +142,8 @@ export abstract class AbstractRegistryService { // skip updating keys from 0 to `usedSigningKeys` of previous collected data // since the contract guarantees that these keys cannot be changed - const unchangedKeysMaxIndex = isSameOperator ? prevOperator.usedSigningKeys : 0; + const unchangedKeysMaxIndex = + isSameOperator && prevOperator.finalizedUsedSigningKeys ? prevOperator.finalizedUsedSigningKeys : 0; // get the right border up to which the keys should be updated // it's different for different scenarios const toIndex = this.getToIndex(currOperator); @@ -121,7 +154,7 @@ export abstract class AbstractRegistryService { const operatorIndex = currOperator.index; const overrides = { blockTag: { blockHash } }; - // TODO: use feature flag + const result = await this.keyBatchFetch.fetch(moduleAddress, operatorIndex, fromIndex, toIndex, overrides); const operatorKeys = result.filter((key) => key); From 2471e3f185c3a90db7d6589cc0e004532e72e741 Mon Sep 17 00:00:00 2001 From: Eddort Date: Tue, 19 Dec 2023 13:54:34 +0100 Subject: [PATCH 04/48] feat: new indexing logic --- .../execution-provider.service.ts | 9 ++ src/common/registry/fetch/operator.fetch.ts | 6 +- src/common/registry/main/abstract-registry.ts | 21 +-- src/common/registry/main/constants.ts | 1 + .../registry/storage/operator.entity.ts | 4 + src/jobs/keys-update/keys-update.service.ts | 141 +++++++++++++++--- src/storage/el-meta.entity.ts | 4 + src/storage/el-meta.storage.ts | 8 +- src/storage/sr-module.entity.ts | 7 +- src/storage/sr-module.storage.ts | 4 +- 10 files changed, 158 insertions(+), 47 deletions(-) diff --git a/src/common/execution-provider/execution-provider.service.ts b/src/common/execution-provider/execution-provider.service.ts index 45b1e074..e84eafaa 100644 --- a/src/common/execution-provider/execution-provider.service.ts +++ b/src/common/execution-provider/execution-provider.service.ts @@ -46,4 +46,13 @@ export class ExecutionProviderService { const block = await this.provider.getBlock(blockHashOrBlockTag); return { number: block.number, hash: block.hash, timestamp: block.timestamp }; } + + /** + * + * Returns full block info + */ + public async getFullBlock(blockHashOrBlockTag: number | string) { + const block = await this.provider.getBlock(blockHashOrBlockTag); + return block; + } } diff --git a/src/common/registry/fetch/operator.fetch.ts b/src/common/registry/fetch/operator.fetch.ts index 673d82f4..223e7090 100644 --- a/src/common/registry/fetch/operator.fetch.ts +++ b/src/common/registry/fetch/operator.fetch.ts @@ -72,8 +72,10 @@ export class RegistryOperatorFetchService { overrides: CallOverrides = {}, ): Promise { const fullInfo = true; - const operator = await this.getContract(moduleAddress).getNodeOperator(operatorIndex, fullInfo, overrides as any); - const finalizedOperator = await this.getContract(moduleAddress).getNodeOperator(operatorIndex, fullInfo, { + const contract = this.getContract(moduleAddress); + + const operator = await contract.getNodeOperator(operatorIndex, fullInfo, overrides as any); + const finalizedOperator = await contract.getNodeOperator(operatorIndex, fullInfo, { blockTag: 'finalized', }); diff --git a/src/common/registry/main/abstract-registry.ts b/src/common/registry/main/abstract-registry.ts index d5abac36..e0c3fc2b 100644 --- a/src/common/registry/main/abstract-registry.ts +++ b/src/common/registry/main/abstract-registry.ts @@ -87,26 +87,7 @@ export abstract class AbstractRegistryService { } // check how big the difference between the blocks is, if it exceeds, we should update the state anyway if (toBlockNumber - fromBlockNumber > BLOCKS_OVERLAP) return true; - - return await this.operatorFetch.operatorsWereChanged(moduleAddress, fromBlockNumber, toBlockNumber); - } - - /** - * - * @param moduleAddress contract address - * @returns Check if operators have been changed - */ - public async keysWereChanged( - moduleAddress: string, - fromBlockNumber: number, - toBlockNumber: number, - ): Promise { - if (fromBlockNumber > toBlockNumber) { - throw new Error(`invalid blocks range: ${fromBlockNumber} (fromBlockNumber) > ${toBlockNumber} (toBlockNumber)`); - } - // check how big the difference between the blocks is, if it exceeds, we should update the state anyway - if (toBlockNumber - fromBlockNumber > BLOCKS_OVERLAP) return true; - + // rename return await this.operatorFetch.operatorsWereChanged(moduleAddress, fromBlockNumber, toBlockNumber); } diff --git a/src/common/registry/main/constants.ts b/src/common/registry/main/constants.ts index 17883f7b..8a6fcc14 100644 --- a/src/common/registry/main/constants.ts +++ b/src/common/registry/main/constants.ts @@ -1 +1,2 @@ export const REGISTRY_GLOBAL_OPTIONS_TOKEN = Symbol('registryGlobalOptions'); +export const BLOCKS_OVERLAP = 120; diff --git a/src/common/registry/storage/operator.entity.ts b/src/common/registry/storage/operator.entity.ts index 2538a588..1074a491 100644 --- a/src/common/registry/storage/operator.entity.ts +++ b/src/common/registry/storage/operator.entity.ts @@ -17,6 +17,7 @@ export class RegistryOperator { this.totalSigningKeys = operator.totalSigningKeys; this.usedSigningKeys = operator.usedSigningKeys; this.moduleAddress = operator.moduleAddress; + this.finalizedUsedSigningKeys = operator.finalizedUsedSigningKeys; } @PrimaryKey() @@ -46,4 +47,7 @@ export class RegistryOperator { @PrimaryKey() @Property({ length: ADDRESS_LEN }) moduleAddress!: string; + + @Property() + finalizedUsedSigningKeys!: number; } diff --git a/src/jobs/keys-update/keys-update.service.ts b/src/jobs/keys-update/keys-update.service.ts index 2f5075b4..33388dba 100644 --- a/src/jobs/keys-update/keys-update.service.ts +++ b/src/jobs/keys-update/keys-update.service.ts @@ -1,4 +1,5 @@ import { Inject, Injectable } from '@nestjs/common'; +import { range } from '@lido-nestjs/utils'; import { LOGGER_PROVIDER, LoggerService } from 'common/logger'; import { ConfigService } from 'common/config'; import { JobService } from 'common/job'; @@ -15,6 +16,8 @@ import { PrometheusService } from 'common/prometheus'; import { SrModuleEntity } from 'storage/sr-module.entity'; import { StakingModule } from 'staking-router-modules/interfaces/staking-module.interface'; +const MAX_BLOCKS_OVERLAP = 30; + class KeyOutdatedError extends Error { lastBlock: number; @@ -115,10 +118,17 @@ export class KeysUpdateService { // read from database last execution layer data const prevElMeta = await this.elMetaStorage.get(); + // handle the situation when the node has fallen behind the service state if (prevElMeta && prevElMeta?.blockNumber > currElMeta.number) { this.logger.warn('Previous data is newer than current data', prevElMeta); return; } + + if (prevElMeta?.blockHash && prevElMeta.blockHash === currElMeta.hash) { + this.logger.debug?.('same state, skip', { prevElMeta, currElMeta }); + return; + } + // Get modules from storage const storageModules = await this.srModulesStorage.findAll(); // Get staking modules from SR contract @@ -133,44 +143,101 @@ export class KeysUpdateService { await this.entityManager.transactional( async () => { - // Update EL meta in db - await this.elMetaStorage.update(currElMeta); + const prevBlockHash = prevElMeta?.blockHash; + const currentBlockHash = currElMeta.hash; + + let lastChangedBlockHash = prevBlockHash || currentBlockHash; + + let isReorgDetected = false; for (const contractModule of contractModules) { + const { stakingModuleAddress } = contractModule; + // Find implementation for staking module const moduleInstance = this.stakingRouterService.getStakingRouterModuleImpl(contractModule.type); // Read current nonce from contract - const currNonce = await moduleInstance.getCurrentNonce(contractModule.stakingModuleAddress, currElMeta.hash); + const currNonce = await moduleInstance.getCurrentNonce(stakingModuleAddress, currentBlockHash); // Read module in storage const moduleInStorage = await this.srModulesStorage.findOneById(contractModule.moduleId); const prevNonce = moduleInStorage?.nonce; - // update staking module information - await this.srModulesStorage.upsert(contractModule, currNonce); this.logger.log(`Nonce previous value: ${prevNonce}, nonce current value: ${currNonce}`); - if (prevNonce === currNonce) { - this.logger.log("Nonce wasn't changed, no need to update keys"); - // case when prevELMeta is undefined but prevNonce === currNonce looks like invalid - // use here prevElMeta.blockNumber + 1 because operators were updated in database for prevElMeta.blockNumber block - if ( - prevElMeta && - prevElMeta.blockNumber < currElMeta.number && - (await moduleInstance.operatorsWereChanged( - contractModule.stakingModuleAddress, - prevElMeta.blockNumber + 1, - currElMeta.number, - )) - ) { - this.logger.log('Update events happened, need to update operators'); - await moduleInstance.updateOperators(contractModule.stakingModuleAddress, currElMeta.hash); - } + if (!prevElMeta) { + this.logger.log('No past state found, start indexing', { stakingModuleAddress, currentBlockHash }); + + await moduleInstance.update(stakingModuleAddress, currentBlockHash); + await this.srModulesStorage.upsert(contractModule, currNonce, currentBlockHash); + lastChangedBlockHash = currentBlockHash; + continue; + } + + if (prevNonce !== currNonce) { + this.logger.log('Nonce has been changed, start indexing', { + stakingModuleAddress, + currentBlockHash, + prevNonce, + currNonce, + }); + + await moduleInstance.update(stakingModuleAddress, currentBlockHash); + await this.srModulesStorage.upsert(contractModule, currNonce, currentBlockHash); + lastChangedBlockHash = currentBlockHash; + continue; + } + if (this.isTooMuchDiffBetweenBlocks(prevElMeta.blockNumber, currElMeta.number)) { + this.logger.log('Too much difference between the blocks, start indexing', { + stakingModuleAddress, + currentBlockHash, + }); + + await moduleInstance.update(stakingModuleAddress, currentBlockHash); + await this.srModulesStorage.upsert(contractModule, currNonce, currentBlockHash); + lastChangedBlockHash = currentBlockHash; continue; } - await moduleInstance.update(contractModule.stakingModuleAddress, currElMeta.hash); + // calculate once per iteration + // no need to recheck each module separately + isReorgDetected = isReorgDetected ? true : await this.isReorgDetected(prevElMeta.blockHash, currentBlockHash); + + if (isReorgDetected) { + this.logger.log('Reorg detected, start indexing', { stakingModuleAddress, currentBlockHash }); + + await moduleInstance.update(stakingModuleAddress, currentBlockHash); + await this.srModulesStorage.upsert(contractModule, currNonce, currentBlockHash); + lastChangedBlockHash = currentBlockHash; + continue; + } + + if ( + prevElMeta.blockNumber < currElMeta.number && + (await moduleInstance.operatorsWereChanged( + contractModule.stakingModuleAddress, + prevElMeta.blockNumber + 1, + currElMeta.number, + )) + ) { + this.logger.log('Update operator events happened, need to update operators', { + stakingModuleAddress, + currentBlockHash, + }); + + await moduleInstance.updateOperators(stakingModuleAddress, currentBlockHash); + await this.srModulesStorage.upsert(contractModule, currNonce, currentBlockHash); + lastChangedBlockHash = currentBlockHash; + continue; + } + + this.logger.log('No changes have been detected in the module, indexing is not required', { + stakingModuleAddress, + currentBlockHash, + }); } + + // Update EL meta in db + await this.elMetaStorage.update({ ...currElMeta, lastChangedBlockHash }); }, { isolationLevel: IsolationLevel.READ_COMMITTED }, ); @@ -178,6 +245,36 @@ export class KeysUpdateService { return currElMeta; } + public async isReorgDetected(prevBlockHash: string, currentBlockHash: string) { + const currentBlock = await this.executionProvider.getFullBlock(currentBlockHash); + const prevBlock = await this.executionProvider.getFullBlock(prevBlockHash); + + if (currentBlock.parentHash === prevBlock.hash) return false; + // TODO: different hash but same number + if (currentBlock.number === prevBlock.number) return true; + + const blocks = await Promise.all( + range(prevBlock.number, currentBlock.number).map(async (bNumber) => { + return await this.executionProvider.getFullBlock(bNumber); + }), + ); + + for (let i = 1; i < blocks.length; i++) { + const previousBlock = blocks[i - 1]; + const currentBlock = blocks[i]; + + if (currentBlock.parentHash !== previousBlock.hash) { + return false; + } + } + + return true; + } + + public isTooMuchDiffBetweenBlocks(prevBlockNumber: number, currentBlockNumber: number) { + return currentBlockNumber - prevBlockNumber >= MAX_BLOCKS_OVERLAP; + } + /** * Update prometheus metrics of staking modules */ diff --git a/src/storage/el-meta.entity.ts b/src/storage/el-meta.entity.ts index 693e1c5c..49c82f21 100644 --- a/src/storage/el-meta.entity.ts +++ b/src/storage/el-meta.entity.ts @@ -11,6 +11,7 @@ export class ElMetaEntity { this.blockNumber = meta.blockNumber; this.blockHash = meta.blockHash.toLocaleLowerCase(); this.timestamp = meta.timestamp; + this.lastChangedBlockHash = meta.lastChangedBlockHash; } @PrimaryKey() @@ -22,4 +23,7 @@ export class ElMetaEntity { @Property() timestamp: number; + + @Property({ length: BLOCK_HASH_LEN }) + lastChangedBlockHash: string; } diff --git a/src/storage/el-meta.storage.ts b/src/storage/el-meta.storage.ts index 4407ae41..1a022bdf 100644 --- a/src/storage/el-meta.storage.ts +++ b/src/storage/el-meta.storage.ts @@ -12,13 +12,19 @@ export class ElMetaStorageService { return result[0] ?? null; } - async update(currElMeta: { number: number; hash: string; timestamp: number }): Promise { + async update(currElMeta: { + number: number; + hash: string; + timestamp: number; + lastChangedBlockHash: string; + }): Promise { await this.repository.nativeDelete({}); await this.repository.persist( new ElMetaEntity({ blockHash: currElMeta.hash, blockNumber: currElMeta.number, timestamp: currElMeta.timestamp, + lastChangedBlockHash: currElMeta.lastChangedBlockHash, }), ); await this.repository.flush(); diff --git a/src/storage/sr-module.entity.ts b/src/storage/sr-module.entity.ts index 2e23bc24..888bd4b8 100644 --- a/src/storage/sr-module.entity.ts +++ b/src/storage/sr-module.entity.ts @@ -7,7 +7,7 @@ import { SRModuleRepository } from './sr-module.repository'; export class SrModuleEntity implements StakingModule { [EntityRepositoryType]?: SRModuleRepository; - constructor(srModule: StakingModule, nonce: number) { + constructor(srModule: StakingModule, nonce: number, lastChangedBlockHash: string) { this.moduleId = srModule.moduleId; this.stakingModuleAddress = srModule.stakingModuleAddress; this.moduleFee = srModule.moduleFee; @@ -21,6 +21,7 @@ export class SrModuleEntity implements StakingModule { this.type = srModule.type; this.active = srModule.active; this.nonce = nonce; + this.lastChangedBlockHash = lastChangedBlockHash; } @PrimaryKey() @@ -81,4 +82,8 @@ export class SrModuleEntity implements StakingModule { // nonce value @Property() nonce: number; + + // last changed block hash + @Property() + lastChangedBlockHash: string; } diff --git a/src/storage/sr-module.storage.ts b/src/storage/sr-module.storage.ts index 0bbecd61..4a4f9661 100644 --- a/src/storage/sr-module.storage.ts +++ b/src/storage/sr-module.storage.ts @@ -21,7 +21,7 @@ export class SRModuleStorageService { return await this.repository.findAll(); } - async upsert(srModule: StakingModule, nonce: number): Promise { + async upsert(srModule: StakingModule, nonce: number, lastChangedBlockHash: string): Promise { // Try to find an existing entity by moduleId or stakingModuleAddress let existingModule = await this.repository.findOne({ moduleId: srModule.moduleId, @@ -32,6 +32,7 @@ export class SRModuleStorageService { existingModule = new SrModuleEntity( { ...srModule, stakingModuleAddress: srModule.stakingModuleAddress.toLowerCase() }, nonce, + lastChangedBlockHash, ); } else { // If the entity exists, update its properties @@ -45,6 +46,7 @@ export class SRModuleStorageService { existingModule.exitedValidatorsCount = srModule.exitedValidatorsCount; existingModule.active = srModule.active; existingModule.nonce = nonce; + existingModule.lastChangedBlockHash = lastChangedBlockHash; } // Save the entity (either a new one or an updated one) From 92ecf447303ef2d6af0ccd4f1dd432107b48fb41 Mon Sep 17 00:00:00 2001 From: Eddort Date: Tue, 19 Dec 2023 13:58:51 +0100 Subject: [PATCH 05/48] fix: revert operatorsWereChanged logic --- src/common/registry/main/abstract-registry.ts | 19 +++---------------- src/common/registry/main/constants.ts | 1 - 2 files changed, 3 insertions(+), 17 deletions(-) diff --git a/src/common/registry/main/abstract-registry.ts b/src/common/registry/main/abstract-registry.ts index e0c3fc2b..99ebe7d5 100644 --- a/src/common/registry/main/abstract-registry.ts +++ b/src/common/registry/main/abstract-registry.ts @@ -14,7 +14,7 @@ import { RegistryOperator } from '../storage/operator.entity'; import { compareOperators } from '../utils/operator.utils'; -import { BLOCKS_OVERLAP, REGISTRY_GLOBAL_OPTIONS_TOKEN } from './constants'; +import { REGISTRY_GLOBAL_OPTIONS_TOKEN } from './constants'; import { RegistryOptions } from './interfaces/module.interface'; import { chunk } from '@lido-nestjs/utils'; import { RegistryKeyBatchFetchService } from '../fetch/key-batch.fetch'; @@ -72,22 +72,9 @@ export abstract class AbstractRegistryService { */ public async operatorsWereChanged( moduleAddress: string, - { - fromBlockNumber, - toBlockNumber, - }: { - fromBlockNumber?: number; - toBlockNumber: number; - }, + fromBlockNumber: number, + toBlockNumber: number, ): Promise { - if (fromBlockNumber === undefined) return true; - - if (fromBlockNumber > toBlockNumber) { - throw new Error(`invalid blocks range: ${fromBlockNumber} (fromBlockNumber) > ${toBlockNumber} (toBlockNumber)`); - } - // check how big the difference between the blocks is, if it exceeds, we should update the state anyway - if (toBlockNumber - fromBlockNumber > BLOCKS_OVERLAP) return true; - // rename return await this.operatorFetch.operatorsWereChanged(moduleAddress, fromBlockNumber, toBlockNumber); } diff --git a/src/common/registry/main/constants.ts b/src/common/registry/main/constants.ts index 8a6fcc14..17883f7b 100644 --- a/src/common/registry/main/constants.ts +++ b/src/common/registry/main/constants.ts @@ -1,2 +1 @@ export const REGISTRY_GLOBAL_OPTIONS_TOKEN = Symbol('registryGlobalOptions'); -export const BLOCKS_OVERLAP = 120; From 183cbbd7b2cbc412b2ac3ada106ef696951d258a Mon Sep 17 00:00:00 2001 From: Eddort Date: Tue, 19 Dec 2023 14:24:00 +0100 Subject: [PATCH 06/48] fix: tests --- .../registry/test/fetch/key.fetch.spec.ts | 29 ++++++++++++++++++- .../test/fetch/operator.fetch.spec.ts | 4 +-- .../registry/test/fixtures/db.fixture.ts | 4 +++ .../test/fixtures/operator.fixture.ts | 1 + .../test/utils/operator.utils.spec.ts | 2 ++ 5 files changed, 37 insertions(+), 3 deletions(-) diff --git a/src/common/registry/test/fetch/key.fetch.spec.ts b/src/common/registry/test/fetch/key.fetch.spec.ts index b23cbb58..6490b325 100644 --- a/src/common/registry/test/fetch/key.fetch.spec.ts +++ b/src/common/registry/test/fetch/key.fetch.spec.ts @@ -76,7 +76,34 @@ describe('Keys', () => { const result = await fetchService.fetch(address, expected.operatorIndex); expect(result).toEqual([expected]); - expect(mockCall).toBeCalledTimes(2); + expect(mockCall).toBeCalledTimes(3); + }); + + test('fetch all operator keys with reorg', async () => { + const expected = { operatorIndex: 1, index: 0, moduleAddress: address, ...key }; + + mockCall + .mockImplementationOnce(async () => { + const iface = new Interface(Registry__factory.abi); + return iface.encodeFunctionResult( + 'getNodeOperator', + operatorFields({ + ...operator, + moduleAddress: address, + totalSigningKeys: 1, + usedSigningKeys: 2, + finalizedUsedSigningKeys: 1, + }), + ); + }) + .mockImplementation(async () => { + const iface = new Interface(Registry__factory.abi); + return iface.encodeFunctionResult('getSigningKey', keyFields); + }); + const result = await fetchService.fetch(address, expected.operatorIndex); + console.log(result); + expect(result).toEqual([expected]); + expect(mockCall).toBeCalledTimes(3); }); test('fetch. fromIndex > toIndex', async () => { diff --git a/src/common/registry/test/fetch/operator.fetch.spec.ts b/src/common/registry/test/fetch/operator.fetch.spec.ts index 5ff8a939..174086c5 100644 --- a/src/common/registry/test/fetch/operator.fetch.spec.ts +++ b/src/common/registry/test/fetch/operator.fetch.spec.ts @@ -48,7 +48,7 @@ describe('Operators', () => { const result = await fetchService.fetchOne(address, expected.index); expect(result).toEqual(expected); - expect(mockCall).toBeCalledTimes(1); + expect(mockCall).toBeCalledTimes(2); }); test('fetch', async () => { @@ -62,7 +62,7 @@ describe('Operators', () => { const result = await fetchService.fetch(address, expectedFirst.index, expectedSecond.index + 1); expect(result).toEqual([expectedFirst, expectedSecond]); - expect(mockCall).toBeCalledTimes(2); + expect(mockCall).toBeCalledTimes(4); }); test('fetch all', async () => { diff --git a/src/common/registry/test/fixtures/db.fixture.ts b/src/common/registry/test/fixtures/db.fixture.ts index 7763870e..4d5f8d99 100644 --- a/src/common/registry/test/fixtures/db.fixture.ts +++ b/src/common/registry/test/fixtures/db.fixture.ts @@ -15,6 +15,7 @@ export const operators = [ stoppedValidators: 0, totalSigningKeys: 3, usedSigningKeys: 3, + finalizedUsedSigningKeys: 3, }, { index: 1, @@ -25,6 +26,7 @@ export const operators = [ stoppedValidators: 0, totalSigningKeys: 3, usedSigningKeys: 3, + finalizedUsedSigningKeys: 3, }, ]; @@ -99,6 +101,7 @@ export const newOperator = { stoppedValidators: 0, totalSigningKeys: 3, usedSigningKeys: 3, + finalizedUsedSigningKeys: 3, }; export const operatorWithDefaultsRecords = { @@ -110,4 +113,5 @@ export const operatorWithDefaultsRecords = { stoppedValidators: 0, totalSigningKeys: 0, usedSigningKeys: 0, + finalizedUsedSigningKeys: 0, }; diff --git a/src/common/registry/test/fixtures/operator.fixture.ts b/src/common/registry/test/fixtures/operator.fixture.ts index aa3189a5..c9d88b0d 100644 --- a/src/common/registry/test/fixtures/operator.fixture.ts +++ b/src/common/registry/test/fixtures/operator.fixture.ts @@ -9,6 +9,7 @@ export const operator = { stakingLimit: 1, usedSigningKeys: 2, totalSigningKeys: 3, + finalizedUsedSigningKeys: 2, }; export const operatorFields = (operator: Partial) => [ diff --git a/src/common/registry/test/utils/operator.utils.spec.ts b/src/common/registry/test/utils/operator.utils.spec.ts index 4e26c644..66f1a6b3 100644 --- a/src/common/registry/test/utils/operator.utils.spec.ts +++ b/src/common/registry/test/utils/operator.utils.spec.ts @@ -10,6 +10,7 @@ describe('Compare operators util', () => { stoppedValidators: 0, totalSigningKeys: 1, usedSigningKeys: 1, + finalizedUsedSigningKeys: 1, moduleAddress: '0x9D4AF1Ee19Dad8857db3a45B0374c81c8A1C6320', }; @@ -22,6 +23,7 @@ describe('Compare operators util', () => { stoppedValidators: 0, totalSigningKeys: 2, usedSigningKeys: 2, + finalizedUsedSigningKeys: 2, moduleAddress: '0x9D4AF1Ee19Dad8857db3a45B0374c81c8A1C6320', }; From cca8041d1f7fc5308103e5cf82460ef5e7ffb768 Mon Sep 17 00:00:00 2001 From: Eddort Date: Wed, 20 Dec 2023 10:47:20 +0100 Subject: [PATCH 07/48] feat: add data to endpoints --- src/http/common/entities/el-block-snapshot.ts | 7 +++++++ src/http/common/entities/sr-module.ts | 7 +++++++ 2 files changed, 14 insertions(+) diff --git a/src/http/common/entities/el-block-snapshot.ts b/src/http/common/entities/el-block-snapshot.ts index 6abd34e3..1464b785 100644 --- a/src/http/common/entities/el-block-snapshot.ts +++ b/src/http/common/entities/el-block-snapshot.ts @@ -6,6 +6,7 @@ export class ELBlockSnapshot implements ElMetaEntity { this.blockNumber = meta.blockNumber; this.blockHash = meta.blockHash; this.timestamp = meta.timestamp; + this.lastChangedBlockHash = meta.lastChangedBlockHash; } @ApiProperty({ @@ -25,4 +26,10 @@ export class ELBlockSnapshot implements ElMetaEntity { description: 'Block timestamp', }) timestamp: number; + + @ApiProperty({ + required: true, + description: 'Last changed block hash — used to determine that a change has been made to this block', + }) + lastChangedBlockHash: string; } diff --git a/src/http/common/entities/sr-module.ts b/src/http/common/entities/sr-module.ts index fb313899..f399a250 100644 --- a/src/http/common/entities/sr-module.ts +++ b/src/http/common/entities/sr-module.ts @@ -17,6 +17,7 @@ export class StakingModuleResponse implements Omit Date: Wed, 20 Dec 2023 11:01:51 +0100 Subject: [PATCH 08/48] fix: fixtures and entity types --- src/http/common/entities/operator.ts | 2 +- src/http/db.fixtures.ts | 4 ++++ src/http/module.fixture.ts | 2 ++ 3 files changed, 7 insertions(+), 1 deletion(-) diff --git a/src/http/common/entities/operator.ts b/src/http/common/entities/operator.ts index 38acdef0..d8115c40 100644 --- a/src/http/common/entities/operator.ts +++ b/src/http/common/entities/operator.ts @@ -3,7 +3,7 @@ import { ApiProperty } from '@nestjs/swagger'; import { RegistryOperator } from '../../../common/registry'; import { addressToChecksum } from '../utils'; -export class Operator implements RegistryOperator { +export class Operator implements Omit { constructor(operator: RegistryOperator) { this.name = operator.name; this.rewardAddress = operator.rewardAddress; diff --git a/src/http/db.fixtures.ts b/src/http/db.fixtures.ts index 18240eff..0a6c0d41 100644 --- a/src/http/db.fixtures.ts +++ b/src/http/db.fixtures.ts @@ -228,6 +228,7 @@ export const operatorOneCurated: RegistryOperator = { usedSigningKeys: 2, totalSigningKeys: 3, moduleAddress: curatedModule.stakingModuleAddress, + finalizedUsedSigningKeys: 2, }; export const operatorTwoCurated: RegistryOperator = { @@ -240,6 +241,7 @@ export const operatorTwoCurated: RegistryOperator = { usedSigningKeys: 2, totalSigningKeys: 3, moduleAddress: curatedModule.stakingModuleAddress, + finalizedUsedSigningKeys: 2, }; export const operatorOneDvt: RegistryOperator = { @@ -252,6 +254,7 @@ export const operatorOneDvt: RegistryOperator = { usedSigningKeys: 2, totalSigningKeys: 3, moduleAddress: dvtModule.stakingModuleAddress, + finalizedUsedSigningKeys: 2, }; export const operatorTwoDvt: RegistryOperator = { @@ -264,6 +267,7 @@ export const operatorTwoDvt: RegistryOperator = { usedSigningKeys: 2, totalSigningKeys: 3, moduleAddress: dvtModule.stakingModuleAddress, + finalizedUsedSigningKeys: 2, }; export const operators = [operatorOneCurated, operatorTwoCurated, operatorOneDvt, operatorTwoDvt]; diff --git a/src/http/module.fixture.ts b/src/http/module.fixture.ts index be18ec96..1f5e3af1 100644 --- a/src/http/module.fixture.ts +++ b/src/http/module.fixture.ts @@ -17,6 +17,7 @@ export const curatedModuleResp: StakingModuleResponse = { nonce: 1, exitedValidatorsCount: 0, active: true, + lastChangedBlockHash: '', }; export const dvtModuleAddressWithChecksum = '0x0165878A594ca255338adfa4d48449f69242Eb8F'; @@ -35,4 +36,5 @@ export const dvtModuleResp: StakingModuleResponse = { nonce: 1, exitedValidatorsCount: 0, active: true, + lastChangedBlockHash: '', }; From 3fa909b00ab0b0055f7f8bfa528f5db24d4bf4ce Mon Sep 17 00:00:00 2001 From: Eddort Date: Wed, 20 Dec 2023 11:18:20 +0100 Subject: [PATCH 09/48] fix: operators fetch spec --- src/common/registry/test/fetch/key.fetch.spec.ts | 1 - src/common/registry/test/fetch/operator.fetch.spec.ts | 5 ++--- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/src/common/registry/test/fetch/key.fetch.spec.ts b/src/common/registry/test/fetch/key.fetch.spec.ts index 6490b325..7fee5049 100644 --- a/src/common/registry/test/fetch/key.fetch.spec.ts +++ b/src/common/registry/test/fetch/key.fetch.spec.ts @@ -101,7 +101,6 @@ describe('Keys', () => { return iface.encodeFunctionResult('getSigningKey', keyFields); }); const result = await fetchService.fetch(address, expected.operatorIndex); - console.log(result); expect(result).toEqual([expected]); expect(mockCall).toBeCalledTimes(3); }); diff --git a/src/common/registry/test/fetch/operator.fetch.spec.ts b/src/common/registry/test/fetch/operator.fetch.spec.ts index 174086c5..33948828 100644 --- a/src/common/registry/test/fetch/operator.fetch.spec.ts +++ b/src/common/registry/test/fetch/operator.fetch.spec.ts @@ -73,16 +73,15 @@ describe('Operators', () => { const iface = new Interface(Registry__factory.abi); return iface.encodeFunctionResult('getNodeOperatorsCount', [1]); }) - .mockImplementationOnce(async () => { + .mockImplementation(async () => { const iface = new Interface(Registry__factory.abi); operator['moduleAddress'] = address; - // operatorFields(operator); return iface.encodeFunctionResult('getNodeOperator', operatorFields(operator)); }); const result = await fetchService.fetch(address); expect(result).toEqual([expected]); - expect(mockCall).toBeCalledTimes(2); + expect(mockCall).toBeCalledTimes(3); }); test('fetch. fromIndex > toIndex', async () => { From 138e9c301cbd1686af5ada860c442ac51f2fd67a Mon Sep 17 00:00:00 2001 From: Eddort Date: Wed, 20 Dec 2023 12:05:02 +0100 Subject: [PATCH 10/48] fix: reorg detection --- src/jobs/keys-update/keys-update.service.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/jobs/keys-update/keys-update.service.ts b/src/jobs/keys-update/keys-update.service.ts index 33388dba..552c64a4 100644 --- a/src/jobs/keys-update/keys-update.service.ts +++ b/src/jobs/keys-update/keys-update.service.ts @@ -251,7 +251,7 @@ export class KeysUpdateService { if (currentBlock.parentHash === prevBlock.hash) return false; // TODO: different hash but same number - if (currentBlock.number === prevBlock.number) return true; + // if (currentBlock.number === prevBlock.number) return true; const blocks = await Promise.all( range(prevBlock.number, currentBlock.number).map(async (bNumber) => { @@ -264,11 +264,11 @@ export class KeysUpdateService { const currentBlock = blocks[i]; if (currentBlock.parentHash !== previousBlock.hash) { - return false; + return true; } } - return true; + return false; } public isTooMuchDiffBetweenBlocks(prevBlockNumber: number, currentBlockNumber: number) { From 39a92e90240f869ed052052b1a1a9750c8acbdc5 Mon Sep 17 00:00:00 2001 From: Eddort Date: Wed, 20 Dec 2023 12:05:12 +0100 Subject: [PATCH 11/48] feat: add migration --- src/migrations/Migration20231220104403.ts | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) create mode 100644 src/migrations/Migration20231220104403.ts diff --git a/src/migrations/Migration20231220104403.ts b/src/migrations/Migration20231220104403.ts new file mode 100644 index 00000000..00376807 --- /dev/null +++ b/src/migrations/Migration20231220104403.ts @@ -0,0 +1,19 @@ +import { Migration } from '@mikro-orm/migrations'; + +export class Migration20231220104403 extends Migration { + async up(): Promise { + this.addSql('alter table "el_meta_entity" add column "last_changed_block_hash" varchar(66) not null;'); + + this.addSql('alter table "registry_operator" add column "finalized_used_signing_keys" int not null;'); + + this.addSql('alter table "sr_module_entity" add column "last_changed_block_hash" varchar(255) not null;'); + } + + async down(): Promise { + this.addSql('alter table "el_meta_entity" drop column "last_changed_block_hash";'); + + this.addSql('alter table "registry_operator" drop column "finalized_used_signing_keys";'); + + this.addSql('alter table "sr_module_entity" drop column "last_changed_block_hash";'); + } +} From 7b018f3ab2206806bb802bd84e90005d64616e32 Mon Sep 17 00:00:00 2001 From: Eddort Date: Wed, 20 Dec 2023 14:50:22 +0100 Subject: [PATCH 12/48] feat: moduleAddresses filter --- src/http/common/entities/key-query.ts | 6 ++++++ src/http/keys/keys.service.ts | 4 +++- src/staking-router-modules/staking-router.service.ts | 10 ++++++---- src/storage/sr-module.storage.ts | 5 +++++ 4 files changed, 20 insertions(+), 5 deletions(-) diff --git a/src/http/common/entities/key-query.ts b/src/http/common/entities/key-query.ts index f9c748ce..5bbb9f9d 100644 --- a/src/http/common/entities/key-query.ts +++ b/src/http/common/entities/key-query.ts @@ -37,4 +37,10 @@ export class KeyQuery { @Type(() => Number) @IsOptional() operatorIndex?: number; + + @ApiProperty({ isArray: true, type: String, required: false, description: 'Module address list' }) + @Transform(({ value }) => (Array.isArray(value) ? value : Array(value))) + @Transform(({ value }) => value.map((v) => v.toLowerCase())) + @IsOptional() + moduleAddresses!: string[]; } diff --git a/src/http/keys/keys.service.ts b/src/http/keys/keys.service.ts index 704a0ca9..2e27b3c3 100644 --- a/src/http/keys/keys.service.ts +++ b/src/http/keys/keys.service.ts @@ -18,7 +18,9 @@ export class KeysService { async get( filters: KeyQuery, ): Promise<{ keysGenerators: AsyncGenerator[]; meta: { elBlockSnapshot: ELBlockSnapshot } }> { - const { stakingModules, elBlockSnapshot } = await this.stakingRouterService.getStakingModulesAndMeta(); + const { stakingModules, elBlockSnapshot } = await this.stakingRouterService.getStakingModulesAndMeta( + filters.moduleAddresses, + ); const keysGenerators: AsyncGenerator[] = []; for (const module of stakingModules) { diff --git a/src/staking-router-modules/staking-router.service.ts b/src/staking-router-modules/staking-router.service.ts index af28e02f..89659d43 100644 --- a/src/staking-router-modules/staking-router.service.ts +++ b/src/staking-router-modules/staking-router.service.ts @@ -26,8 +26,10 @@ export class StakingRouterService { * Method for reading staking modules from database * @returns Staking module list from database */ - public async getStakingModules(): Promise { - const srModules = await this.srModulesStorage.findAll(); + public async getStakingModules(stakingModuleAddresses?: string[]): Promise { + const srModules = stakingModuleAddresses + ? await this.srModulesStorage.findByAddresses(stakingModuleAddresses) + : await this.srModulesStorage.findAll(); return srModules; } @@ -61,13 +63,13 @@ export class StakingRouterService { * Helper method for getting staking module list and execution layer meta * @returns Staking modules list and execution layer meta */ - public async getStakingModulesAndMeta(): Promise<{ + public async getStakingModulesAndMeta(stakingModuleAddresses?: string[]): Promise<{ stakingModules: SrModuleEntity[]; elBlockSnapshot: ELBlockSnapshot; }> { const { stakingModules, elBlockSnapshot } = await this.entityManager.transactional( async () => { - const stakingModules = await this.getStakingModules(); + const stakingModules = await this.getStakingModules(stakingModuleAddresses); if (stakingModules.length === 0) { this.logger.warn("No staking modules in list. Maybe didn't fetched from SR yet"); diff --git a/src/storage/sr-module.storage.ts b/src/storage/sr-module.storage.ts index 4a4f9661..85bc1a87 100644 --- a/src/storage/sr-module.storage.ts +++ b/src/storage/sr-module.storage.ts @@ -21,6 +21,11 @@ export class SRModuleStorageService { return await this.repository.findAll(); } + /** find all keys */ + async findByAddresses(stakingModuleAddresses: string[]): Promise { + return await this.repository.find({ stakingModuleAddress: { $in: stakingModuleAddresses } }); + } + async upsert(srModule: StakingModule, nonce: number, lastChangedBlockHash: string): Promise { // Try to find an existing entity by moduleId or stakingModuleAddress let existingModule = await this.repository.findOne({ From edf5c222bd2a238a4ab7cd5dd2dc87f7c2ae8764 Mon Sep 17 00:00:00 2001 From: Eddort Date: Wed, 20 Dec 2023 19:06:42 +0100 Subject: [PATCH 13/48] refactor: split update function into two --- src/jobs/keys-update/keys-update.service.ts | 264 ++++++++++++-------- 1 file changed, 165 insertions(+), 99 deletions(-) diff --git a/src/jobs/keys-update/keys-update.service.ts b/src/jobs/keys-update/keys-update.service.ts index 552c64a4..a2d9aa91 100644 --- a/src/jobs/keys-update/keys-update.service.ts +++ b/src/jobs/keys-update/keys-update.service.ts @@ -14,7 +14,8 @@ import { SRModuleStorageService } from 'storage/sr-module.storage'; import { IsolationLevel } from '@mikro-orm/core'; import { PrometheusService } from 'common/prometheus'; import { SrModuleEntity } from 'storage/sr-module.entity'; -import { StakingModule } from 'staking-router-modules/interfaces/staking-module.interface'; +import { StakingModule, StakingModuleInterface } from 'staking-router-modules/interfaces/staking-module.interface'; +import { ElMetaEntity } from 'storage/el-meta.entity'; const MAX_BLOCKS_OVERLAP = 30; @@ -27,6 +28,21 @@ class KeyOutdatedError extends Error { } } +interface UpdaterPayload { + currElMeta: { + number: number; + hash: string; + timestamp: number; + }; + prevElMeta: ElMetaEntity | null; + contractModules: StakingModule[]; +} + +interface UpdaterState { + lastChangedBlockHash: string; + isReorgDetected: boolean; +} + @Injectable() export class KeysUpdateService { constructor( @@ -143,101 +159,7 @@ export class KeysUpdateService { await this.entityManager.transactional( async () => { - const prevBlockHash = prevElMeta?.blockHash; - const currentBlockHash = currElMeta.hash; - - let lastChangedBlockHash = prevBlockHash || currentBlockHash; - - let isReorgDetected = false; - - for (const contractModule of contractModules) { - const { stakingModuleAddress } = contractModule; - - // Find implementation for staking module - const moduleInstance = this.stakingRouterService.getStakingRouterModuleImpl(contractModule.type); - // Read current nonce from contract - const currNonce = await moduleInstance.getCurrentNonce(stakingModuleAddress, currentBlockHash); - // Read module in storage - const moduleInStorage = await this.srModulesStorage.findOneById(contractModule.moduleId); - const prevNonce = moduleInStorage?.nonce; - - this.logger.log(`Nonce previous value: ${prevNonce}, nonce current value: ${currNonce}`); - - if (!prevElMeta) { - this.logger.log('No past state found, start indexing', { stakingModuleAddress, currentBlockHash }); - - await moduleInstance.update(stakingModuleAddress, currentBlockHash); - await this.srModulesStorage.upsert(contractModule, currNonce, currentBlockHash); - lastChangedBlockHash = currentBlockHash; - continue; - } - - if (prevNonce !== currNonce) { - this.logger.log('Nonce has been changed, start indexing', { - stakingModuleAddress, - currentBlockHash, - prevNonce, - currNonce, - }); - - await moduleInstance.update(stakingModuleAddress, currentBlockHash); - await this.srModulesStorage.upsert(contractModule, currNonce, currentBlockHash); - lastChangedBlockHash = currentBlockHash; - continue; - } - - if (this.isTooMuchDiffBetweenBlocks(prevElMeta.blockNumber, currElMeta.number)) { - this.logger.log('Too much difference between the blocks, start indexing', { - stakingModuleAddress, - currentBlockHash, - }); - - await moduleInstance.update(stakingModuleAddress, currentBlockHash); - await this.srModulesStorage.upsert(contractModule, currNonce, currentBlockHash); - lastChangedBlockHash = currentBlockHash; - continue; - } - - // calculate once per iteration - // no need to recheck each module separately - isReorgDetected = isReorgDetected ? true : await this.isReorgDetected(prevElMeta.blockHash, currentBlockHash); - - if (isReorgDetected) { - this.logger.log('Reorg detected, start indexing', { stakingModuleAddress, currentBlockHash }); - - await moduleInstance.update(stakingModuleAddress, currentBlockHash); - await this.srModulesStorage.upsert(contractModule, currNonce, currentBlockHash); - lastChangedBlockHash = currentBlockHash; - continue; - } - - if ( - prevElMeta.blockNumber < currElMeta.number && - (await moduleInstance.operatorsWereChanged( - contractModule.stakingModuleAddress, - prevElMeta.blockNumber + 1, - currElMeta.number, - )) - ) { - this.logger.log('Update operator events happened, need to update operators', { - stakingModuleAddress, - currentBlockHash, - }); - - await moduleInstance.updateOperators(stakingModuleAddress, currentBlockHash); - await this.srModulesStorage.upsert(contractModule, currNonce, currentBlockHash); - lastChangedBlockHash = currentBlockHash; - continue; - } - - this.logger.log('No changes have been detected in the module, indexing is not required', { - stakingModuleAddress, - currentBlockHash, - }); - } - - // Update EL meta in db - await this.elMetaStorage.update({ ...currElMeta, lastChangedBlockHash }); + await this.updateStakingModules({ currElMeta, prevElMeta, contractModules }); }, { isolationLevel: IsolationLevel.READ_COMMITTED }, ); @@ -245,13 +167,156 @@ export class KeysUpdateService { return currElMeta; } - public async isReorgDetected(prevBlockHash: string, currentBlockHash: string) { + public async updateStakingModules(updaterPayload: UpdaterPayload): Promise { + const { prevElMeta, currElMeta, contractModules } = updaterPayload; + const prevBlockHash = prevElMeta?.blockHash; + const currentBlockHash = currElMeta.hash; + + const updaterState: UpdaterState = { + lastChangedBlockHash: prevBlockHash || currentBlockHash, + isReorgDetected: false, + }; + + for (const contractModule of contractModules) { + const { stakingModuleAddress } = contractModule; + + // Find implementation for staking module + const moduleInstance = this.stakingRouterService.getStakingRouterModuleImpl(contractModule.type); + // Read current nonce from contract + const currNonce = await moduleInstance.getCurrentNonce(stakingModuleAddress, currentBlockHash); + // Read module in storage + const moduleInStorage = await this.srModulesStorage.findOneById(contractModule.moduleId); + const prevNonce = moduleInStorage?.nonce; + + this.logger.log(`Nonce previous value: ${prevNonce}, nonce current value: ${currNonce}`); + + if (!prevBlockHash) { + this.logger.log('No past state found, start indexing', { stakingModuleAddress, currentBlockHash }); + + await this.updateStakingModule( + updaterState, + moduleInstance, + contractModule, + stakingModuleAddress, + currNonce, + currentBlockHash, + ); + continue; + } + + if (prevNonce !== currNonce) { + this.logger.log('Nonce has been changed, start indexing', { + stakingModuleAddress, + currentBlockHash, + prevNonce, + currNonce, + }); + + await this.updateStakingModule( + updaterState, + moduleInstance, + contractModule, + stakingModuleAddress, + currNonce, + currentBlockHash, + ); + continue; + } + + if (this.isTooMuchDiffBetweenBlocks(prevElMeta.blockNumber, currElMeta.number)) { + this.logger.log('Too much difference between the blocks, start indexing', { + stakingModuleAddress, + currentBlockHash, + }); + + await this.updateStakingModule( + updaterState, + moduleInstance, + contractModule, + stakingModuleAddress, + currNonce, + currentBlockHash, + ); + continue; + } + + if (await this.isReorgDetected(updaterState, prevBlockHash, currentBlockHash)) { + this.logger.log('Reorg detected, start indexing', { stakingModuleAddress, currentBlockHash }); + + await this.updateStakingModule( + updaterState, + moduleInstance, + contractModule, + stakingModuleAddress, + currNonce, + currentBlockHash, + ); + continue; + } + + if ( + prevElMeta.blockNumber < currElMeta.number && + (await moduleInstance.operatorsWereChanged( + contractModule.stakingModuleAddress, + prevElMeta.blockNumber + 1, + currElMeta.number, + )) + ) { + this.logger.log('Update operator events happened, need to update operators', { + stakingModuleAddress, + currentBlockHash, + }); + + await this.updateStakingModule( + updaterState, + moduleInstance, + contractModule, + stakingModuleAddress, + currNonce, + currentBlockHash, + ); + continue; + } + + this.logger.log('No changes have been detected in the module, indexing is not required', { + stakingModuleAddress, + currentBlockHash, + }); + } + + // Update EL meta in db + await this.elMetaStorage.update({ ...currElMeta, lastChangedBlockHash: updaterState.lastChangedBlockHash }); + } + + public async updateStakingModule( + updaterState: UpdaterState, + moduleInstance: StakingModuleInterface, + contractModule: StakingModule, + stakingModuleAddress: string, + currNonce: number, + currentBlockHash: string, + ) { + await moduleInstance.update(stakingModuleAddress, currentBlockHash); + await this.srModulesStorage.upsert(contractModule, currNonce, currentBlockHash); + updaterState.lastChangedBlockHash = currentBlockHash; + } + + public async isReorgDetected(updaterState: UpdaterState, prevBlockHash: string, currentBlockHash: string) { + // calculate once per iteration + // no need to recheck each module separately + if (updaterState.isReorgDetected) { + return true; + } + const currentBlock = await this.executionProvider.getFullBlock(currentBlockHash); const prevBlock = await this.executionProvider.getFullBlock(prevBlockHash); if (currentBlock.parentHash === prevBlock.hash) return false; - // TODO: different hash but same number - // if (currentBlock.number === prevBlock.number) return true; + // different hash but same number + if (currentBlock.number === prevBlock.number) { + updaterState.isReorgDetected = true; + return true; + } const blocks = await Promise.all( range(prevBlock.number, currentBlock.number).map(async (bNumber) => { @@ -264,6 +329,7 @@ export class KeysUpdateService { const currentBlock = blocks[i]; if (currentBlock.parentHash !== previousBlock.hash) { + updaterState.isReorgDetected = true; return true; } } From c7bb4e3c1966cb63b204400741d7f8fb898709d6 Mon Sep 17 00:00:00 2001 From: Eddort Date: Wed, 20 Dec 2023 19:08:19 +0100 Subject: [PATCH 14/48] refactor: move interfaces to a separate file --- src/jobs/keys-update/keys-update.interfaces.ts | 17 +++++++++++++++++ src/jobs/keys-update/keys-update.service.ts | 17 +---------------- 2 files changed, 18 insertions(+), 16 deletions(-) create mode 100644 src/jobs/keys-update/keys-update.interfaces.ts diff --git a/src/jobs/keys-update/keys-update.interfaces.ts b/src/jobs/keys-update/keys-update.interfaces.ts new file mode 100644 index 00000000..d185a8f2 --- /dev/null +++ b/src/jobs/keys-update/keys-update.interfaces.ts @@ -0,0 +1,17 @@ +import { StakingModule } from 'staking-router-modules/interfaces/staking-module.interface'; +import { ElMetaEntity } from 'storage/el-meta.entity'; + +export interface UpdaterPayload { + currElMeta: { + number: number; + hash: string; + timestamp: number; + }; + prevElMeta: ElMetaEntity | null; + contractModules: StakingModule[]; +} + +export interface UpdaterState { + lastChangedBlockHash: string; + isReorgDetected: boolean; +} diff --git a/src/jobs/keys-update/keys-update.service.ts b/src/jobs/keys-update/keys-update.service.ts index a2d9aa91..d5d0b8ea 100644 --- a/src/jobs/keys-update/keys-update.service.ts +++ b/src/jobs/keys-update/keys-update.service.ts @@ -15,7 +15,7 @@ import { IsolationLevel } from '@mikro-orm/core'; import { PrometheusService } from 'common/prometheus'; import { SrModuleEntity } from 'storage/sr-module.entity'; import { StakingModule, StakingModuleInterface } from 'staking-router-modules/interfaces/staking-module.interface'; -import { ElMetaEntity } from 'storage/el-meta.entity'; +import { UpdaterPayload, UpdaterState } from './keys-update.interfaces'; const MAX_BLOCKS_OVERLAP = 30; @@ -28,21 +28,6 @@ class KeyOutdatedError extends Error { } } -interface UpdaterPayload { - currElMeta: { - number: number; - hash: string; - timestamp: number; - }; - prevElMeta: ElMetaEntity | null; - contractModules: StakingModule[]; -} - -interface UpdaterState { - lastChangedBlockHash: string; - isReorgDetected: boolean; -} - @Injectable() export class KeysUpdateService { constructor( From 7e52c4c72a0475473f822ca2aa2babe99083c4f2 Mon Sep 17 00:00:00 2001 From: Eddort Date: Wed, 20 Dec 2023 22:09:10 +0100 Subject: [PATCH 15/48] test: processing of all indexing cases --- src/jobs/keys-update/keys-update.constants.ts | 1 + src/jobs/keys-update/keys-update.fixtures.ts | 34 +++ .../keys-update/keys-update.interfaces.ts | 7 +- .../keys-update/test/update-cases.spec.ts | 165 +++++++++++++++ src/jobs/keys-update/updater.service.ts | 194 ++++++++++++++++++ 5 files changed, 399 insertions(+), 2 deletions(-) create mode 100644 src/jobs/keys-update/keys-update.constants.ts create mode 100644 src/jobs/keys-update/keys-update.fixtures.ts create mode 100644 src/jobs/keys-update/test/update-cases.spec.ts create mode 100644 src/jobs/keys-update/updater.service.ts diff --git a/src/jobs/keys-update/keys-update.constants.ts b/src/jobs/keys-update/keys-update.constants.ts new file mode 100644 index 00000000..4ce41d26 --- /dev/null +++ b/src/jobs/keys-update/keys-update.constants.ts @@ -0,0 +1 @@ +export const MAX_BLOCKS_OVERLAP = 30; diff --git a/src/jobs/keys-update/keys-update.fixtures.ts b/src/jobs/keys-update/keys-update.fixtures.ts new file mode 100644 index 00000000..f5ef37d3 --- /dev/null +++ b/src/jobs/keys-update/keys-update.fixtures.ts @@ -0,0 +1,34 @@ +import { StakingModule } from 'staking-router-modules/interfaces/staking-module.interface'; + +export const stakingModuleFixture: StakingModule = { + moduleId: 1, + stakingModuleAddress: '0x123456789abcdef', + moduleFee: 0.02, + treasuryFee: 0.01, + targetShare: 500, + status: 1, + name: 'Staking Module 1', + lastDepositAt: Date.now() - 86400000, // 24 hours ago + lastDepositBlock: 12345, + exitedValidatorsCount: 10, + type: 'curated', + active: true, +}; + +export const stakingModuleFixtures: StakingModule[] = [ + stakingModuleFixture, + { + moduleId: 2, + stakingModuleAddress: '0x987654321fedcba', + moduleFee: 0.01, + treasuryFee: 0.005, + targetShare: 750, + status: 0, + name: 'Staking Module 2', + lastDepositAt: Date.now() - 172800000, // 48 hours ago + lastDepositBlock: 23456, + exitedValidatorsCount: 5, + type: 'dvt', + active: false, + }, +]; diff --git a/src/jobs/keys-update/keys-update.interfaces.ts b/src/jobs/keys-update/keys-update.interfaces.ts index d185a8f2..63444c06 100644 --- a/src/jobs/keys-update/keys-update.interfaces.ts +++ b/src/jobs/keys-update/keys-update.interfaces.ts @@ -1,5 +1,4 @@ import { StakingModule } from 'staking-router-modules/interfaces/staking-module.interface'; -import { ElMetaEntity } from 'storage/el-meta.entity'; export interface UpdaterPayload { currElMeta: { @@ -7,7 +6,11 @@ export interface UpdaterPayload { hash: string; timestamp: number; }; - prevElMeta: ElMetaEntity | null; + prevElMeta: { + blockNumber: number; + blockHash: string; + timestamp: number; + } | null; contractModules: StakingModule[]; } diff --git a/src/jobs/keys-update/test/update-cases.spec.ts b/src/jobs/keys-update/test/update-cases.spec.ts new file mode 100644 index 00000000..27305007 --- /dev/null +++ b/src/jobs/keys-update/test/update-cases.spec.ts @@ -0,0 +1,165 @@ +import { LOGGER_PROVIDER } from '@lido-nestjs/logger'; +import { Test, TestingModule } from '@nestjs/testing'; +import { ExecutionProviderService } from 'common/execution-provider'; +import { StakingRouterService } from 'staking-router-modules/staking-router.service'; +import { ElMetaStorageService } from 'storage/el-meta.storage'; +import { SRModuleStorageService } from 'storage/sr-module.storage'; +import { stakingModuleFixture, stakingModuleFixtures } from '../keys-update.fixtures'; +import { KeysUpdaterService } from '../updater.service'; + +describe('YourService', () => { + let updaterService: KeysUpdaterService; + let stakingRouterService: StakingRouterService; + let sRModuleStorageService: SRModuleStorageService; + let loggerService: { log: jest.Mock }; + let executionProviderService: ExecutionProviderService; + + beforeEach(async () => { + const module: TestingModule = await Test.createTestingModule({ + providers: [ + { + provide: LOGGER_PROVIDER, + useValue: { + log: jest.fn(), + }, + }, + { + provide: StakingRouterService, + useValue: { + getStakingRouterModuleImpl: () => ({ + getCurrentNonce() { + return 1; + }, + }), + }, + }, + { + provide: ElMetaStorageService, + useValue: { + update: jest.fn(), + }, + }, + { + provide: ExecutionProviderService, + useValue: { + getFullBlock: jest.fn(), + }, + }, + { + provide: SRModuleStorageService, + useValue: { + findOneById: jest.fn(), + upsert: jest.fn(), + }, + }, + KeysUpdaterService, + ], + }).compile(); + + updaterService = module.get(KeysUpdaterService); + stakingRouterService = module.get(StakingRouterService); + sRModuleStorageService = module.get(SRModuleStorageService); + executionProviderService = module.get(ExecutionProviderService); + loggerService = module.get(LOGGER_PROVIDER); + }); + + it('should be defined', () => { + expect(updaterService).toBeDefined(); + }); + + it('No past state found', async () => { + const mockUpdate = jest.spyOn(updaterService, 'updateStakingModule').mockImplementation(); + await updaterService.updateStakingModules({ + currElMeta: { number: 1, hash: '0x1', timestamp: 1 }, + prevElMeta: null, + contractModules: [stakingModuleFixture], + }); + + expect(mockUpdate).toBeCalledTimes(1); + expect(loggerService.log.mock.calls[1][0]).toBe('No past state found, start indexing'); + }); + + it('More than 1 module processed', async () => { + const mockUpdate = jest.spyOn(updaterService, 'updateStakingModule').mockImplementation(); + await updaterService.updateStakingModules({ + currElMeta: { number: 1, hash: '0x1', timestamp: 1 }, + prevElMeta: null, + contractModules: stakingModuleFixtures, + }); + + expect(mockUpdate).toBeCalledTimes(2); + }); + + it('Nonce has been changed', async () => { + const mockUpdate = jest.spyOn(updaterService, 'updateStakingModule').mockImplementation(); + + jest.spyOn(stakingRouterService, 'getStakingRouterModuleImpl').mockImplementation( + () => + ({ + getCurrentNonce() { + return 1; + }, + } as any), + ); + + jest.spyOn(sRModuleStorageService, 'findOneById').mockImplementation( + () => + ({ + nonce: 0, + } as any), + ); + + await updaterService.updateStakingModules({ + currElMeta: { number: 2, hash: '0x1', timestamp: 1 }, + prevElMeta: { blockNumber: 1, blockHash: '0x2', timestamp: 1 }, + contractModules: stakingModuleFixtures, + }); + + expect(mockUpdate).toBeCalledTimes(2); + expect(loggerService.log.mock.calls[1][0]).toBe('Nonce has been changed, start indexing'); + }); + + it('Too much difference between the blocks', async () => { + const mockUpdate = jest.spyOn(updaterService, 'updateStakingModule').mockImplementation(); + + jest.spyOn(sRModuleStorageService, 'findOneById').mockImplementation( + () => + ({ + nonce: 1, + } as any), + ); + + await updaterService.updateStakingModules({ + currElMeta: { number: 100, hash: '0x1', timestamp: 1 }, + prevElMeta: { blockNumber: 2, blockHash: '0x2', timestamp: 1 }, + contractModules: stakingModuleFixtures, + }); + + expect(mockUpdate).toBeCalledTimes(2); + expect(loggerService.log.mock.calls[1][0]).toBe('Too much difference between the blocks, start indexing'); + }); + + it('Reorg detected', async () => { + const mockUpdate = jest.spyOn(updaterService, 'updateStakingModule').mockImplementation(); + + jest.spyOn(sRModuleStorageService, 'findOneById').mockImplementation( + () => + ({ + nonce: 1, + } as any), + ); + + jest + .spyOn(executionProviderService, 'getFullBlock') + .mockImplementation(async () => ({ number: 2, hash: '0x2', timestamp: 1, parentHash: '0x1' } as any)); + + await updaterService.updateStakingModules({ + currElMeta: { number: 2, hash: '0x2', timestamp: 1 }, + prevElMeta: { blockNumber: 2, blockHash: '0x1', timestamp: 1 }, + contractModules: stakingModuleFixtures, + }); + + expect(mockUpdate).toBeCalledTimes(2); + expect(loggerService.log.mock.calls[1][0]).toBe('Reorg detected, start indexing'); + }); +}); diff --git a/src/jobs/keys-update/updater.service.ts b/src/jobs/keys-update/updater.service.ts new file mode 100644 index 00000000..4cd847f4 --- /dev/null +++ b/src/jobs/keys-update/updater.service.ts @@ -0,0 +1,194 @@ +import { Inject, Injectable } from '@nestjs/common'; +import { range } from '@lido-nestjs/utils'; +import { LOGGER_PROVIDER, LoggerService } from 'common/logger'; +import { StakingRouterService } from 'staking-router-modules/staking-router.service'; +import { ElMetaStorageService } from 'storage/el-meta.storage'; +import { ExecutionProviderService } from 'common/execution-provider'; +import { SRModuleStorageService } from 'storage/sr-module.storage'; +import { StakingModule, StakingModuleInterface } from 'staking-router-modules/interfaces/staking-module.interface'; +import { UpdaterPayload, UpdaterState } from './keys-update.interfaces'; +import { MAX_BLOCKS_OVERLAP } from './keys-update.constants'; + +@Injectable() +export class KeysUpdaterService { + constructor( + @Inject(LOGGER_PROVIDER) protected readonly logger: LoggerService, + protected readonly stakingRouterService: StakingRouterService, + protected readonly elMetaStorage: ElMetaStorageService, + protected readonly executionProvider: ExecutionProviderService, + protected readonly srModulesStorage: SRModuleStorageService, + ) {} + public async updateStakingModules(updaterPayload: UpdaterPayload): Promise { + const { prevElMeta, currElMeta, contractModules } = updaterPayload; + const prevBlockHash = prevElMeta?.blockHash; + const currentBlockHash = currElMeta.hash; + + const updaterState: UpdaterState = { + lastChangedBlockHash: prevBlockHash || currentBlockHash, + isReorgDetected: false, + }; + + for (const contractModule of contractModules) { + const { stakingModuleAddress } = contractModule; + + // Find implementation for staking module + const moduleInstance = this.stakingRouterService.getStakingRouterModuleImpl(contractModule.type); + // Read current nonce from contract + const currNonce = await moduleInstance.getCurrentNonce(stakingModuleAddress, currentBlockHash); + // Read module in storage + const moduleInStorage = await this.srModulesStorage.findOneById(contractModule.moduleId); + const prevNonce = moduleInStorage?.nonce; + + this.logger.log(`Nonce previous value: ${prevNonce}, nonce current value: ${currNonce}`); + + if (!prevBlockHash) { + this.logger.log('No past state found, start indexing', { stakingModuleAddress, currentBlockHash }); + + await this.updateStakingModule( + updaterState, + moduleInstance, + contractModule, + stakingModuleAddress, + currNonce, + currentBlockHash, + ); + continue; + } + + if (prevNonce !== currNonce) { + this.logger.log('Nonce has been changed, start indexing', { + stakingModuleAddress, + currentBlockHash, + prevNonce, + currNonce, + }); + + await this.updateStakingModule( + updaterState, + moduleInstance, + contractModule, + stakingModuleAddress, + currNonce, + currentBlockHash, + ); + continue; + } + + if (this.isTooMuchDiffBetweenBlocks(prevElMeta.blockNumber, currElMeta.number)) { + this.logger.log('Too much difference between the blocks, start indexing', { + stakingModuleAddress, + currentBlockHash, + }); + + await this.updateStakingModule( + updaterState, + moduleInstance, + contractModule, + stakingModuleAddress, + currNonce, + currentBlockHash, + ); + continue; + } + + if (await this.isReorgDetected(updaterState, prevBlockHash, currentBlockHash)) { + this.logger.log('Reorg detected, start indexing', { stakingModuleAddress, currentBlockHash }); + + await this.updateStakingModule( + updaterState, + moduleInstance, + contractModule, + stakingModuleAddress, + currNonce, + currentBlockHash, + ); + continue; + } + + if ( + prevElMeta.blockNumber < currElMeta.number && + (await moduleInstance.operatorsWereChanged( + contractModule.stakingModuleAddress, + prevElMeta.blockNumber + 1, + currElMeta.number, + )) + ) { + this.logger.log('Update operator events happened, need to update operators', { + stakingModuleAddress, + currentBlockHash, + }); + + await this.updateStakingModule( + updaterState, + moduleInstance, + contractModule, + stakingModuleAddress, + currNonce, + currentBlockHash, + ); + continue; + } + + this.logger.log('No changes have been detected in the module, indexing is not required', { + stakingModuleAddress, + currentBlockHash, + }); + } + + // Update EL meta in db + await this.elMetaStorage.update({ ...currElMeta, lastChangedBlockHash: updaterState.lastChangedBlockHash }); + } + + public async updateStakingModule( + updaterState: UpdaterState, + moduleInstance: StakingModuleInterface, + contractModule: StakingModule, + stakingModuleAddress: string, + currNonce: number, + currentBlockHash: string, + ) { + await moduleInstance.update(stakingModuleAddress, currentBlockHash); + await this.srModulesStorage.upsert(contractModule, currNonce, currentBlockHash); + updaterState.lastChangedBlockHash = currentBlockHash; + } + + public async isReorgDetected(updaterState: UpdaterState, prevBlockHash: string, currentBlockHash: string) { + // calculate once per iteration + // no need to recheck each module separately + if (updaterState.isReorgDetected) { + return true; + } + + const currentBlock = await this.executionProvider.getFullBlock(currentBlockHash); + const prevBlock = await this.executionProvider.getFullBlock(prevBlockHash); + + if (currentBlock.parentHash === prevBlock.hash) return false; + // different hash but same number + if (currentBlock.number === prevBlock.number) { + updaterState.isReorgDetected = true; + return true; + } + + const blocks = await Promise.all( + range(prevBlock.number, currentBlock.number).map(async (bNumber) => { + return await this.executionProvider.getFullBlock(bNumber); + }), + ); + + for (let i = 1; i < blocks.length; i++) { + const previousBlock = blocks[i - 1]; + const currentBlock = blocks[i]; + + if (currentBlock.parentHash !== previousBlock.hash) { + updaterState.isReorgDetected = true; + return true; + } + } + + return false; + } + + public isTooMuchDiffBetweenBlocks(prevBlockNumber: number, currentBlockNumber: number) { + return currentBlockNumber - prevBlockNumber >= MAX_BLOCKS_OVERLAP; + } +} From 2796b6ff7bbd2b6b2e792d5f3fc97eb3ab983872 Mon Sep 17 00:00:00 2001 From: Eddort Date: Wed, 20 Dec 2023 22:16:31 +0100 Subject: [PATCH 16/48] refactor: rename updater --- ...dater.service.ts => staking-module-updater.service.ts} | 2 +- src/jobs/keys-update/test/update-cases.spec.ts | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) rename src/jobs/keys-update/{updater.service.ts => staking-module-updater.service.ts} (99%) diff --git a/src/jobs/keys-update/updater.service.ts b/src/jobs/keys-update/staking-module-updater.service.ts similarity index 99% rename from src/jobs/keys-update/updater.service.ts rename to src/jobs/keys-update/staking-module-updater.service.ts index 4cd847f4..9a4b93db 100644 --- a/src/jobs/keys-update/updater.service.ts +++ b/src/jobs/keys-update/staking-module-updater.service.ts @@ -10,7 +10,7 @@ import { UpdaterPayload, UpdaterState } from './keys-update.interfaces'; import { MAX_BLOCKS_OVERLAP } from './keys-update.constants'; @Injectable() -export class KeysUpdaterService { +export class StakingModuleUpdaterService { constructor( @Inject(LOGGER_PROVIDER) protected readonly logger: LoggerService, protected readonly stakingRouterService: StakingRouterService, diff --git a/src/jobs/keys-update/test/update-cases.spec.ts b/src/jobs/keys-update/test/update-cases.spec.ts index 27305007..a6a287dd 100644 --- a/src/jobs/keys-update/test/update-cases.spec.ts +++ b/src/jobs/keys-update/test/update-cases.spec.ts @@ -5,10 +5,10 @@ import { StakingRouterService } from 'staking-router-modules/staking-router.serv import { ElMetaStorageService } from 'storage/el-meta.storage'; import { SRModuleStorageService } from 'storage/sr-module.storage'; import { stakingModuleFixture, stakingModuleFixtures } from '../keys-update.fixtures'; -import { KeysUpdaterService } from '../updater.service'; +import { StakingModuleUpdaterService } from '../staking-module-updater.service'; describe('YourService', () => { - let updaterService: KeysUpdaterService; + let updaterService: StakingModuleUpdaterService; let stakingRouterService: StakingRouterService; let sRModuleStorageService: SRModuleStorageService; let loggerService: { log: jest.Mock }; @@ -52,11 +52,11 @@ describe('YourService', () => { upsert: jest.fn(), }, }, - KeysUpdaterService, + StakingModuleUpdaterService, ], }).compile(); - updaterService = module.get(KeysUpdaterService); + updaterService = module.get(StakingModuleUpdaterService); stakingRouterService = module.get(StakingRouterService); sRModuleStorageService = module.get(SRModuleStorageService); executionProviderService = module.get(ExecutionProviderService); From 153345401b4605fd4d1deb25d9a72ca615b0944d Mon Sep 17 00:00:00 2001 From: Eddort Date: Wed, 20 Dec 2023 22:25:34 +0100 Subject: [PATCH 17/48] fix: different hash but same number case --- src/jobs/keys-update/staking-module-updater.service.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/jobs/keys-update/staking-module-updater.service.ts b/src/jobs/keys-update/staking-module-updater.service.ts index 9a4b93db..91bdc165 100644 --- a/src/jobs/keys-update/staking-module-updater.service.ts +++ b/src/jobs/keys-update/staking-module-updater.service.ts @@ -164,7 +164,7 @@ export class StakingModuleUpdaterService { if (currentBlock.parentHash === prevBlock.hash) return false; // different hash but same number - if (currentBlock.number === prevBlock.number) { + if (currentBlock.hash !== prevBlock.hash && currentBlock.number === prevBlock.number) { updaterState.isReorgDetected = true; return true; } From ba06c2c03d70d6eadb24fd347bb398aabd9e7343 Mon Sep 17 00:00:00 2001 From: Eddort Date: Wed, 20 Dec 2023 22:25:44 +0100 Subject: [PATCH 18/48] test: reorg spec --- .../keys-update/test/detect-reorg.spec.ts | 114 ++++++++++++++++++ 1 file changed, 114 insertions(+) create mode 100644 src/jobs/keys-update/test/detect-reorg.spec.ts diff --git a/src/jobs/keys-update/test/detect-reorg.spec.ts b/src/jobs/keys-update/test/detect-reorg.spec.ts new file mode 100644 index 00000000..230172ca --- /dev/null +++ b/src/jobs/keys-update/test/detect-reorg.spec.ts @@ -0,0 +1,114 @@ +import { LOGGER_PROVIDER } from '@lido-nestjs/logger'; +import { Test, TestingModule } from '@nestjs/testing'; +import { ExecutionProviderService } from 'common/execution-provider'; +import { StakingRouterService } from 'staking-router-modules/staking-router.service'; +import { ElMetaStorageService } from 'storage/el-meta.storage'; +import { SRModuleStorageService } from 'storage/sr-module.storage'; +import { UpdaterState } from '../keys-update.interfaces'; +import { StakingModuleUpdaterService } from '../staking-module-updater.service'; + +describe('YourService', () => { + let updaterService: StakingModuleUpdaterService; + let executionProviderService: ExecutionProviderService; + + beforeEach(async () => { + const module: TestingModule = await Test.createTestingModule({ + providers: [ + { + provide: LOGGER_PROVIDER, + useValue: { + log: jest.fn(), + }, + }, + { + provide: StakingRouterService, + useValue: { + getStakingRouterModuleImpl: () => ({ + getCurrentNonce() { + return 1; + }, + }), + }, + }, + { + provide: ElMetaStorageService, + useValue: { + update: jest.fn(), + }, + }, + { + provide: ExecutionProviderService, + useValue: { + getFullBlock: jest.fn(), + }, + }, + { + provide: SRModuleStorageService, + useValue: { + findOneById: jest.fn(), + upsert: jest.fn(), + }, + }, + StakingModuleUpdaterService, + ], + }).compile(); + + updaterService = module.get(StakingModuleUpdaterService); + executionProviderService = module.get(ExecutionProviderService); + }); + + it('should be defined', () => { + expect(updaterService).toBeDefined(); + }); + + it('parent hash of the currentBlock matches the hash of the prevBlock', async () => { + const updaterState: UpdaterState = { + lastChangedBlockHash: '0x1', + isReorgDetected: false, + }; + + jest + .spyOn(executionProviderService, 'getFullBlock') + .mockImplementationOnce(async () => ({ number: 2, hash: '0x2', timestamp: 1, parentHash: '0x1' } as any)); + + jest + .spyOn(executionProviderService, 'getFullBlock') + .mockImplementationOnce(async () => ({ number: 1, hash: '0x1', timestamp: 1, parentHash: '0x0' } as any)); + + expect(await updaterService.isReorgDetected(updaterState, '0x1', '0x1')).toBeFalsy(); + }); + + it('parent hash of the currentBlock matches the hash of the prevBlock', async () => { + const updaterState: UpdaterState = { + lastChangedBlockHash: '0x1', + isReorgDetected: false, + }; + + jest + .spyOn(executionProviderService, 'getFullBlock') + .mockImplementationOnce(async () => ({ number: 2, hash: '0x2', timestamp: 1, parentHash: '0x1' } as any)); + + jest + .spyOn(executionProviderService, 'getFullBlock') + .mockImplementationOnce(async () => ({ number: 1, hash: '0x1', timestamp: 1, parentHash: '0x0' } as any)); + + expect(await updaterService.isReorgDetected(updaterState, '0x1', '0x1')).toBeFalsy(); + }); + + it('same block number but different hashes', async () => { + const updaterState: UpdaterState = { + lastChangedBlockHash: '0x1', + isReorgDetected: false, + }; + + jest + .spyOn(executionProviderService, 'getFullBlock') + .mockImplementationOnce(async () => ({ number: 2, hash: '0x2', timestamp: 1, parentHash: '0x2' } as any)); + + jest + .spyOn(executionProviderService, 'getFullBlock') + .mockImplementationOnce(async () => ({ number: 2, hash: '0x1', timestamp: 1, parentHash: '0x0' } as any)); + + expect(await updaterService.isReorgDetected(updaterState, '0x1', '0x1')).toBeTruthy(); + }); +}); From a8a1bc024056ef297a9315e005bf327ed0292b81 Mon Sep 17 00:00:00 2001 From: Eddort Date: Wed, 20 Dec 2023 22:38:50 +0100 Subject: [PATCH 19/48] test: reorg cases --- .../keys-update/test/detect-reorg.spec.ts | 61 ++++++++++++++++++- .../keys-update/test/update-cases.spec.ts | 2 +- 2 files changed, 61 insertions(+), 2 deletions(-) diff --git a/src/jobs/keys-update/test/detect-reorg.spec.ts b/src/jobs/keys-update/test/detect-reorg.spec.ts index 230172ca..1230d512 100644 --- a/src/jobs/keys-update/test/detect-reorg.spec.ts +++ b/src/jobs/keys-update/test/detect-reorg.spec.ts @@ -7,7 +7,7 @@ import { SRModuleStorageService } from 'storage/sr-module.storage'; import { UpdaterState } from '../keys-update.interfaces'; import { StakingModuleUpdaterService } from '../staking-module-updater.service'; -describe('YourService', () => { +describe('detect reorg', () => { let updaterService: StakingModuleUpdaterService; let executionProviderService: ExecutionProviderService; @@ -76,6 +76,7 @@ describe('YourService', () => { .mockImplementationOnce(async () => ({ number: 1, hash: '0x1', timestamp: 1, parentHash: '0x0' } as any)); expect(await updaterService.isReorgDetected(updaterState, '0x1', '0x1')).toBeFalsy(); + expect(updaterState.isReorgDetected).toBeFalsy(); }); it('parent hash of the currentBlock matches the hash of the prevBlock', async () => { @@ -93,6 +94,7 @@ describe('YourService', () => { .mockImplementationOnce(async () => ({ number: 1, hash: '0x1', timestamp: 1, parentHash: '0x0' } as any)); expect(await updaterService.isReorgDetected(updaterState, '0x1', '0x1')).toBeFalsy(); + expect(updaterState.isReorgDetected).toBeFalsy(); }); it('same block number but different hashes', async () => { @@ -110,5 +112,62 @@ describe('YourService', () => { .mockImplementationOnce(async () => ({ number: 2, hash: '0x1', timestamp: 1, parentHash: '0x0' } as any)); expect(await updaterService.isReorgDetected(updaterState, '0x1', '0x1')).toBeTruthy(); + expect(updaterState.isReorgDetected).toBeTruthy(); + }); + + it('check blockchain (happy pass)', async () => { + const updaterState: UpdaterState = { + lastChangedBlockHash: '0x1', + isReorgDetected: false, + }; + + jest + .spyOn(executionProviderService, 'getFullBlock') + .mockImplementationOnce(async () => ({ number: 100, hash: '0x100', timestamp: 1, parentHash: '0x99' } as any)); + + jest + .spyOn(executionProviderService, 'getFullBlock') + .mockImplementationOnce(async () => ({ number: 1, hash: '0x1', timestamp: 1, parentHash: '0x0' } as any)); + + jest.spyOn(executionProviderService, 'getFullBlock').mockImplementation( + async (blockHashOrBlockTag: string | number) => + ({ + number: Number(blockHashOrBlockTag), + hash: `0x${blockHashOrBlockTag}`, + timestamp: 1, + parentHash: `0x${Number(blockHashOrBlockTag) - 1}`, + } as any), + ); + + expect(await updaterService.isReorgDetected(updaterState, '0x1', '0x1')).toBeFalsy(); + expect(updaterState.isReorgDetected).toBeFalsy(); + }); + + it('check blockchain (parent hash does not match)', async () => { + const updaterState: UpdaterState = { + lastChangedBlockHash: '0x1', + isReorgDetected: false, + }; + + jest + .spyOn(executionProviderService, 'getFullBlock') + .mockImplementationOnce(async () => ({ number: 100, hash: '0x100', timestamp: 1, parentHash: '0x99' } as any)); + + jest + .spyOn(executionProviderService, 'getFullBlock') + .mockImplementationOnce(async () => ({ number: 1, hash: '0x1', timestamp: 1, parentHash: '0x0' } as any)); + + jest.spyOn(executionProviderService, 'getFullBlock').mockImplementation( + async (blockHashOrBlockTag: string | number) => + ({ + number: Number(blockHashOrBlockTag), + hash: `0x${blockHashOrBlockTag}`, + timestamp: 1, + parentHash: `0xSORRY`, + } as any), + ); + + expect(await updaterService.isReorgDetected(updaterState, '0x1', '0x1')).toBeTruthy(); + expect(updaterState.isReorgDetected).toBeTruthy(); }); }); diff --git a/src/jobs/keys-update/test/update-cases.spec.ts b/src/jobs/keys-update/test/update-cases.spec.ts index a6a287dd..dc990a65 100644 --- a/src/jobs/keys-update/test/update-cases.spec.ts +++ b/src/jobs/keys-update/test/update-cases.spec.ts @@ -7,7 +7,7 @@ import { SRModuleStorageService } from 'storage/sr-module.storage'; import { stakingModuleFixture, stakingModuleFixtures } from '../keys-update.fixtures'; import { StakingModuleUpdaterService } from '../staking-module-updater.service'; -describe('YourService', () => { +describe('update cases', () => { let updaterService: StakingModuleUpdaterService; let stakingRouterService: StakingRouterService; let sRModuleStorageService: SRModuleStorageService; From 403a91a7ed14da062f887600ae71f1da5806a551 Mon Sep 17 00:00:00 2001 From: Eddort Date: Wed, 20 Dec 2023 22:39:14 +0100 Subject: [PATCH 20/48] fix: block range in isReorgDetected method --- src/jobs/keys-update/staking-module-updater.service.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/jobs/keys-update/staking-module-updater.service.ts b/src/jobs/keys-update/staking-module-updater.service.ts index 91bdc165..544632d5 100644 --- a/src/jobs/keys-update/staking-module-updater.service.ts +++ b/src/jobs/keys-update/staking-module-updater.service.ts @@ -170,7 +170,7 @@ export class StakingModuleUpdaterService { } const blocks = await Promise.all( - range(prevBlock.number, currentBlock.number).map(async (bNumber) => { + range(prevBlock.number, currentBlock.number + 1).map(async (bNumber) => { return await this.executionProvider.getFullBlock(bNumber); }), ); From 9cef0d3408946ca223b4b92a2835023e82039fea Mon Sep 17 00:00:00 2001 From: Eddort Date: Wed, 20 Dec 2023 22:43:36 +0100 Subject: [PATCH 21/48] refactor: connect updater to keys-update service --- src/jobs/keys-update/keys-update.module.ts | 5 +- src/jobs/keys-update/keys-update.service.ts | 184 +------------------- 2 files changed, 7 insertions(+), 182 deletions(-) diff --git a/src/jobs/keys-update/keys-update.module.ts b/src/jobs/keys-update/keys-update.module.ts index 5019bc72..7a03cd27 100644 --- a/src/jobs/keys-update/keys-update.module.ts +++ b/src/jobs/keys-update/keys-update.module.ts @@ -6,6 +6,7 @@ import { StakingRouterFetchModule } from 'staking-router-modules/contracts'; import { ExecutionProviderModule } from 'common/execution-provider'; import { StorageModule } from 'storage/storage.module'; import { PrometheusModule } from 'common/prometheus'; +import { StakingModuleUpdaterService } from './staking-module-updater.service'; @Module({ imports: [ @@ -16,7 +17,7 @@ import { PrometheusModule } from 'common/prometheus'; StorageModule, PrometheusModule, ], - providers: [KeysUpdateService], - exports: [KeysUpdateService], + providers: [KeysUpdateService, StakingModuleUpdaterService], + exports: [KeysUpdateService, StakingModuleUpdaterService], }) export class KeysUpdateModule {} diff --git a/src/jobs/keys-update/keys-update.service.ts b/src/jobs/keys-update/keys-update.service.ts index d5d0b8ea..f5794111 100644 --- a/src/jobs/keys-update/keys-update.service.ts +++ b/src/jobs/keys-update/keys-update.service.ts @@ -1,5 +1,4 @@ import { Inject, Injectable } from '@nestjs/common'; -import { range } from '@lido-nestjs/utils'; import { LOGGER_PROVIDER, LoggerService } from 'common/logger'; import { ConfigService } from 'common/config'; import { JobService } from 'common/job'; @@ -14,10 +13,8 @@ import { SRModuleStorageService } from 'storage/sr-module.storage'; import { IsolationLevel } from '@mikro-orm/core'; import { PrometheusService } from 'common/prometheus'; import { SrModuleEntity } from 'storage/sr-module.entity'; -import { StakingModule, StakingModuleInterface } from 'staking-router-modules/interfaces/staking-module.interface'; -import { UpdaterPayload, UpdaterState } from './keys-update.interfaces'; - -const MAX_BLOCKS_OVERLAP = 30; +import { StakingModule } from 'staking-router-modules/interfaces/staking-module.interface'; +import { StakingModuleUpdaterService } from './staking-module-updater.service'; class KeyOutdatedError extends Error { lastBlock: number; @@ -42,6 +39,7 @@ export class KeysUpdateService { protected readonly executionProvider: ExecutionProviderService, protected readonly srModulesStorage: SRModuleStorageService, protected readonly prometheusService: PrometheusService, + protected readonly stakingModuleUpdaterService: StakingModuleUpdaterService, ) {} protected lastTimestampSec: number | undefined = undefined; @@ -144,7 +142,7 @@ export class KeysUpdateService { await this.entityManager.transactional( async () => { - await this.updateStakingModules({ currElMeta, prevElMeta, contractModules }); + await this.stakingModuleUpdaterService.updateStakingModules({ currElMeta, prevElMeta, contractModules }); }, { isolationLevel: IsolationLevel.READ_COMMITTED }, ); @@ -152,180 +150,6 @@ export class KeysUpdateService { return currElMeta; } - public async updateStakingModules(updaterPayload: UpdaterPayload): Promise { - const { prevElMeta, currElMeta, contractModules } = updaterPayload; - const prevBlockHash = prevElMeta?.blockHash; - const currentBlockHash = currElMeta.hash; - - const updaterState: UpdaterState = { - lastChangedBlockHash: prevBlockHash || currentBlockHash, - isReorgDetected: false, - }; - - for (const contractModule of contractModules) { - const { stakingModuleAddress } = contractModule; - - // Find implementation for staking module - const moduleInstance = this.stakingRouterService.getStakingRouterModuleImpl(contractModule.type); - // Read current nonce from contract - const currNonce = await moduleInstance.getCurrentNonce(stakingModuleAddress, currentBlockHash); - // Read module in storage - const moduleInStorage = await this.srModulesStorage.findOneById(contractModule.moduleId); - const prevNonce = moduleInStorage?.nonce; - - this.logger.log(`Nonce previous value: ${prevNonce}, nonce current value: ${currNonce}`); - - if (!prevBlockHash) { - this.logger.log('No past state found, start indexing', { stakingModuleAddress, currentBlockHash }); - - await this.updateStakingModule( - updaterState, - moduleInstance, - contractModule, - stakingModuleAddress, - currNonce, - currentBlockHash, - ); - continue; - } - - if (prevNonce !== currNonce) { - this.logger.log('Nonce has been changed, start indexing', { - stakingModuleAddress, - currentBlockHash, - prevNonce, - currNonce, - }); - - await this.updateStakingModule( - updaterState, - moduleInstance, - contractModule, - stakingModuleAddress, - currNonce, - currentBlockHash, - ); - continue; - } - - if (this.isTooMuchDiffBetweenBlocks(prevElMeta.blockNumber, currElMeta.number)) { - this.logger.log('Too much difference between the blocks, start indexing', { - stakingModuleAddress, - currentBlockHash, - }); - - await this.updateStakingModule( - updaterState, - moduleInstance, - contractModule, - stakingModuleAddress, - currNonce, - currentBlockHash, - ); - continue; - } - - if (await this.isReorgDetected(updaterState, prevBlockHash, currentBlockHash)) { - this.logger.log('Reorg detected, start indexing', { stakingModuleAddress, currentBlockHash }); - - await this.updateStakingModule( - updaterState, - moduleInstance, - contractModule, - stakingModuleAddress, - currNonce, - currentBlockHash, - ); - continue; - } - - if ( - prevElMeta.blockNumber < currElMeta.number && - (await moduleInstance.operatorsWereChanged( - contractModule.stakingModuleAddress, - prevElMeta.blockNumber + 1, - currElMeta.number, - )) - ) { - this.logger.log('Update operator events happened, need to update operators', { - stakingModuleAddress, - currentBlockHash, - }); - - await this.updateStakingModule( - updaterState, - moduleInstance, - contractModule, - stakingModuleAddress, - currNonce, - currentBlockHash, - ); - continue; - } - - this.logger.log('No changes have been detected in the module, indexing is not required', { - stakingModuleAddress, - currentBlockHash, - }); - } - - // Update EL meta in db - await this.elMetaStorage.update({ ...currElMeta, lastChangedBlockHash: updaterState.lastChangedBlockHash }); - } - - public async updateStakingModule( - updaterState: UpdaterState, - moduleInstance: StakingModuleInterface, - contractModule: StakingModule, - stakingModuleAddress: string, - currNonce: number, - currentBlockHash: string, - ) { - await moduleInstance.update(stakingModuleAddress, currentBlockHash); - await this.srModulesStorage.upsert(contractModule, currNonce, currentBlockHash); - updaterState.lastChangedBlockHash = currentBlockHash; - } - - public async isReorgDetected(updaterState: UpdaterState, prevBlockHash: string, currentBlockHash: string) { - // calculate once per iteration - // no need to recheck each module separately - if (updaterState.isReorgDetected) { - return true; - } - - const currentBlock = await this.executionProvider.getFullBlock(currentBlockHash); - const prevBlock = await this.executionProvider.getFullBlock(prevBlockHash); - - if (currentBlock.parentHash === prevBlock.hash) return false; - // different hash but same number - if (currentBlock.number === prevBlock.number) { - updaterState.isReorgDetected = true; - return true; - } - - const blocks = await Promise.all( - range(prevBlock.number, currentBlock.number).map(async (bNumber) => { - return await this.executionProvider.getFullBlock(bNumber); - }), - ); - - for (let i = 1; i < blocks.length; i++) { - const previousBlock = blocks[i - 1]; - const currentBlock = blocks[i]; - - if (currentBlock.parentHash !== previousBlock.hash) { - updaterState.isReorgDetected = true; - return true; - } - } - - return false; - } - - public isTooMuchDiffBetweenBlocks(prevBlockNumber: number, currentBlockNumber: number) { - return currentBlockNumber - prevBlockNumber >= MAX_BLOCKS_OVERLAP; - } - /** * Update prometheus metrics of staking modules */ From e9c83f7ff5369d48224b7f4bbf2ea434f2397e4f Mon Sep 17 00:00:00 2001 From: Eddort Date: Wed, 20 Dec 2023 23:25:05 +0100 Subject: [PATCH 22/48] fix: e2e tests --- .../registry/test/fixtures/connect.fixture.ts | 80 +++++++++++++++++++ src/http/db.fixtures.ts | 3 + src/http/el-meta.fixture.ts | 1 + src/http/keys/keys.e2e-spec.ts | 28 ++++--- src/http/operator.fixtures.ts | 2 + .../sr-modules-keys.e2e-spec.ts | 28 ++++--- .../sr-modules-operators-keys.e2e-spec.ts | 11 ++- .../sr-modules-operators.e2e-spec.ts | 22 ++--- .../sr-modules-validators.e2e-spec.ts | 12 +-- src/http/sr-modules/sr-modules.e2e-spec.ts | 16 ++-- src/jobs/keys-update/keys-update.fixtures.ts | 2 + .../staking-router-fetch.service.ts | 1 + .../interfaces/staking-module.interface.ts | 2 + src/storage/sr-module.storage.e2e-spec.ts | 13 +-- 14 files changed, 173 insertions(+), 48 deletions(-) diff --git a/src/common/registry/test/fixtures/connect.fixture.ts b/src/common/registry/test/fixtures/connect.fixture.ts index aa1c644f..c4877347 100644 --- a/src/common/registry/test/fixtures/connect.fixture.ts +++ b/src/common/registry/test/fixtures/connect.fixture.ts @@ -16,6 +16,7 @@ export const operators = [ stoppedValidators: 0, totalSigningKeys: 0, usedSigningKeys: 0, + finalizedUsedSigningKeys: 0, }, { index: 1, @@ -27,6 +28,7 @@ export const operators = [ stoppedValidators: 1, totalSigningKeys: 123, usedSigningKeys: 51, + finalizedUsedSigningKeys: 51, }, { index: 2, @@ -38,6 +40,7 @@ export const operators = [ stoppedValidators: 0, totalSigningKeys: 0, usedSigningKeys: 0, + finalizedUsedSigningKeys: 0, }, { index: 3, @@ -49,6 +52,7 @@ export const operators = [ stoppedValidators: 0, totalSigningKeys: 0, usedSigningKeys: 0, + finalizedUsedSigningKeys: 0, }, { index: 4, @@ -60,6 +64,7 @@ export const operators = [ stoppedValidators: 2, totalSigningKeys: 10, usedSigningKeys: 10, + finalizedUsedSigningKeys: 10, }, { index: 5, @@ -71,6 +76,7 @@ export const operators = [ stoppedValidators: 0, totalSigningKeys: 0, usedSigningKeys: 0, + finalizedUsedSigningKeys: 0, }, { index: 6, @@ -82,6 +88,7 @@ export const operators = [ stoppedValidators: 338, totalSigningKeys: 2511, usedSigningKeys: 2109, + finalizedUsedSigningKeys: 2109, }, { index: 7, @@ -93,6 +100,7 @@ export const operators = [ stoppedValidators: 1, totalSigningKeys: 20, usedSigningKeys: 20, + finalizedUsedSigningKeys: 20, }, { index: 8, @@ -104,6 +112,7 @@ export const operators = [ stoppedValidators: 6, totalSigningKeys: 109, usedSigningKeys: 109, + finalizedUsedSigningKeys: 109, }, { index: 9, @@ -115,6 +124,7 @@ export const operators = [ stoppedValidators: 5, totalSigningKeys: 10, usedSigningKeys: 10, + finalizedUsedSigningKeys: 10, }, { index: 10, @@ -126,6 +136,7 @@ export const operators = [ stoppedValidators: 2, totalSigningKeys: 310, usedSigningKeys: 17, + finalizedUsedSigningKeys: 17, }, { index: 11, @@ -137,6 +148,7 @@ export const operators = [ stoppedValidators: 2, totalSigningKeys: 110, usedSigningKeys: 10, + finalizedUsedSigningKeys: 10, }, { index: 12, @@ -148,6 +160,7 @@ export const operators = [ stoppedValidators: 0, totalSigningKeys: 41, usedSigningKeys: 0, + finalizedUsedSigningKeys: 0, }, { index: 13, @@ -159,6 +172,7 @@ export const operators = [ stoppedValidators: 0, totalSigningKeys: 0, usedSigningKeys: 0, + finalizedUsedSigningKeys: 0, }, { index: 14, @@ -170,6 +184,7 @@ export const operators = [ stoppedValidators: 0, totalSigningKeys: 0, usedSigningKeys: 0, + finalizedUsedSigningKeys: 0, }, { index: 15, @@ -181,6 +196,7 @@ export const operators = [ stoppedValidators: 4, totalSigningKeys: 20, usedSigningKeys: 20, + finalizedUsedSigningKeys: 20, }, { index: 16, @@ -192,6 +208,7 @@ export const operators = [ stoppedValidators: 2, totalSigningKeys: 10, usedSigningKeys: 10, + finalizedUsedSigningKeys: 10, }, { index: 17, @@ -203,6 +220,7 @@ export const operators = [ stoppedValidators: 2, totalSigningKeys: 10, usedSigningKeys: 10, + finalizedUsedSigningKeys: 10, }, { index: 18, @@ -214,6 +232,7 @@ export const operators = [ stoppedValidators: 2, totalSigningKeys: 10, usedSigningKeys: 10, + finalizedUsedSigningKeys: 10, }, { index: 19, @@ -225,6 +244,7 @@ export const operators = [ stoppedValidators: 0, totalSigningKeys: 0, usedSigningKeys: 0, + finalizedUsedSigningKeys: 0, }, { index: 20, @@ -236,6 +256,7 @@ export const operators = [ stoppedValidators: 0, totalSigningKeys: 0, usedSigningKeys: 0, + finalizedUsedSigningKeys: 0, }, { index: 21, @@ -247,6 +268,7 @@ export const operators = [ stoppedValidators: 0, totalSigningKeys: 0, usedSigningKeys: 0, + finalizedUsedSigningKeys: 0, }, { index: 22, @@ -258,6 +280,7 @@ export const operators = [ stoppedValidators: 0, totalSigningKeys: 0, usedSigningKeys: 0, + finalizedUsedSigningKeys: 0, }, { index: 23, @@ -269,6 +292,7 @@ export const operators = [ stoppedValidators: 22, totalSigningKeys: 62, usedSigningKeys: 48, + finalizedUsedSigningKeys: 48, }, { index: 24, @@ -280,6 +304,7 @@ export const operators = [ stoppedValidators: 1, totalSigningKeys: 20, usedSigningKeys: 20, + finalizedUsedSigningKeys: 20, }, { index: 25, @@ -291,6 +316,7 @@ export const operators = [ stoppedValidators: 2, totalSigningKeys: 10, usedSigningKeys: 10, + finalizedUsedSigningKeys: 10, }, { index: 26, @@ -302,6 +328,7 @@ export const operators = [ stoppedValidators: 2, totalSigningKeys: 10, usedSigningKeys: 10, + finalizedUsedSigningKeys: 10, }, { index: 27, @@ -313,6 +340,7 @@ export const operators = [ stoppedValidators: 7, totalSigningKeys: 22, usedSigningKeys: 22, + finalizedUsedSigningKeys: 22, }, { index: 28, @@ -324,6 +352,7 @@ export const operators = [ stoppedValidators: 2, totalSigningKeys: 20, usedSigningKeys: 12, + finalizedUsedSigningKeys: 12, }, { index: 29, @@ -335,6 +364,7 @@ export const operators = [ stoppedValidators: 1, totalSigningKeys: 20, usedSigningKeys: 20, + finalizedUsedSigningKeys: 20, }, { index: 30, @@ -346,6 +376,7 @@ export const operators = [ stoppedValidators: 2, totalSigningKeys: 10, usedSigningKeys: 10, + finalizedUsedSigningKeys: 10, }, { index: 31, @@ -357,6 +388,7 @@ export const operators = [ stoppedValidators: 0, totalSigningKeys: 0, usedSigningKeys: 0, + finalizedUsedSigningKeys: 0, }, { index: 32, @@ -368,6 +400,7 @@ export const operators = [ stoppedValidators: 0, totalSigningKeys: 2, usedSigningKeys: 0, + finalizedUsedSigningKeys: 0, }, { index: 33, @@ -379,6 +412,7 @@ export const operators = [ stoppedValidators: 0, totalSigningKeys: 0, usedSigningKeys: 0, + finalizedUsedSigningKeys: 0, }, { index: 34, @@ -390,6 +424,7 @@ export const operators = [ stoppedValidators: 0, totalSigningKeys: 10, usedSigningKeys: 0, + finalizedUsedSigningKeys: 0, }, { index: 35, @@ -401,6 +436,7 @@ export const operators = [ stoppedValidators: 0, totalSigningKeys: 250, usedSigningKeys: 0, + finalizedUsedSigningKeys: 0, }, { index: 36, @@ -412,6 +448,7 @@ export const operators = [ stoppedValidators: 2, totalSigningKeys: 10, usedSigningKeys: 10, + finalizedUsedSigningKeys: 10, }, { index: 37, @@ -423,6 +460,7 @@ export const operators = [ stoppedValidators: 2, totalSigningKeys: 10, usedSigningKeys: 10, + finalizedUsedSigningKeys: 10, }, { index: 38, @@ -434,6 +472,7 @@ export const operators = [ stoppedValidators: 5, totalSigningKeys: 10, usedSigningKeys: 10, + finalizedUsedSigningKeys: 10, }, { index: 39, @@ -445,6 +484,7 @@ export const operators = [ stoppedValidators: 2, totalSigningKeys: 10, usedSigningKeys: 10, + finalizedUsedSigningKeys: 10, }, { index: 40, @@ -456,6 +496,7 @@ export const operators = [ stoppedValidators: 2, totalSigningKeys: 12, usedSigningKeys: 10, + finalizedUsedSigningKeys: 10, }, { index: 41, @@ -467,6 +508,7 @@ export const operators = [ stoppedValidators: 930, totalSigningKeys: 2000, usedSigningKeys: 2000, + finalizedUsedSigningKeys: 2000, }, { index: 42, @@ -478,6 +520,7 @@ export const operators = [ stoppedValidators: 18, totalSigningKeys: 1010, usedSigningKeys: 1010, + finalizedUsedSigningKeys: 1010, }, { index: 43, @@ -489,6 +532,7 @@ export const operators = [ stoppedValidators: 2, totalSigningKeys: 10, usedSigningKeys: 10, + finalizedUsedSigningKeys: 10, }, { index: 44, @@ -500,6 +544,7 @@ export const operators = [ stoppedValidators: 2, totalSigningKeys: 20, usedSigningKeys: 20, + finalizedUsedSigningKeys: 20, }, { index: 45, @@ -511,6 +556,7 @@ export const operators = [ stoppedValidators: 4, totalSigningKeys: 10, usedSigningKeys: 10, + finalizedUsedSigningKeys: 10, }, { index: 46, @@ -522,6 +568,7 @@ export const operators = [ stoppedValidators: 3, totalSigningKeys: 500, usedSigningKeys: 10, + finalizedUsedSigningKeys: 10, }, { index: 47, @@ -533,6 +580,7 @@ export const operators = [ stoppedValidators: 0, totalSigningKeys: 1, usedSigningKeys: 1, + finalizedUsedSigningKeys: 1, }, { index: 48, @@ -544,6 +592,7 @@ export const operators = [ stoppedValidators: 1, totalSigningKeys: 1, usedSigningKeys: 1, + finalizedUsedSigningKeys: 1, }, { index: 49, @@ -555,6 +604,7 @@ export const operators = [ stoppedValidators: 1, totalSigningKeys: 1, usedSigningKeys: 1, + finalizedUsedSigningKeys: 1, }, { index: 50, @@ -566,6 +616,7 @@ export const operators = [ stoppedValidators: 1, totalSigningKeys: 1, usedSigningKeys: 1, + finalizedUsedSigningKeys: 1, }, { index: 51, @@ -577,6 +628,7 @@ export const operators = [ stoppedValidators: 0, totalSigningKeys: 470, usedSigningKeys: 0, + finalizedUsedSigningKeys: 0, }, { index: 52, @@ -588,6 +640,7 @@ export const operators = [ stoppedValidators: 5, totalSigningKeys: 5, usedSigningKeys: 5, + finalizedUsedSigningKeys: 5, }, { index: 53, @@ -599,6 +652,7 @@ export const operators = [ stoppedValidators: 5, totalSigningKeys: 5, usedSigningKeys: 5, + finalizedUsedSigningKeys: 5, }, { index: 54, @@ -610,6 +664,7 @@ export const operators = [ stoppedValidators: 5, totalSigningKeys: 5, usedSigningKeys: 5, + finalizedUsedSigningKeys: 5, }, { index: 55, @@ -621,6 +676,7 @@ export const operators = [ stoppedValidators: 5, totalSigningKeys: 5, usedSigningKeys: 5, + finalizedUsedSigningKeys: 5, }, { index: 56, @@ -632,6 +688,7 @@ export const operators = [ stoppedValidators: 5, totalSigningKeys: 5, usedSigningKeys: 5, + finalizedUsedSigningKeys: 5, }, { index: 57, @@ -643,6 +700,7 @@ export const operators = [ stoppedValidators: 5, totalSigningKeys: 5, usedSigningKeys: 5, + finalizedUsedSigningKeys: 5, }, { index: 58, @@ -654,6 +712,7 @@ export const operators = [ stoppedValidators: 5, totalSigningKeys: 5, usedSigningKeys: 5, + finalizedUsedSigningKeys: 5, }, { index: 59, @@ -665,6 +724,7 @@ export const operators = [ stoppedValidators: 5, totalSigningKeys: 5, usedSigningKeys: 5, + finalizedUsedSigningKeys: 5, }, { index: 60, @@ -676,6 +736,7 @@ export const operators = [ stoppedValidators: 5, totalSigningKeys: 5, usedSigningKeys: 5, + finalizedUsedSigningKeys: 5, }, { index: 61, @@ -687,6 +748,7 @@ export const operators = [ stoppedValidators: 5, totalSigningKeys: 5, usedSigningKeys: 5, + finalizedUsedSigningKeys: 5, }, { index: 62, @@ -698,6 +760,7 @@ export const operators = [ stoppedValidators: 0, totalSigningKeys: 0, usedSigningKeys: 0, + finalizedUsedSigningKeys: 0, }, { index: 63, @@ -709,6 +772,7 @@ export const operators = [ stoppedValidators: 14, totalSigningKeys: 2000, usedSigningKeys: 1786, + finalizedUsedSigningKeys: 1786, }, { index: 64, @@ -720,6 +784,7 @@ export const operators = [ stoppedValidators: 13, totalSigningKeys: 3000, usedSigningKeys: 1785, + finalizedUsedSigningKeys: 1785, }, { index: 65, @@ -731,6 +796,7 @@ export const operators = [ stoppedValidators: 12, totalSigningKeys: 3000, usedSigningKeys: 1783, + finalizedUsedSigningKeys: 1783, }, { index: 66, @@ -742,6 +808,7 @@ export const operators = [ stoppedValidators: 10, totalSigningKeys: 2000, usedSigningKeys: 1781, + finalizedUsedSigningKeys: 1781, }, { index: 67, @@ -753,6 +820,7 @@ export const operators = [ stoppedValidators: 10, totalSigningKeys: 2000, usedSigningKeys: 1781, + finalizedUsedSigningKeys: 1781, }, { index: 68, @@ -764,6 +832,7 @@ export const operators = [ stoppedValidators: 0, totalSigningKeys: 1, usedSigningKeys: 0, + finalizedUsedSigningKeys: 0, }, { index: 69, @@ -775,6 +844,7 @@ export const operators = [ stoppedValidators: 5, totalSigningKeys: 5, usedSigningKeys: 5, + finalizedUsedSigningKeys: 5, }, { index: 70, @@ -786,6 +856,7 @@ export const operators = [ stoppedValidators: 5, totalSigningKeys: 5, usedSigningKeys: 5, + finalizedUsedSigningKeys: 5, }, { index: 71, @@ -797,6 +868,7 @@ export const operators = [ stoppedValidators: 5, totalSigningKeys: 10, usedSigningKeys: 5, + finalizedUsedSigningKeys: 5, }, { index: 72, @@ -808,6 +880,7 @@ export const operators = [ stoppedValidators: 5, totalSigningKeys: 5, usedSigningKeys: 5, + finalizedUsedSigningKeys: 5, }, { index: 73, @@ -819,6 +892,7 @@ export const operators = [ stoppedValidators: 5, totalSigningKeys: 5, usedSigningKeys: 5, + finalizedUsedSigningKeys: 5, }, { index: 74, @@ -830,6 +904,7 @@ export const operators = [ stoppedValidators: 5, totalSigningKeys: 5, usedSigningKeys: 5, + finalizedUsedSigningKeys: 5, }, { index: 75, @@ -841,6 +916,7 @@ export const operators = [ stoppedValidators: 5, totalSigningKeys: 5, usedSigningKeys: 5, + finalizedUsedSigningKeys: 5, }, { index: 76, @@ -852,6 +928,7 @@ export const operators = [ stoppedValidators: 0, totalSigningKeys: 0, usedSigningKeys: 0, + finalizedUsedSigningKeys: 0, }, { index: 77, @@ -863,6 +940,7 @@ export const operators = [ stoppedValidators: 5, totalSigningKeys: 5, usedSigningKeys: 5, + finalizedUsedSigningKeys: 5, }, { index: 78, @@ -874,6 +952,7 @@ export const operators = [ stoppedValidators: 5, totalSigningKeys: 125, usedSigningKeys: 125, + finalizedUsedSigningKeys: 125, }, { index: 79, @@ -885,6 +964,7 @@ export const operators = [ stoppedValidators: 2, totalSigningKeys: 500, usedSigningKeys: 500, + finalizedUsedSigningKeys: 500, }, ]; diff --git a/src/http/db.fixtures.ts b/src/http/db.fixtures.ts index 0a6c0d41..b59c5c2f 100644 --- a/src/http/db.fixtures.ts +++ b/src/http/db.fixtures.ts @@ -17,6 +17,7 @@ export const curatedModule: StakingModule = { lastDepositBlock: 9, exitedValidatorsCount: 0, active: true, + lastChangedBlockHash: '', }; export const dvtModule: StakingModule = { @@ -32,6 +33,7 @@ export const dvtModule: StakingModule = { lastDepositBlock: 10, exitedValidatorsCount: 0, active: true, + lastChangedBlockHash: '', }; export const updatedCuratedModule: StakingModule = { @@ -47,6 +49,7 @@ export const updatedCuratedModule: StakingModule = { lastDepositBlock: 10, exitedValidatorsCount: 1, active: false, + lastChangedBlockHash: '', }; export const srModules = [curatedModule, dvtModule]; diff --git a/src/http/el-meta.fixture.ts b/src/http/el-meta.fixture.ts index 8d8109e7..92643eaa 100644 --- a/src/http/el-meta.fixture.ts +++ b/src/http/el-meta.fixture.ts @@ -2,4 +2,5 @@ export const elMeta = { number: 74, hash: '0x662e3e713207240b25d01324b6eccdc91493249a5048881544254994694530a5', timestamp: 1691500803, + lastChangedBlockHash: '0x662e3e713207240b25d01324b6eccdc91493249a5048881544254994694530a5', }; diff --git a/src/http/keys/keys.e2e-spec.ts b/src/http/keys/keys.e2e-spec.ts index 3001b5b4..04b7cc75 100644 --- a/src/http/keys/keys.e2e-spec.ts +++ b/src/http/keys/keys.e2e-spec.ts @@ -97,8 +97,8 @@ describe('KeyController (e2e)', () => { await keysStorageService.save(keys); // lets save modules - await moduleStorageService.upsert(dvtModule, 1); - await moduleStorageService.upsert(curatedModule, 1); + await moduleStorageService.upsert(dvtModule, 1, ''); + await moduleStorageService.upsert(curatedModule, 1, ''); }); afterAll(async () => { @@ -119,6 +119,7 @@ describe('KeyController (e2e)', () => { blockNumber: elMeta.number, blockHash: elMeta.hash, timestamp: elMeta.timestamp, + lastChangedBlockHash: elMeta.lastChangedBlockHash, }, }); }); @@ -136,6 +137,7 @@ describe('KeyController (e2e)', () => { blockNumber: elMeta.number, blockHash: elMeta.hash, timestamp: elMeta.timestamp, + lastChangedBlockHash: elMeta.lastChangedBlockHash, }, }); }); @@ -152,6 +154,7 @@ describe('KeyController (e2e)', () => { blockNumber: elMeta.number, blockHash: elMeta.hash, timestamp: elMeta.timestamp, + lastChangedBlockHash: elMeta.lastChangedBlockHash, }, }); }); @@ -170,6 +173,7 @@ describe('KeyController (e2e)', () => { blockNumber: elMeta.number, blockHash: elMeta.hash, timestamp: elMeta.timestamp, + lastChangedBlockHash: elMeta.lastChangedBlockHash, }, }); }); @@ -188,6 +192,7 @@ describe('KeyController (e2e)', () => { blockNumber: elMeta.number, blockHash: elMeta.hash, timestamp: elMeta.timestamp, + lastChangedBlockHash: elMeta.lastChangedBlockHash, }, }); }); @@ -202,6 +207,7 @@ describe('KeyController (e2e)', () => { blockNumber: elMeta.number, blockHash: elMeta.hash, timestamp: elMeta.timestamp, + lastChangedBlockHash: elMeta.lastChangedBlockHash, }, }); }); @@ -242,6 +248,7 @@ describe('KeyController (e2e)', () => { blockNumber: elMeta.number, blockHash: elMeta.hash, timestamp: elMeta.timestamp, + lastChangedBlockHash: elMeta.lastChangedBlockHash, }, }); }); @@ -271,7 +278,7 @@ describe('KeyController (e2e)', () => { // lets save keys await keysStorageService.save(keys); - await moduleStorageService.upsert(curatedModule, 1); + await moduleStorageService.upsert(curatedModule, 1, ''); const resp = await request(app.getHttpServer()).get('/v1/keys'); expect(resp.status).toEqual(425); @@ -307,7 +314,7 @@ describe('KeyController (e2e)', () => { it('should return too early response if there are no meta', async () => { // lets save keys await keysStorageService.save(keys); - await moduleStorageService.upsert(curatedModule, 1); + await moduleStorageService.upsert(curatedModule, 1, ''); const pubkeys = [keys[0].key, keys[1].key]; const resp = await request(app.getHttpServer()) .post(`/v1/keys/find`) @@ -327,8 +334,8 @@ describe('KeyController (e2e)', () => { await keysStorageService.save(keys); // lets save modules - await moduleStorageService.upsert(dvtModule, 1); - await moduleStorageService.upsert(curatedModule, 1); + await moduleStorageService.upsert(dvtModule, 1, ''); + await moduleStorageService.upsert(curatedModule, 1, ''); }); afterAll(async () => { @@ -355,6 +362,7 @@ describe('KeyController (e2e)', () => { blockNumber: elMeta.number, blockHash: elMeta.hash, timestamp: elMeta.timestamp, + lastChangedBlockHash: elMeta.lastChangedBlockHash, }, }); }); @@ -375,6 +383,7 @@ describe('KeyController (e2e)', () => { blockNumber: elMeta.number, blockHash: elMeta.hash, timestamp: elMeta.timestamp, + lastChangedBlockHash: elMeta.lastChangedBlockHash, }, }); }); @@ -418,7 +427,7 @@ describe('KeyController (e2e)', () => { it('should return too early response if there are no meta', async () => { // lets save keys await keysStorageService.save(keys); - await moduleStorageService.upsert(curatedModule, 1); + await moduleStorageService.upsert(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 }); @@ -432,8 +441,8 @@ describe('KeyController (e2e)', () => { // lets save keys await keysStorageService.save(keys); // lets save modules - await moduleStorageService.upsert(dvtModule, 1); - await moduleStorageService.upsert(curatedModule, 1); + await moduleStorageService.upsert(dvtModule, 1, ''); + await moduleStorageService.upsert(curatedModule, 1, ''); }); afterAll(async () => { @@ -455,6 +464,7 @@ describe('KeyController (e2e)', () => { blockNumber: elMeta.number, blockHash: elMeta.hash, timestamp: elMeta.timestamp, + lastChangedBlockHash: elMeta.lastChangedBlockHash, }, }); }); diff --git a/src/http/operator.fixtures.ts b/src/http/operator.fixtures.ts index 8efd5515..605214a8 100644 --- a/src/http/operator.fixtures.ts +++ b/src/http/operator.fixtures.ts @@ -10,10 +10,12 @@ import { curatedModuleAddressWithCheckSum, dvtModuleAddressWithChecksum } from ' export const dvtOperatorsResp: Operator[] = [operatorOneDvt, operatorTwoDvt].map((op) => ({ ...op, + finalizedUsedSigningKeys: undefined, moduleAddress: dvtModuleAddressWithChecksum, })); export const curatedOperatorsResp: Operator[] = [operatorOneCurated, operatorTwoCurated].map((op) => ({ ...op, + finalizedUsedSigningKeys: undefined, moduleAddress: curatedModuleAddressWithCheckSum, })); diff --git a/src/http/sr-modules-keys/sr-modules-keys.e2e-spec.ts b/src/http/sr-modules-keys/sr-modules-keys.e2e-spec.ts index 5b505373..ecc5d802 100644 --- a/src/http/sr-modules-keys/sr-modules-keys.e2e-spec.ts +++ b/src/http/sr-modules-keys/sr-modules-keys.e2e-spec.ts @@ -108,8 +108,8 @@ describe('SRModulesKeysController (e2e)', () => { // lets save keys await keysStorageService.save(keys); // lets save modules - await moduleStorageService.upsert(dvtModule, 1); - await moduleStorageService.upsert(curatedModule, 1); + await moduleStorageService.upsert(dvtModule, 1, ''); + await moduleStorageService.upsert(curatedModule, 1, ''); }); afterAll(async () => { @@ -127,6 +127,7 @@ describe('SRModulesKeysController (e2e)', () => { blockNumber: elMeta.number, blockHash: elMeta.hash, timestamp: elMeta.timestamp, + lastChangedBlockHash: elMeta.lastChangedBlockHash, }, }); }); @@ -166,6 +167,7 @@ describe('SRModulesKeysController (e2e)', () => { blockNumber: elMeta.number, blockHash: elMeta.hash, timestamp: elMeta.timestamp, + lastChangedBlockHash: elMeta.lastChangedBlockHash, }, }); }); @@ -185,6 +187,7 @@ describe('SRModulesKeysController (e2e)', () => { blockNumber: elMeta.number, blockHash: elMeta.hash, timestamp: elMeta.timestamp, + lastChangedBlockHash: elMeta.lastChangedBlockHash, }, }); }); @@ -202,6 +205,7 @@ describe('SRModulesKeysController (e2e)', () => { blockNumber: elMeta.number, blockHash: elMeta.hash, timestamp: elMeta.timestamp, + lastChangedBlockHash: elMeta.lastChangedBlockHash, }, }); }); @@ -236,7 +240,7 @@ describe('SRModulesKeysController (e2e)', () => { }); it('Should return too early response if there are no meta', async () => { - await moduleStorageService.upsert(dvtModule, 1); + await moduleStorageService.upsert(dvtModule, 1, ''); const resp = await request(app.getHttpServer()).get(`/v1/modules/${dvtModule.moduleId}/keys`); expect(resp.status).toEqual(425); expect(resp.body).toEqual({ message: 'Too early response', statusCode: 425 }); @@ -252,8 +256,8 @@ describe('SRModulesKeysController (e2e)', () => { // lets save keys await keysStorageService.save(keys); // lets save modules - await moduleStorageService.upsert(dvtModule, 1); - await moduleStorageService.upsert(curatedModule, 1); + await moduleStorageService.upsert(dvtModule, 1, ''); + await moduleStorageService.upsert(curatedModule, 1, ''); }); afterAll(async () => { @@ -278,6 +282,7 @@ describe('SRModulesKeysController (e2e)', () => { blockNumber: elMeta.number, blockHash: elMeta.hash, timestamp: elMeta.timestamp, + lastChangedBlockHash: elMeta.lastChangedBlockHash, }, }); }); @@ -298,6 +303,7 @@ describe('SRModulesKeysController (e2e)', () => { blockNumber: elMeta.number, blockHash: elMeta.hash, timestamp: elMeta.timestamp, + lastChangedBlockHash: elMeta.lastChangedBlockHash, }, }); }); @@ -326,7 +332,7 @@ describe('SRModulesKeysController (e2e)', () => { }); it('Should return too early response if there are no meta', async () => { - await moduleStorageService.upsert(dvtModule, 1); + await moduleStorageService.upsert(dvtModule, 1, ''); const resp = await request(app.getHttpServer()) .post(`/v1/modules/${dvtModule.moduleId}/keys/find`) .set('Content-Type', 'application/json') @@ -345,8 +351,8 @@ describe('SRModulesKeysController (e2e)', () => { // lets save keys await keysStorageService.save(keys); // lets save modules - await moduleStorageService.upsert(dvtModule, 1); - await moduleStorageService.upsert(curatedModule, 1); + await moduleStorageService.upsert(dvtModule, 1, ''); + await moduleStorageService.upsert(curatedModule, 1, ''); }); afterAll(async () => { @@ -363,6 +369,7 @@ describe('SRModulesKeysController (e2e)', () => { blockNumber: elMeta.number, blockHash: elMeta.hash, timestamp: elMeta.timestamp, + lastChangedBlockHash: elMeta.lastChangedBlockHash, }, }); }); @@ -403,6 +410,7 @@ describe('SRModulesKeysController (e2e)', () => { blockNumber: elMeta.number, blockHash: elMeta.hash, timestamp: elMeta.timestamp, + lastChangedBlockHash: elMeta.lastChangedBlockHash, }, }); }); @@ -427,6 +435,7 @@ describe('SRModulesKeysController (e2e)', () => { blockNumber: elMeta.number, blockHash: elMeta.hash, timestamp: elMeta.timestamp, + lastChangedBlockHash: elMeta.lastChangedBlockHash, }, }); }); @@ -444,6 +453,7 @@ describe('SRModulesKeysController (e2e)', () => { blockNumber: elMeta.number, blockHash: elMeta.hash, timestamp: elMeta.timestamp, + lastChangedBlockHash: elMeta.lastChangedBlockHash, }, }); }); @@ -458,7 +468,7 @@ describe('SRModulesKeysController (e2e)', () => { }); it('Should return too early response if there are no meta', async () => { - await moduleStorageService.upsert(dvtModule, 1); + await moduleStorageService.upsert(dvtModule, 1, ''); const resp = await request(app.getHttpServer()).get(`/v1/modules/keys`); expect(resp.status).toEqual(425); expect(resp.body).toEqual({ message: 'Too early response', statusCode: 425 }); diff --git a/src/http/sr-modules-operators-keys/sr-modules-operators-keys.e2e-spec.ts b/src/http/sr-modules-operators-keys/sr-modules-operators-keys.e2e-spec.ts index 4aa1ddb3..b933448c 100644 --- a/src/http/sr-modules-operators-keys/sr-modules-operators-keys.e2e-spec.ts +++ b/src/http/sr-modules-operators-keys/sr-modules-operators-keys.e2e-spec.ts @@ -106,8 +106,8 @@ describe('SRModulesOperatorsKeysController (e2e)', () => { // lets save operators await operatorsStorageService.save(operators); // lets save modules - await moduleStorageService.upsert(dvtModule, 1); - await moduleStorageService.upsert(curatedModule, 1); + await moduleStorageService.upsert(dvtModule, 1, ''); + await moduleStorageService.upsert(curatedModule, 1, ''); }); afterAll(async () => { @@ -129,6 +129,7 @@ describe('SRModulesOperatorsKeysController (e2e)', () => { blockNumber: elMeta.number, blockHash: elMeta.hash, timestamp: elMeta.timestamp, + lastChangedBlockHash: elMeta.lastChangedBlockHash, }, }, operator: dvtOperatorsResp[0], @@ -184,6 +185,7 @@ describe('SRModulesOperatorsKeysController (e2e)', () => { blockNumber: elMeta.number, blockHash: elMeta.hash, timestamp: elMeta.timestamp, + lastChangedBlockHash: elMeta.lastChangedBlockHash, }, }); }); @@ -225,6 +227,7 @@ describe('SRModulesOperatorsKeysController (e2e)', () => { blockNumber: elMeta.number, blockHash: elMeta.hash, timestamp: elMeta.timestamp, + lastChangedBlockHash: elMeta.lastChangedBlockHash, }, }); }); @@ -246,6 +249,7 @@ describe('SRModulesOperatorsKeysController (e2e)', () => { blockNumber: elMeta.number, blockHash: elMeta.hash, timestamp: elMeta.timestamp, + lastChangedBlockHash: elMeta.lastChangedBlockHash, }, }); }); @@ -264,6 +268,7 @@ describe('SRModulesOperatorsKeysController (e2e)', () => { blockNumber: elMeta.number, blockHash: elMeta.hash, timestamp: elMeta.timestamp, + lastChangedBlockHash: elMeta.lastChangedBlockHash, }, }); }); @@ -298,7 +303,7 @@ describe('SRModulesOperatorsKeysController (e2e)', () => { }); it('should return too early response if there are no meta', async () => { - await moduleStorageService.upsert(dvtModule, 1); + await moduleStorageService.upsert(dvtModule, 1, ''); const resp = await request(app.getHttpServer()).get(`/v1/modules/${dvtModule.moduleId}/operators/keys`); expect(resp.status).toEqual(425); expect(resp.body).toEqual({ message: 'Too early response', statusCode: 425 }); diff --git a/src/http/sr-modules-operators/sr-modules-operators.e2e-spec.ts b/src/http/sr-modules-operators/sr-modules-operators.e2e-spec.ts index 11a3c76a..7ec2ea94 100644 --- a/src/http/sr-modules-operators/sr-modules-operators.e2e-spec.ts +++ b/src/http/sr-modules-operators/sr-modules-operators.e2e-spec.ts @@ -110,8 +110,8 @@ describe('SRModuleOperatorsController (e2e)', () => { await operatorsStorageService.save(operators); // lets save modules - await moduleStorageService.upsert(dvtModule, 1); - await moduleStorageService.upsert(curatedModule, 1); + await moduleStorageService.upsert(dvtModule, 1, ''); + await moduleStorageService.upsert(curatedModule, 1, ''); }); afterAll(async () => { @@ -131,6 +131,7 @@ describe('SRModuleOperatorsController (e2e)', () => { blockNumber: elMeta.number, blockHash: elMeta.hash, timestamp: elMeta.timestamp, + lastChangedBlockHash: elMeta.lastChangedBlockHash, }, }); }); @@ -159,7 +160,7 @@ describe('SRModuleOperatorsController (e2e)', () => { it('should return too early response if there are no meta', async () => { // lets save operators await operatorsStorageService.save(operators); - await moduleStorageService.upsert(curatedModule, 1); + await moduleStorageService.upsert(curatedModule, 1, ''); const resp = await request(app.getHttpServer()).get('/v1/operators'); expect(resp.status).toEqual(425); @@ -177,8 +178,8 @@ describe('SRModuleOperatorsController (e2e)', () => { await operatorsStorageService.save(operators); // lets save modules - await moduleStorageService.upsert(dvtModule, 1); - await moduleStorageService.upsert(curatedModule, 1); + await moduleStorageService.upsert(dvtModule, 1, ''); + await moduleStorageService.upsert(curatedModule, 1, ''); }); afterAll(async () => { @@ -204,6 +205,7 @@ describe('SRModuleOperatorsController (e2e)', () => { blockNumber: elMeta.number, blockHash: elMeta.hash, timestamp: elMeta.timestamp, + lastChangedBlockHash: elMeta.lastChangedBlockHash, }, }); @@ -217,6 +219,7 @@ describe('SRModuleOperatorsController (e2e)', () => { blockNumber: elMeta.number, blockHash: elMeta.hash, timestamp: elMeta.timestamp, + lastChangedBlockHash: elMeta.lastChangedBlockHash, }, }); }); @@ -256,7 +259,7 @@ describe('SRModuleOperatorsController (e2e)', () => { it('should return too early response if there are no meta', async () => { // lets save operators await operatorsStorageService.save(operators); - await moduleStorageService.upsert(curatedModule, 1); + await moduleStorageService.upsert(curatedModule, 1, ''); const resp = await request(app.getHttpServer()).get(`/v1/modules/${curatedModule.moduleId}/operators`); expect(resp.status).toEqual(425); @@ -274,8 +277,8 @@ describe('SRModuleOperatorsController (e2e)', () => { await operatorsStorageService.save(operators); // lets save modules - await moduleStorageService.upsert(dvtModule, 1); - await moduleStorageService.upsert(curatedModule, 1); + await moduleStorageService.upsert(dvtModule, 1, ''); + await moduleStorageService.upsert(curatedModule, 1, ''); }); afterAll(async () => { @@ -295,6 +298,7 @@ describe('SRModuleOperatorsController (e2e)', () => { blockNumber: elMeta.number, blockHash: elMeta.hash, timestamp: elMeta.timestamp, + lastChangedBlockHash: elMeta.lastChangedBlockHash, }, }); }); @@ -352,7 +356,7 @@ describe('SRModuleOperatorsController (e2e)', () => { it('should return too early response if there are no meta', async () => { // lets save operators await operatorsStorageService.save(operators); - await moduleStorageService.upsert(curatedModule, 1); + await moduleStorageService.upsert(curatedModule, 1, ''); const resp = await request(app.getHttpServer()).get(`/v1/modules/${curatedModule.moduleId}/operators/1`); expect(resp.status).toEqual(425); diff --git a/src/http/sr-modules-validators/sr-modules-validators.e2e-spec.ts b/src/http/sr-modules-validators/sr-modules-validators.e2e-spec.ts index fc743328..13813b80 100644 --- a/src/http/sr-modules-validators/sr-modules-validators.e2e-spec.ts +++ b/src/http/sr-modules-validators/sr-modules-validators.e2e-spec.ts @@ -146,8 +146,8 @@ describe('SRModulesValidatorsController (e2e)', () => { // lets save keys await keysStorageService.save(keys); // lets save modules - await moduleStorageService.upsert(dvtModule, 1); - await moduleStorageService.upsert(curatedModule, 1); + await moduleStorageService.upsert(dvtModule, 1, ''); + await moduleStorageService.upsert(curatedModule, 1, ''); await validatorsRegistry.update(slot); }); @@ -347,7 +347,7 @@ describe('SRModulesValidatorsController (e2e)', () => { }); it('Should return too early response if there are no meta', async () => { - await moduleStorageService.upsert(dvtModule, 1); + await moduleStorageService.upsert(dvtModule, 1, ''); const resp = await request(app.getHttpServer()).get( `/v1/modules/${dvtModule.moduleId}/validators/validator-exits-to-prepare/1`, ); @@ -363,8 +363,8 @@ describe('SRModulesValidatorsController (e2e)', () => { // lets save keys await keysStorageService.save(keys); // lets save modules - await moduleStorageService.upsert(dvtModule, 1); - await moduleStorageService.upsert(curatedModule, 1); + await moduleStorageService.upsert(dvtModule, 1, ''); + await moduleStorageService.upsert(curatedModule, 1, ''); await validatorsRegistry.update(slot); }); @@ -565,7 +565,7 @@ describe('SRModulesValidatorsController (e2e)', () => { }); it('Should return too early response if there are no meta', async () => { - await moduleStorageService.upsert(dvtModule, 1); + await moduleStorageService.upsert(dvtModule, 1, ''); const resp = await request(app.getHttpServer()).get( `/v1/modules/${dvtModule.moduleId}/validators/generate-unsigned-exit-messages/1`, ); diff --git a/src/http/sr-modules/sr-modules.e2e-spec.ts b/src/http/sr-modules/sr-modules.e2e-spec.ts index 47b3de5c..410de1e7 100644 --- a/src/http/sr-modules/sr-modules.e2e-spec.ts +++ b/src/http/sr-modules/sr-modules.e2e-spec.ts @@ -91,8 +91,8 @@ describe('SRModulesController (e2e)', () => { await elMetaStorageService.update(elMeta); // lets save modules - await moduleStorageService.upsert(dvtModule, 1); - await moduleStorageService.upsert(curatedModule, 1); + await moduleStorageService.upsert(dvtModule, 1, ''); + await moduleStorageService.upsert(curatedModule, 1, ''); }); afterAll(async () => { @@ -110,6 +110,7 @@ describe('SRModulesController (e2e)', () => { blockNumber: elMeta.number, blockHash: elMeta.hash, timestamp: elMeta.timestamp, + lastChangedBlockHash: elMeta.lastChangedBlockHash, }); }); }); @@ -131,7 +132,7 @@ describe('SRModulesController (e2e)', () => { }); it('Should return too early response if there are no meta', async () => { - await moduleStorageService.upsert(curatedModule, 1); + await moduleStorageService.upsert(curatedModule, 1, ''); const resp = await request(app.getHttpServer()).get('/v1/modules'); expect(resp.status).toEqual(425); expect(resp.body).toEqual({ message: 'Too early response', statusCode: 425 }); @@ -145,8 +146,8 @@ describe('SRModulesController (e2e)', () => { // lets save meta await elMetaStorageService.update(elMeta); // lets save modules - await moduleStorageService.upsert(dvtModule, 1); - await moduleStorageService.upsert(curatedModule, 1); + await moduleStorageService.upsert(dvtModule, 1, ''); + await moduleStorageService.upsert(curatedModule, 1, ''); }); afterAll(async () => { await cleanDB(); @@ -160,6 +161,7 @@ describe('SRModulesController (e2e)', () => { blockNumber: elMeta.number, blockHash: elMeta.hash, timestamp: elMeta.timestamp, + lastChangedBlockHash: elMeta.lastChangedBlockHash, }); }); @@ -171,6 +173,7 @@ describe('SRModulesController (e2e)', () => { blockNumber: elMeta.number, blockHash: elMeta.hash, timestamp: elMeta.timestamp, + lastChangedBlockHash: elMeta.lastChangedBlockHash, }); }); @@ -182,6 +185,7 @@ describe('SRModulesController (e2e)', () => { blockNumber: elMeta.number, blockHash: elMeta.hash, timestamp: elMeta.timestamp, + lastChangedBlockHash: elMeta.lastChangedBlockHash, }); }); @@ -215,7 +219,7 @@ describe('SRModulesController (e2e)', () => { }); it('Should return too early response if there are no meta', async () => { - await moduleStorageService.upsert(curatedModule, 1); + await moduleStorageService.upsert(curatedModule, 1, ''); const resp = await request(app.getHttpServer()).get(`/v1/modules/${curatedModule.moduleId}`); expect(resp.status).toEqual(425); expect(resp.body).toEqual({ message: 'Too early response', statusCode: 425 }); diff --git a/src/jobs/keys-update/keys-update.fixtures.ts b/src/jobs/keys-update/keys-update.fixtures.ts index f5ef37d3..5b70abfa 100644 --- a/src/jobs/keys-update/keys-update.fixtures.ts +++ b/src/jobs/keys-update/keys-update.fixtures.ts @@ -13,6 +13,7 @@ export const stakingModuleFixture: StakingModule = { exitedValidatorsCount: 10, type: 'curated', active: true, + lastChangedBlockHash: '', }; export const stakingModuleFixtures: StakingModule[] = [ @@ -30,5 +31,6 @@ export const stakingModuleFixtures: StakingModule[] = [ exitedValidatorsCount: 5, type: 'dvt', active: false, + lastChangedBlockHash: '', }, ]; diff --git a/src/staking-router-modules/contracts/staking-router/staking-router-fetch.service.ts b/src/staking-router-modules/contracts/staking-router/staking-router-fetch.service.ts index 4e167654..92b86825 100644 --- a/src/staking-router-modules/contracts/staking-router/staking-router-fetch.service.ts +++ b/src/staking-router-modules/contracts/staking-router/staking-router-fetch.service.ts @@ -64,6 +64,7 @@ export class StakingRouterFetchService { lastDepositBlock: stakingModule.lastDepositBlock.toNumber(), exitedValidatorsCount: stakingModule.exitedValidatorsCount.toNumber(), active: isActive, + lastChangedBlockHash: '', }; }), ); diff --git a/src/staking-router-modules/interfaces/staking-module.interface.ts b/src/staking-router-modules/interfaces/staking-module.interface.ts index ac77a3ea..83a8ceb5 100644 --- a/src/staking-router-modules/interfaces/staking-module.interface.ts +++ b/src/staking-router-modules/interfaces/staking-module.interface.ts @@ -27,6 +27,8 @@ export interface StakingModule { type: string; //STAKING_MODULE_TYPE; // is module active active: boolean; + // last changed block hash + lastChangedBlockHash: string; } export interface StakingModuleInterface { diff --git a/src/storage/sr-module.storage.e2e-spec.ts b/src/storage/sr-module.storage.e2e-spec.ts index 20e79403..7dd0d392 100644 --- a/src/storage/sr-module.storage.e2e-spec.ts +++ b/src/storage/sr-module.storage.e2e-spec.ts @@ -35,7 +35,7 @@ describe('Staking Module Storage', () => { test('add new module in empty database', async () => { const nonce = 1; - await srModuleStorageService.upsert(curatedModule, nonce); + await srModuleStorageService.upsert(curatedModule, nonce, ''); const updatesStakingModules0 = await srModuleStorageService.findAll(); const stakingModule0 = updatesStakingModules0[0]; @@ -56,7 +56,7 @@ describe('Staking Module Storage', () => { expect(stakingModule0.active).toEqual(curatedModule.active); const dvtNonce = 2; - await srModuleStorageService.upsert(dvtModule, dvtNonce); + await srModuleStorageService.upsert(dvtModule, dvtNonce, ''); const updatesStakingModules1 = await srModuleStorageService.findAll(); expect(updatesStakingModules1.length).toEqual(2); const stakingModule1 = updatesStakingModules1[1]; @@ -78,7 +78,7 @@ describe('Staking Module Storage', () => { test('update existing module', async () => { const nonce = 1; - await srModuleStorageService.upsert(curatedModule, nonce); + await srModuleStorageService.upsert(curatedModule, nonce, ''); const initialStakingModules = await srModuleStorageService.findAll(); const initialStakingModulesAmount = initialStakingModules.length; const stakingModule0 = initialStakingModules[0]; @@ -99,7 +99,7 @@ describe('Staking Module Storage', () => { expect(stakingModule0.active).toEqual(curatedModule.active); const updatedNonce = 12; - await srModuleStorageService.upsert(updatedCuratedModule, updatedNonce); + await srModuleStorageService.upsert(updatedCuratedModule, updatedNonce, ''); const updatedStakingModules = await srModuleStorageService.findAll(); const updatedStakingModulesAmount = updatedStakingModules.length; @@ -129,8 +129,8 @@ describe('Staking Module Storage', () => { test('check search by contract address', async () => { const nonce = 1; - await srModuleStorageService.upsert(curatedModule, nonce); - await srModuleStorageService.upsert(dvtModule, nonce); + await srModuleStorageService.upsert(curatedModule, nonce, ''); + await srModuleStorageService.upsert(dvtModule, nonce, ''); const curatedModuleDb = await srModuleStorageService.findOneByContractAddress(curatedModule.stakingModuleAddress); const dvtModuleDb = await srModuleStorageService.findOneByContractAddress(dvtModule.stakingModuleAddress); @@ -143,6 +143,7 @@ describe('Staking Module Storage', () => { await srModuleStorageService.upsert( { ...curatedModule, stakingModuleAddress: '0xDC64A140AA3E981100A9BECA4E685F962F0CF6C9' }, nonce, + '', ); const stakingModuleNull = await srModuleStorageService.findOneByContractAddress( '0xDC64A140AA3E981100A9BECA4E685F962F0CF6C9', From 60efe6326053a5eb9c50d13d1f0cf17a46ac52b5 Mon Sep 17 00:00:00 2001 From: Eddort Date: Wed, 20 Dec 2023 23:39:55 +0100 Subject: [PATCH 23/48] fix: reorg spec --- src/jobs/keys-update/test/update-cases.spec.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/jobs/keys-update/test/update-cases.spec.ts b/src/jobs/keys-update/test/update-cases.spec.ts index dc990a65..1e6a7aad 100644 --- a/src/jobs/keys-update/test/update-cases.spec.ts +++ b/src/jobs/keys-update/test/update-cases.spec.ts @@ -151,8 +151,11 @@ describe('update cases', () => { jest .spyOn(executionProviderService, 'getFullBlock') - .mockImplementation(async () => ({ number: 2, hash: '0x2', timestamp: 1, parentHash: '0x1' } as any)); + .mockImplementationOnce(async () => ({ number: 2, hash: '0x2', timestamp: 1, parentHash: '0x111' } as any)); + jest + .spyOn(executionProviderService, 'getFullBlock') + .mockImplementationOnce(async () => ({ number: 2, hash: '0x1', timestamp: 1, parentHash: '0x111' } as any)); await updaterService.updateStakingModules({ currElMeta: { number: 2, hash: '0x2', timestamp: 1 }, prevElMeta: { blockNumber: 2, blockHash: '0x1', timestamp: 1 }, From 8f8bb45d17279a7802769547cff8f39a1935894b Mon Sep 17 00:00:00 2001 From: Eddort Date: Wed, 20 Dec 2023 23:47:35 +0100 Subject: [PATCH 24/48] fix: sql e2e spec --- .../test/validator-registry/connect.e2e-spec.ts | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/src/common/registry/test/validator-registry/connect.e2e-spec.ts b/src/common/registry/test/validator-registry/connect.e2e-spec.ts index c4ae0b7d..36f294e3 100644 --- a/src/common/registry/test/validator-registry/connect.e2e-spec.ts +++ b/src/common/registry/test/validator-registry/connect.e2e-spec.ts @@ -5,8 +5,7 @@ import { BatchProviderModule, ExtendedJsonRpcBatchProvider } from '@lido-nestjs/ import { ValidatorRegistryModule, ValidatorRegistryService, RegistryStorageService } from '../../'; -import { clearDb, compareTestOperators } from '../testing.utils'; - +import { clearDb, compareTestOperators, mikroORMConfig } from '../testing.utils'; import { operators } from '../fixtures/connect.fixture'; import { MikroORM } from '@mikro-orm/core'; import { REGISTRY_CONTRACT_ADDRESSES } from '@lido-nestjs/contracts'; @@ -31,12 +30,7 @@ describe('Registry', () => { beforeEach(async () => { const imports = [ - MikroOrmModule.forRoot({ - dbName: ':memory:', - type: 'sqlite', - allowGlobalContext: true, - entities: ['./**/*.entity.ts'], - }), + MikroOrmModule.forRoot(mikroORMConfig), BatchProviderModule.forRoot({ url: process.env.PROVIDERS_URLS as string, requestPolicy: { From ac084c59cab2e3c0f2e64903ea0a04481bcc67e6 Mon Sep 17 00:00:00 2001 From: Eddort Date: Thu, 21 Dec 2023 12:19:50 +0100 Subject: [PATCH 25/48] test: debug e2e --- src/common/registry/test/key-registry/connect.e2e-spec.ts | 2 +- src/common/registry/test/validator-registry/connect.e2e-spec.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/common/registry/test/key-registry/connect.e2e-spec.ts b/src/common/registry/test/key-registry/connect.e2e-spec.ts index 84cef1a2..63e3c898 100644 --- a/src/common/registry/test/key-registry/connect.e2e-spec.ts +++ b/src/common/registry/test/key-registry/connect.e2e-spec.ts @@ -56,7 +56,7 @@ describe('Registry', () => { await storageService.onModuleDestroy(); }); - test('Update', async () => { + test.skip('Update', async () => { const blockHash = '0x4ef0f15a8a04a97f60a9f76ba83d27bcf98dac9635685cd05fe1d78bd6e93418'; await registryService.update(address, blockHash); diff --git a/src/common/registry/test/validator-registry/connect.e2e-spec.ts b/src/common/registry/test/validator-registry/connect.e2e-spec.ts index 36f294e3..8bad0ade 100644 --- a/src/common/registry/test/validator-registry/connect.e2e-spec.ts +++ b/src/common/registry/test/validator-registry/connect.e2e-spec.ts @@ -62,7 +62,7 @@ describe('Registry', () => { await storageService.onModuleDestroy(); }); - test('Update', async () => { + test.skip('Update', async () => { const blockHash = '0x4ef0f15a8a04a97f60a9f76ba83d27bcf98dac9635685cd05fe1d78bd6e93418'; await registryService.update(address, blockHash); From 91ef826a05b2a3228b66e3673a26b74626907e30 Mon Sep 17 00:00:00 2001 From: Eddort Date: Thu, 21 Dec 2023 12:30:39 +0100 Subject: [PATCH 26/48] fix: chronix tests --- src/app/simple-dvt-deploy.e2e-chain.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/app/simple-dvt-deploy.e2e-chain.ts b/src/app/simple-dvt-deploy.e2e-chain.ts index 37a2c817..a47f75b4 100644 --- a/src/app/simple-dvt-deploy.e2e-chain.ts +++ b/src/app/simple-dvt-deploy.e2e-chain.ts @@ -9,7 +9,7 @@ import { RegistryKeyStorageService } from '../common/registry'; import { ElMetaStorageService } from '../storage/el-meta.storage'; import { SRModuleStorageService } from '../storage/sr-module.storage'; import { KeysUpdateService } from '../jobs/keys-update'; -import { ExecutionProvider, ExecutionProviderService } from '../common/execution-provider'; +import { ExecutionProvider } from '../common/execution-provider'; import { ConfigService } from '../common/config'; import { PrometheusService } from '../common/prometheus'; import { StakingRouterService } from '../staking-router-modules/staking-router.service'; @@ -64,8 +64,6 @@ describe('Simple DVT deploy', () => { }); moduleRef = await Test.createTestingModule({ imports: [AppModule] }) - .overrideProvider(ExecutionProviderService) - .useValue(session.provider) .overrideProvider(SimpleFallbackJsonRpcBatchProvider) .useValue(session.provider) .overrideProvider(ExecutionProvider) @@ -89,7 +87,9 @@ describe('Simple DVT deploy', () => { .overrideProvider(ConfigService) .useValue({ get(path) { - const conf = { LIDO_LOCATOR_ADDRESS: '0xC1d0b3DE6792Bf6b4b37EccdcC24e45978Cfd2Eb' }; + const conf = { + LIDO_LOCATOR_ADDRESS: '0xC1d0b3DE6792Bf6b4b37EccdcC24e45978Cfd2Eb', + }; return conf[path]; }, }) From 05d25535359b01f02d315fabe5fc4a8369628f73 Mon Sep 17 00:00:00 2001 From: Eddort Date: Thu, 21 Dec 2023 18:38:43 +0100 Subject: [PATCH 27/48] fix: e2e connects tests --- src/common/registry/fetch/operator.fetch.ts | 23 +++++++++++++++---- .../test/key-registry/connect.e2e-spec.ts | 13 +++++++---- .../validator-registry/connect.e2e-spec.ts | 19 +++++++++++---- 3 files changed, 42 insertions(+), 13 deletions(-) diff --git a/src/common/registry/fetch/operator.fetch.ts b/src/common/registry/fetch/operator.fetch.ts index 223e7090..72fcf4ed 100644 --- a/src/common/registry/fetch/operator.fetch.ts +++ b/src/common/registry/fetch/operator.fetch.ts @@ -59,12 +59,27 @@ export class RegistryOperatorFetchService { return false; } + /** return blockTag for finalized block, it need for testing purposes */ + public getFinalizedBlockTag() { + return 'finalized'; + } + /** fetches number of operators */ public async count(moduleAddress: string, overrides: CallOverrides = {}): Promise { const bigNumber = await this.getContract(moduleAddress).getNodeOperatorsCount(overrides as any); return bigNumber.toNumber(); } + /** fetches finalized operator */ + public async getFinalizedNodeOperator(moduleAddress: string, operatorIndex: number) { + const fullInfo = true; + const contract = this.getContract(moduleAddress); + const finalizedOperator = await contract.getNodeOperator(operatorIndex, fullInfo, { + blockTag: this.getFinalizedBlockTag(), + }); + return finalizedOperator; + } + /** fetches one operator */ public async fetchOne( moduleAddress: string, @@ -75,9 +90,6 @@ export class RegistryOperatorFetchService { const contract = this.getContract(moduleAddress); const operator = await contract.getNodeOperator(operatorIndex, fullInfo, overrides as any); - const finalizedOperator = await contract.getNodeOperator(operatorIndex, fullInfo, { - blockTag: 'finalized', - }); const { name, @@ -89,7 +101,10 @@ export class RegistryOperatorFetchService { totalDepositedValidators, } = operator; - const { totalDepositedValidators: finalizedUsedSigningKeys } = finalizedOperator; + const { totalDepositedValidators: finalizedUsedSigningKeys } = await this.getFinalizedNodeOperator( + moduleAddress, + operatorIndex, + ); return { index: operatorIndex, diff --git a/src/common/registry/test/key-registry/connect.e2e-spec.ts b/src/common/registry/test/key-registry/connect.e2e-spec.ts index 63e3c898..7f407314 100644 --- a/src/common/registry/test/key-registry/connect.e2e-spec.ts +++ b/src/common/registry/test/key-registry/connect.e2e-spec.ts @@ -2,7 +2,7 @@ import { Test } from '@nestjs/testing'; import { MikroOrmModule } from '@mikro-orm/nestjs'; import { nullTransport, LoggerModule } from '@lido-nestjs/logger'; import { BatchProviderModule, ExtendedJsonRpcBatchProvider } from '@lido-nestjs/execution'; -import { KeyRegistryModule, KeyRegistryService, RegistryStorageService } from '../../'; +import { KeyRegistryModule, KeyRegistryService, RegistryOperatorFetchService, RegistryStorageService } from '../../'; import { clearDb, compareTestOperators, mikroORMConfig } from '../testing.utils'; import { operators } from '../fixtures/connect.fixture'; import { MikroORM } from '@mikro-orm/core'; @@ -14,6 +14,7 @@ dotenv.config(); describe('Registry', () => { let registryService: KeyRegistryService; let storageService: RegistryStorageService; + let registryOperatorFetchService: RegistryOperatorFetchService; let mikroOrm: MikroORM; if (!process.env.CHAIN_ID) { console.error("CHAIN_ID wasn't provides"); @@ -24,6 +25,8 @@ describe('Registry', () => { return { ...key, moduleAddress: address }; }); + const blockHash = '0x4ef0f15a8a04a97f60a9f76ba83d27bcf98dac9635685cd05fe1d78bd6e93418'; + beforeEach(async () => { const imports = [ MikroOrmModule.forRoot(mikroORMConfig), @@ -46,7 +49,11 @@ describe('Registry', () => { const moduleRef = await Test.createTestingModule({ imports }).compile(); registryService = moduleRef.get(KeyRegistryService); storageService = moduleRef.get(RegistryStorageService); + registryOperatorFetchService = moduleRef.get(RegistryOperatorFetchService); mikroOrm = moduleRef.get(MikroORM); + + jest.spyOn(registryOperatorFetchService, 'getFinalizedBlockTag').mockImplementation(() => ({ blockHash } as any)); + const generator = mikroOrm.getSchemaGenerator(); await generator.updateSchema(); }); @@ -56,9 +63,7 @@ describe('Registry', () => { await storageService.onModuleDestroy(); }); - test.skip('Update', async () => { - const blockHash = '0x4ef0f15a8a04a97f60a9f76ba83d27bcf98dac9635685cd05fe1d78bd6e93418'; - + test('Update', async () => { await registryService.update(address, blockHash); await compareTestOperators(address, registryService, { diff --git a/src/common/registry/test/validator-registry/connect.e2e-spec.ts b/src/common/registry/test/validator-registry/connect.e2e-spec.ts index 8bad0ade..c25644a6 100644 --- a/src/common/registry/test/validator-registry/connect.e2e-spec.ts +++ b/src/common/registry/test/validator-registry/connect.e2e-spec.ts @@ -3,7 +3,12 @@ import { MikroOrmModule } from '@mikro-orm/nestjs'; import { nullTransport, LoggerModule } from '@lido-nestjs/logger'; import { BatchProviderModule, ExtendedJsonRpcBatchProvider } from '@lido-nestjs/execution'; -import { ValidatorRegistryModule, ValidatorRegistryService, RegistryStorageService } from '../../'; +import { + ValidatorRegistryModule, + ValidatorRegistryService, + RegistryStorageService, + RegistryOperatorFetchService, +} from '../../'; import { clearDb, compareTestOperators, mikroORMConfig } from '../testing.utils'; import { operators } from '../fixtures/connect.fixture'; @@ -15,6 +20,7 @@ dotenv.config(); describe('Registry', () => { let registryService: ValidatorRegistryService; + let registryOperatorFetchService: RegistryOperatorFetchService; let mikroOrm: MikroORM; let storageService: RegistryStorageService; @@ -28,6 +34,8 @@ describe('Registry', () => { return { ...key, moduleAddress: address }; }); + const blockHash = '0x4ef0f15a8a04a97f60a9f76ba83d27bcf98dac9635685cd05fe1d78bd6e93418'; + beforeEach(async () => { const imports = [ MikroOrmModule.forRoot(mikroORMConfig), @@ -50,8 +58,11 @@ describe('Registry', () => { const moduleRef = await Test.createTestingModule({ imports }).compile(); registryService = moduleRef.get(ValidatorRegistryService); storageService = moduleRef.get(RegistryStorageService); - + registryOperatorFetchService = moduleRef.get(RegistryOperatorFetchService); mikroOrm = moduleRef.get(MikroORM); + + jest.spyOn(registryOperatorFetchService, 'getFinalizedBlockTag').mockImplementation(() => ({ blockHash } as any)); + const generator = mikroOrm.getSchemaGenerator(); await generator.updateSchema(); }); @@ -62,9 +73,7 @@ describe('Registry', () => { await storageService.onModuleDestroy(); }); - test.skip('Update', async () => { - const blockHash = '0x4ef0f15a8a04a97f60a9f76ba83d27bcf98dac9635685cd05fe1d78bd6e93418'; - + test('Update', async () => { await registryService.update(address, blockHash); await compareTestOperators(address, registryService, { From 66ccb6685af1eeea99d8e9ac5f29664375a8ec27 Mon Sep 17 00:00:00 2001 From: Eddort Date: Thu, 21 Dec 2023 18:57:57 +0100 Subject: [PATCH 28/48] fix: 425 error while fetching modules --- src/staking-router-modules/staking-router.service.ts | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/staking-router-modules/staking-router.service.ts b/src/staking-router-modules/staking-router.service.ts index 89659d43..bf4e27d5 100644 --- a/src/staking-router-modules/staking-router.service.ts +++ b/src/staking-router-modules/staking-router.service.ts @@ -70,11 +70,12 @@ export class StakingRouterService { const { stakingModules, elBlockSnapshot } = await this.entityManager.transactional( async () => { const stakingModules = await this.getStakingModules(stakingModuleAddresses); - - if (stakingModules.length === 0) { - this.logger.warn("No staking modules in list. Maybe didn't fetched from SR yet"); - throw httpExceptionTooEarlyResp(); - } + // TODO: Can I just delete this check? + // TODO: approve from Anna + // if (stakingModules.length === 0) { + // this.logger.warn("No staking modules in list. Maybe didn't fetched from SR yet"); + // throw httpExceptionTooEarlyResp(); + // } const elBlockSnapshot = await this.getElBlockSnapshot(); From 959980086af3b67ee044d7a683b1d0eb78077284 Mon Sep 17 00:00:00 2001 From: Eddort Date: Thu, 21 Dec 2023 19:05:10 +0100 Subject: [PATCH 29/48] fix: 425 http error --- src/staking-router-modules/staking-router.service.ts | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/staking-router-modules/staking-router.service.ts b/src/staking-router-modules/staking-router.service.ts index bf4e27d5..6bcb0552 100644 --- a/src/staking-router-modules/staking-router.service.ts +++ b/src/staking-router-modules/staking-router.service.ts @@ -70,12 +70,12 @@ export class StakingRouterService { const { stakingModules, elBlockSnapshot } = await this.entityManager.transactional( async () => { const stakingModules = await this.getStakingModules(stakingModuleAddresses); - // TODO: Can I just delete this check? - // TODO: approve from Anna - // if (stakingModules.length === 0) { - // this.logger.warn("No staking modules in list. Maybe didn't fetched from SR yet"); - // throw httpExceptionTooEarlyResp(); - // } + + // If the target query involves retrieving module-specific data, we do not throw the 425 exception + if (stakingModules.length === 0 && !stakingModuleAddresses) { + this.logger.warn("No staking modules in list. Maybe didn't fetched from SR yet"); + throw httpExceptionTooEarlyResp(); + } const elBlockSnapshot = await this.getElBlockSnapshot(); From e9d22498cce21996a32b69bb4ddf48b040f8693e Mon Sep 17 00:00:00 2001 From: Eddort Date: Thu, 21 Dec 2023 23:32:43 +0100 Subject: [PATCH 30/48] fix: reorg detection --- src/jobs/keys-update/staking-module-updater.service.ts | 3 +++ src/jobs/keys-update/test/detect-reorg.spec.ts | 6 +++--- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/src/jobs/keys-update/staking-module-updater.service.ts b/src/jobs/keys-update/staking-module-updater.service.ts index 544632d5..aad8ad25 100644 --- a/src/jobs/keys-update/staking-module-updater.service.ts +++ b/src/jobs/keys-update/staking-module-updater.service.ts @@ -175,6 +175,9 @@ export class StakingModuleUpdaterService { }), ); + if (blocks[0].hash !== prevBlockHash) return true; + if (blocks[blocks.length - 1].hash !== currentBlockHash) return true; + for (let i = 1; i < blocks.length; i++) { const previousBlock = blocks[i - 1]; const currentBlock = blocks[i]; diff --git a/src/jobs/keys-update/test/detect-reorg.spec.ts b/src/jobs/keys-update/test/detect-reorg.spec.ts index 1230d512..ab7a7521 100644 --- a/src/jobs/keys-update/test/detect-reorg.spec.ts +++ b/src/jobs/keys-update/test/detect-reorg.spec.ts @@ -117,7 +117,7 @@ describe('detect reorg', () => { it('check blockchain (happy pass)', async () => { const updaterState: UpdaterState = { - lastChangedBlockHash: '0x1', + lastChangedBlockHash: '0x0', isReorgDetected: false, }; @@ -139,7 +139,7 @@ describe('detect reorg', () => { } as any), ); - expect(await updaterService.isReorgDetected(updaterState, '0x1', '0x1')).toBeFalsy(); + expect(await updaterService.isReorgDetected(updaterState, '0x1', '0x100')).toBeFalsy(); expect(updaterState.isReorgDetected).toBeFalsy(); }); @@ -167,7 +167,7 @@ describe('detect reorg', () => { } as any), ); - expect(await updaterService.isReorgDetected(updaterState, '0x1', '0x1')).toBeTruthy(); + expect(await updaterService.isReorgDetected(updaterState, '0x1', '0x100')).toBeTruthy(); expect(updaterState.isReorgDetected).toBeTruthy(); }); }); From d72df8265a090cb89c83b6f5dc515c127f23e821 Mon Sep 17 00:00:00 2001 From: Eddort Date: Thu, 21 Dec 2023 23:42:30 +0100 Subject: [PATCH 31/48] refactor: better logs --- src/jobs/keys-update/keys-update.service.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/jobs/keys-update/keys-update.service.ts b/src/jobs/keys-update/keys-update.service.ts index f5794111..a6cd5ee8 100644 --- a/src/jobs/keys-update/keys-update.service.ts +++ b/src/jobs/keys-update/keys-update.service.ts @@ -124,7 +124,7 @@ export class KeysUpdateService { } if (prevElMeta?.blockHash && prevElMeta.blockHash === currElMeta.hash) { - this.logger.debug?.('same state, skip', { prevElMeta, currElMeta }); + this.logger.log('Same blockHash, indexing is not required', { prevElMeta, currElMeta }); return; } From 754d6a490d026a7a0445eb2046ae9e6f39c8e446 Mon Sep 17 00:00:00 2001 From: Eddort Date: Sat, 23 Dec 2023 16:26:50 +0100 Subject: [PATCH 32/48] test: reorg handling in registry module --- src/common/registry/main/abstract-registry.ts | 4 +- .../test/key-registry/registry-update.spec.ts | 17 +-- src/common/registry/test/mock-utils.ts | 9 +- .../registry-update.spec.ts | 106 +++++++++++++++--- 4 files changed, 100 insertions(+), 36 deletions(-) diff --git a/src/common/registry/main/abstract-registry.ts b/src/common/registry/main/abstract-registry.ts index 99ebe7d5..5564a093 100644 --- a/src/common/registry/main/abstract-registry.ts +++ b/src/common/registry/main/abstract-registry.ts @@ -108,10 +108,10 @@ export abstract class AbstractRegistryService { const prevOperator = previousOperators[currentIndex] ?? null; const isSameOperator = compareOperators(prevOperator, currOperator); + const finalizedUsedSigningKeys = prevOperator ? prevOperator.finalizedUsedSigningKeys : null; // skip updating keys from 0 to `usedSigningKeys` of previous collected data // since the contract guarantees that these keys cannot be changed - const unchangedKeysMaxIndex = - isSameOperator && prevOperator.finalizedUsedSigningKeys ? prevOperator.finalizedUsedSigningKeys : 0; + const unchangedKeysMaxIndex = isSameOperator && finalizedUsedSigningKeys ? finalizedUsedSigningKeys : 0; // get the right border up to which the keys should be updated // it's different for different scenarios const toIndex = this.getToIndex(currOperator); diff --git a/src/common/registry/test/key-registry/registry-update.spec.ts b/src/common/registry/test/key-registry/registry-update.spec.ts index 7661bebf..90f8d47e 100644 --- a/src/common/registry/test/key-registry/registry-update.spec.ts +++ b/src/common/registry/test/key-registry/registry-update.spec.ts @@ -31,6 +31,8 @@ describe('Registry', () => { const CHAIN_ID = process.env.CHAIN_ID || 1; const address = REGISTRY_CONTRACT_ADDRESSES[CHAIN_ID]; + const blockHash = '0x4ef0f15a8a04a97f60a9f76ba83d27bcf98dac9635685cd05fe1d78bd6e93418'; + const keysWithModuleAddress = keys.map((key) => { return { ...key, moduleAddress: address }; }); @@ -90,8 +92,6 @@ describe('Registry', () => { operators: operatorsWithModuleAddress, }); - const blockHash = '0x4ef0f15a8a04a97f60a9f76ba83d27bcf98dac9635685cd05fe1d78bd6e93418'; - await registryService.update(address, blockHash); expect(saveOperatorsRegistryMock).toBeCalledTimes(1); // 2 - number of operators @@ -116,8 +116,6 @@ describe('Registry', () => { operators: operatorsWithModuleAddress, }); - const blockHash = '0x4ef0f15a8a04a97f60a9f76ba83d27bcf98dac9635685cd05fe1d78bd6e93418'; - await registryService.update(address, blockHash); expect(saveOperatorsRegistryMock).toBeCalledTimes(1); expect(saveKeyRegistryMock.mock.calls.length).toBeGreaterThanOrEqual(1); @@ -139,8 +137,6 @@ describe('Registry', () => { operators: newOperators, }); - const blockHash = '0x4ef0f15a8a04a97f60a9f76ba83d27bcf98dac9635685cd05fe1d78bd6e93418'; - await registryService.update(address, blockHash); expect(saveOperatorsRegistryMock).toBeCalledTimes(1); expect(saveKeyRegistryMock.mock.calls.length).toBeGreaterThanOrEqual(1); @@ -163,8 +159,6 @@ describe('Registry', () => { operators: newOperators, }); - const blockHash = '0x4ef0f15a8a04a97f60a9f76ba83d27bcf98dac9635685cd05fe1d78bd6e93418'; - await registryService.update(address, blockHash); expect(saveOperatorRegistryMock).toBeCalledTimes(1); expect(saveKeyRegistryMock.mock.calls.length).toBeGreaterThanOrEqual(1); @@ -189,8 +183,6 @@ describe('Registry', () => { operators: newOperators, }); - const blockHash = '0x4ef0f15a8a04a97f60a9f76ba83d27bcf98dac9635685cd05fe1d78bd6e93418'; - await registryService.update(address, blockHash); expect(saveOperatorsRegistryMock).toBeCalledTimes(1); expect(saveKeyRegistryMock.mock.calls.length).toBeGreaterThanOrEqual(1); @@ -213,8 +205,6 @@ describe('Registry', () => { operators: newOperators, }); - const blockHash = '0x4ef0f15a8a04a97f60a9f76ba83d27bcf98dac9635685cd05fe1d78bd6e93418'; - await registryService.update(address, blockHash); expect(saveOperatorsRegistryMock).toBeCalledTimes(1); expect(saveKeyRegistryMock.mock.calls.length).toBeGreaterThanOrEqual(1); @@ -235,7 +225,6 @@ describe('Registry', () => { keys: keysWithModuleAddress, operators: newOperators, }); - const blockHash = '0x4ef0f15a8a04a97f60a9f76ba83d27bcf98dac9635685cd05fe1d78bd6e93418'; await registryService.update(address, blockHash); expect(saveOperatorsRegistryMock).toBeCalledTimes(1); @@ -286,8 +275,6 @@ describe('Registry', () => { operators: newOperators, }); - const blockHash = '0x4ef0f15a8a04a97f60a9f76ba83d27bcf98dac9635685cd05fe1d78bd6e93418'; - await registryService.update(address, blockHash); expect(saveOperatorsRegistryMock).toBeCalledTimes(1); expect(saveKeyRegistryMock.mock.calls.length).toBeGreaterThanOrEqual(1); diff --git a/src/common/registry/test/mock-utils.ts b/src/common/registry/test/mock-utils.ts index 955d1cae..b76912d6 100644 --- a/src/common/registry/test/mock-utils.ts +++ b/src/common/registry/test/mock-utils.ts @@ -19,12 +19,17 @@ export const registryServiceMock = ( { keys, operators }: Payload, ) => { const fetchBatchKey = moduleRef.get(RegistryKeyBatchFetchService); - jest + const fetchSigningKeysInBatchesMock = jest .spyOn(fetchBatchKey, 'fetchSigningKeysInBatches') .mockImplementation(async (moduleAddress, operatorIndex, fromIndex, totalAmount) => { return findKeys(keys, operatorIndex, fromIndex, totalAmount); }); const operatorFetch = moduleRef.get(RegistryOperatorFetchService); - jest.spyOn(operatorFetch, 'fetch').mockImplementation(async () => operators); + const operatorsMock = jest.spyOn(operatorFetch, 'fetch').mockImplementation(async () => operators); + + return () => { + fetchSigningKeysInBatchesMock.mockReset(); + operatorsMock.mockReset(); + }; }; diff --git a/src/common/registry/test/validator-registry/registry-update.spec.ts b/src/common/registry/test/validator-registry/registry-update.spec.ts index 5fe136a4..9b307ff9 100644 --- a/src/common/registry/test/validator-registry/registry-update.spec.ts +++ b/src/common/registry/test/validator-registry/registry-update.spec.ts @@ -23,6 +23,8 @@ import { registryServiceMock } from '../mock-utils'; import { MikroORM } from '@mikro-orm/core'; import { REGISTRY_CONTRACT_ADDRESSES } from '@lido-nestjs/contracts'; +const blockHash = '0x4ef0f15a8a04a97f60a9f76ba83d27bcf98dac9635685cd05fe1d78bd6e93418'; + describe('Validator registry', () => { const provider = new JsonRpcBatchProvider(process.env.PROVIDERS_URLS); const CHAIN_ID = process.env.CHAIN_ID || 1; @@ -87,8 +89,6 @@ describe('Validator registry', () => { operators: operatorsWithModuleAddress, }); - const blockHash = '0x4ef0f15a8a04a97f60a9f76ba83d27bcf98dac9635685cd05fe1d78bd6e93418'; - await registryService.update(address, blockHash); // update function doesn't make a decision about update no more // so here would happen update if list of keys was changed @@ -110,8 +110,6 @@ describe('Validator registry', () => { operators: operatorsWithModuleAddress, }); - const blockHash = '0x4ef0f15a8a04a97f60a9f76ba83d27bcf98dac9635685cd05fe1d78bd6e93418'; - await registryService.update(address, blockHash); expect(saveRegistryMock).toBeCalledTimes(1); expect(saveKeyRegistryMock).toBeCalledTimes(2); @@ -129,8 +127,6 @@ describe('Validator registry', () => { operators: operatorsWithModuleAddress, }); - const blockHash = '0x4ef0f15a8a04a97f60a9f76ba83d27bcf98dac9635685cd05fe1d78bd6e93418'; - await registryService.update(address, blockHash); expect(saveOperatorsRegistryMock).toBeCalledTimes(1); expect(saveKeyRegistryMock.mock.calls.length).toBeGreaterThanOrEqual(1); @@ -151,8 +147,6 @@ describe('Validator registry', () => { operators: newOperators, }); - const blockHash = '0x4ef0f15a8a04a97f60a9f76ba83d27bcf98dac9635685cd05fe1d78bd6e93418'; - await registryService.update(address, blockHash); expect(saveOperatorsRegistryMock).toBeCalledTimes(1); expect(saveKeyRegistryMock.mock.calls.length).toBeGreaterThanOrEqual(1); @@ -172,8 +166,6 @@ describe('Validator registry', () => { operators: newOperators, }); - const blockHash = '0x4ef0f15a8a04a97f60a9f76ba83d27bcf98dac9635685cd05fe1d78bd6e93418'; - await registryService.update(address, blockHash); expect(saveRegistryMock).toBeCalledTimes(1); expect(saveKeyRegistryMock.mock.calls.length).toBeGreaterThanOrEqual(1); @@ -197,8 +189,6 @@ describe('Validator registry', () => { operators: newOperators, }); - const blockHash = '0x4ef0f15a8a04a97f60a9f76ba83d27bcf98dac9635685cd05fe1d78bd6e93418'; - await registryService.update(address, blockHash); expect(saveOperatorRegistryMock).toBeCalledTimes(1); expect(saveKeyRegistryMock.mock.calls.length).toBeGreaterThanOrEqual(1); @@ -224,8 +214,6 @@ describe('Validator registry', () => { operators: newOperators, }); - const blockHash = '0x4ef0f15a8a04a97f60a9f76ba83d27bcf98dac9635685cd05fe1d78bd6e93418'; - await registryService.update(address, blockHash); expect(saveRegistryMock).toBeCalledTimes(1); expect(saveKeyRegistryMock.mock.calls.length).toBeGreaterThanOrEqual(1); @@ -246,8 +234,6 @@ describe('Validator registry', () => { operators: newOperators, }); - const blockHash = '0x4ef0f15a8a04a97f60a9f76ba83d27bcf98dac9635685cd05fe1d78bd6e93418'; - await registryService.update(address, blockHash); expect(saveOperatorRegistryMock).toBeCalledTimes(1); expect(saveKeyRegistryMock.mock.calls.length).toBeGreaterThanOrEqual(1); @@ -327,7 +313,6 @@ describe('Empty registry', () => { keys: keysWithModuleAddress, operators: operatorsWithModuleAddress, }); - const blockHash = '0x4ef0f15a8a04a97f60a9f76ba83d27bcf98dac9635685cd05fe1d78bd6e93418'; await registryService.update(address, blockHash); expect(saveRegistryMock).toBeCalledTimes(1); @@ -339,3 +324,90 @@ describe('Empty registry', () => { await registryService.update(address, blockHash); }); }); + +describe('Reorg detection', () => { + const provider = new JsonRpcBatchProvider(process.env.PROVIDERS_URLS); + let registryService: ValidatorRegistryService; + let registryStorageService: RegistryStorageService; + let moduleRef: TestingModule; + const mockCall = jest.spyOn(provider, 'call').mockImplementation(async () => ''); + const CHAIN_ID = process.env.CHAIN_ID || 1; + const address = REGISTRY_CONTRACT_ADDRESSES[CHAIN_ID]; + let mikroOrm: MikroORM; + + jest.spyOn(provider, 'detectNetwork').mockImplementation(async () => getNetwork('mainnet')); + + beforeEach(async () => { + const imports = [ + MikroOrmModule.forRoot(mikroORMConfig), + MockLoggerModule.forRoot({ + log: jest.fn(), + error: jest.fn(), + warn: jest.fn(), + }), + ValidatorRegistryModule.forFeature({ provider }), + ]; + moduleRef = await Test.createTestingModule({ + imports, + providers: [{ provide: LOGGER_PROVIDER, useValue: {} }], + }).compile(); + registryService = moduleRef.get(ValidatorRegistryService); + registryStorageService = moduleRef.get(RegistryStorageService); + mikroOrm = moduleRef.get(MikroORM); + const generator = mikroOrm.getSchemaGenerator(); + await generator.updateSchema(); + }); + + afterEach(async () => { + mockCall.mockReset(); + await clearDb(mikroOrm); + await registryStorageService.onModuleDestroy(); + }); + + test('init on update', async () => { + const saveRegistryMock = jest.spyOn(registryService, 'saveOperators'); + const saveKeyRegistryMock = jest.spyOn(registryService, 'saveKeys'); + const finalizedUsedSigningKeys = 1; + + const keysWithModuleAddress = keys.map((key) => { + return { ...key, moduleAddress: address }; + }); + + const operatorsWithModuleAddress = operators.map((key) => { + return { ...key, moduleAddress: address, finalizedUsedSigningKeys }; + }); + + const unrefMock = registryServiceMock(moduleRef, provider, { + keys: keysWithModuleAddress, + operators: operatorsWithModuleAddress, + }); + + await registryService.update(address, blockHash); + + expect(saveRegistryMock).toBeCalledTimes(1); + expect(saveKeyRegistryMock.mock.calls.length).toBeGreaterThanOrEqual(1); + + await compareTestKeysAndOperators(address, registryService, { + keys: keysWithModuleAddress, + operators: operatorsWithModuleAddress, + }); + + unrefMock(); + + const keysWithSpoiledLeftEdge = clone(keysWithModuleAddress).map((key) => + key.index >= finalizedUsedSigningKeys ? { ...key } : { ...key, key: '', depositSignature: '' }, + ); + + registryServiceMock(moduleRef, provider, { + keys: keysWithSpoiledLeftEdge, + operators: operatorsWithModuleAddress, + }); + + await registryService.update(address, blockHash); + + await compareTestKeysAndOperators(address, registryService, { + keys: keysWithModuleAddress, + operators: operatorsWithModuleAddress, + }); + }); +}); From 998c17a31214c3a0de287471db8dfaf6689ce8dd Mon Sep 17 00:00:00 2001 From: Eddort Date: Sun, 24 Dec 2023 14:27:04 +0100 Subject: [PATCH 33/48] test: key registry --- .../test/key-registry/registry-update.spec.ts | 95 ++++++++++++++++++- .../registry-update.spec.ts | 4 +- 2 files changed, 95 insertions(+), 4 deletions(-) diff --git a/src/common/registry/test/key-registry/registry-update.spec.ts b/src/common/registry/test/key-registry/registry-update.spec.ts index 90f8d47e..0ece5c5c 100644 --- a/src/common/registry/test/key-registry/registry-update.spec.ts +++ b/src/common/registry/test/key-registry/registry-update.spec.ts @@ -1,6 +1,6 @@ import { Test, TestingModule } from '@nestjs/testing'; import { MikroOrmModule } from '@mikro-orm/nestjs'; -import { nullTransport, LoggerModule } from '@lido-nestjs/logger'; +import { nullTransport, LoggerModule, MockLoggerModule, LOGGER_PROVIDER } from '@lido-nestjs/logger'; import { getNetwork } from '@ethersproject/networks'; import { JsonRpcBatchProvider } from '@ethersproject/providers'; import { @@ -26,13 +26,13 @@ import * as dotenv from 'dotenv'; dotenv.config(); +const blockHash = '0x4ef0f15a8a04a97f60a9f76ba83d27bcf98dac9635685cd05fe1d78bd6e93418'; + describe('Registry', () => { const provider = new JsonRpcBatchProvider(process.env.PROVIDERS_URLS); const CHAIN_ID = process.env.CHAIN_ID || 1; const address = REGISTRY_CONTRACT_ADDRESSES[CHAIN_ID]; - const blockHash = '0x4ef0f15a8a04a97f60a9f76ba83d27bcf98dac9635685cd05fe1d78bd6e93418'; - const keysWithModuleAddress = keys.map((key) => { return { ...key, moduleAddress: address }; }); @@ -285,3 +285,92 @@ describe('Registry', () => { }); }); }); + +describe('Reorg detection', () => { + const provider = new JsonRpcBatchProvider(process.env.PROVIDERS_URLS); + let registryService: KeyRegistryService; + let registryStorageService: RegistryStorageService; + let moduleRef: TestingModule; + const mockCall = jest.spyOn(provider, 'call').mockImplementation(async () => ''); + const CHAIN_ID = process.env.CHAIN_ID || 1; + const address = REGISTRY_CONTRACT_ADDRESSES[CHAIN_ID]; + let mikroOrm: MikroORM; + + jest.spyOn(provider, 'detectNetwork').mockImplementation(async () => getNetwork('mainnet')); + + beforeEach(async () => { + const imports = [ + MikroOrmModule.forRoot(mikroORMConfig), + MockLoggerModule.forRoot({ + log: jest.fn(), + error: jest.fn(), + warn: jest.fn(), + }), + KeyRegistryModule.forFeature({ provider }), + ]; + moduleRef = await Test.createTestingModule({ + imports, + providers: [{ provide: LOGGER_PROVIDER, useValue: {} }], + }).compile(); + registryService = moduleRef.get(KeyRegistryService); + registryStorageService = moduleRef.get(RegistryStorageService); + mikroOrm = moduleRef.get(MikroORM); + const generator = mikroOrm.getSchemaGenerator(); + await generator.updateSchema(); + }); + + afterEach(async () => { + mockCall.mockReset(); + await clearDb(mikroOrm); + await registryStorageService.onModuleDestroy(); + }); + + test('init on update', async () => { + const saveRegistryMock = jest.spyOn(registryService, 'saveOperators'); + const saveKeyRegistryMock = jest.spyOn(registryService, 'saveKeys'); + const finalizedUsedSigningKeys = 1; + + const keysWithModuleAddress = keys.map((key) => { + return { ...key, moduleAddress: address }; + }); + + const operatorsWithModuleAddress = operators.map((key) => { + return { ...key, moduleAddress: address, finalizedUsedSigningKeys }; + }); + + const unrefMock = registryServiceMock(moduleRef, provider, { + keys: keysWithModuleAddress, + operators: operatorsWithModuleAddress, + }); + + await registryService.update(address, blockHash); + + expect(saveRegistryMock).toBeCalledTimes(1); + expect(saveKeyRegistryMock.mock.calls.length).toBeGreaterThanOrEqual(2); + + await compareTestKeysAndOperators(address, registryService, { + keys: keysWithModuleAddress, + operators: operatorsWithModuleAddress, + }); + + unrefMock(); + + // Let's corrupt the data below to make sure that + // the update method handles the left boundary correctly + const keysWithSpoiledLeftEdge = clone(keysWithModuleAddress).map((key) => + key.index >= finalizedUsedSigningKeys ? { ...key } : { ...key, key: '', depositSignature: '' }, + ); + + registryServiceMock(moduleRef, provider, { + keys: keysWithSpoiledLeftEdge, + operators: operatorsWithModuleAddress, + }); + + await registryService.update(address, blockHash); + + await compareTestKeysAndOperators(address, registryService, { + keys: keysWithModuleAddress, + operators: operatorsWithModuleAddress, + }); + }); +}); diff --git a/src/common/registry/test/validator-registry/registry-update.spec.ts b/src/common/registry/test/validator-registry/registry-update.spec.ts index 9b307ff9..96ff7f2f 100644 --- a/src/common/registry/test/validator-registry/registry-update.spec.ts +++ b/src/common/registry/test/validator-registry/registry-update.spec.ts @@ -385,7 +385,7 @@ describe('Reorg detection', () => { await registryService.update(address, blockHash); expect(saveRegistryMock).toBeCalledTimes(1); - expect(saveKeyRegistryMock.mock.calls.length).toBeGreaterThanOrEqual(1); + expect(saveKeyRegistryMock.mock.calls.length).toBeGreaterThanOrEqual(2); await compareTestKeysAndOperators(address, registryService, { keys: keysWithModuleAddress, @@ -394,6 +394,8 @@ describe('Reorg detection', () => { unrefMock(); + // Let's corrupt the data below to make sure that + // the update method handles the left boundary correctly const keysWithSpoiledLeftEdge = clone(keysWithModuleAddress).map((key) => key.index >= finalizedUsedSigningKeys ? { ...key } : { ...key, key: '', depositSignature: '' }, ); From 0d01ef63ee946b9582feec8b3e8122ee36e0a1e6 Mon Sep 17 00:00:00 2001 From: Eddort Date: Sun, 24 Dec 2023 14:40:08 +0100 Subject: [PATCH 34/48] test: fix fixtures --- src/http/operator.fixtures.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/http/operator.fixtures.ts b/src/http/operator.fixtures.ts index 605214a8..8efd5515 100644 --- a/src/http/operator.fixtures.ts +++ b/src/http/operator.fixtures.ts @@ -10,12 +10,10 @@ import { curatedModuleAddressWithCheckSum, dvtModuleAddressWithChecksum } from ' export const dvtOperatorsResp: Operator[] = [operatorOneDvt, operatorTwoDvt].map((op) => ({ ...op, - finalizedUsedSigningKeys: undefined, moduleAddress: dvtModuleAddressWithChecksum, })); export const curatedOperatorsResp: Operator[] = [operatorOneCurated, operatorTwoCurated].map((op) => ({ ...op, - finalizedUsedSigningKeys: undefined, moduleAddress: curatedModuleAddressWithCheckSum, })); From d36ebca9e155e3329276da8f5dd842d5fff06aff Mon Sep 17 00:00:00 2001 From: Eddort Date: Sun, 24 Dec 2023 14:58:15 +0100 Subject: [PATCH 35/48] fix: e2e test --- src/http/operator.fixtures.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/http/operator.fixtures.ts b/src/http/operator.fixtures.ts index 8efd5515..605214a8 100644 --- a/src/http/operator.fixtures.ts +++ b/src/http/operator.fixtures.ts @@ -10,10 +10,12 @@ import { curatedModuleAddressWithCheckSum, dvtModuleAddressWithChecksum } from ' export const dvtOperatorsResp: Operator[] = [operatorOneDvt, operatorTwoDvt].map((op) => ({ ...op, + finalizedUsedSigningKeys: undefined, moduleAddress: dvtModuleAddressWithChecksum, })); export const curatedOperatorsResp: Operator[] = [operatorOneCurated, operatorTwoCurated].map((op) => ({ ...op, + finalizedUsedSigningKeys: undefined, moduleAddress: curatedModuleAddressWithCheckSum, })); From 39a42a1bb79341507607d4847697c807726c4957 Mon Sep 17 00:00:00 2001 From: Eddort Date: Sun, 24 Dec 2023 15:00:22 +0100 Subject: [PATCH 36/48] fix: httpExceptionTooEarlyResp --- src/staking-router-modules/staking-router.service.ts | 6 ------ 1 file changed, 6 deletions(-) diff --git a/src/staking-router-modules/staking-router.service.ts b/src/staking-router-modules/staking-router.service.ts index 6bcb0552..02eb93f0 100644 --- a/src/staking-router-modules/staking-router.service.ts +++ b/src/staking-router-modules/staking-router.service.ts @@ -71,12 +71,6 @@ export class StakingRouterService { async () => { const stakingModules = await this.getStakingModules(stakingModuleAddresses); - // If the target query involves retrieving module-specific data, we do not throw the 425 exception - if (stakingModules.length === 0 && !stakingModuleAddresses) { - this.logger.warn("No staking modules in list. Maybe didn't fetched from SR yet"); - throw httpExceptionTooEarlyResp(); - } - const elBlockSnapshot = await this.getElBlockSnapshot(); if (!elBlockSnapshot) { From c60467ee1b10c471cd8d4efca8defd0bb467fa75 Mon Sep 17 00:00:00 2001 From: Eddort Date: Sun, 24 Dec 2023 15:21:42 +0100 Subject: [PATCH 37/48] fix: update log message --- src/jobs/keys-update/keys-update.service.ts | 2 +- src/jobs/keys-update/staking-module-updater.service.ts | 10 +++++----- src/jobs/keys-update/test/update-cases.spec.ts | 8 ++++---- 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/src/jobs/keys-update/keys-update.service.ts b/src/jobs/keys-update/keys-update.service.ts index a6cd5ee8..aeaebb06 100644 --- a/src/jobs/keys-update/keys-update.service.ts +++ b/src/jobs/keys-update/keys-update.service.ts @@ -124,7 +124,7 @@ export class KeysUpdateService { } if (prevElMeta?.blockHash && prevElMeta.blockHash === currElMeta.hash) { - this.logger.log('Same blockHash, indexing is not required', { prevElMeta, currElMeta }); + this.logger.log('Same blockHash, updating is not required', { prevElMeta, currElMeta }); return; } diff --git a/src/jobs/keys-update/staking-module-updater.service.ts b/src/jobs/keys-update/staking-module-updater.service.ts index aad8ad25..8fa14a87 100644 --- a/src/jobs/keys-update/staking-module-updater.service.ts +++ b/src/jobs/keys-update/staking-module-updater.service.ts @@ -42,7 +42,7 @@ export class StakingModuleUpdaterService { this.logger.log(`Nonce previous value: ${prevNonce}, nonce current value: ${currNonce}`); if (!prevBlockHash) { - this.logger.log('No past state found, start indexing', { stakingModuleAddress, currentBlockHash }); + this.logger.log('No past state found, start updating', { stakingModuleAddress, currentBlockHash }); await this.updateStakingModule( updaterState, @@ -56,7 +56,7 @@ export class StakingModuleUpdaterService { } if (prevNonce !== currNonce) { - this.logger.log('Nonce has been changed, start indexing', { + this.logger.log('Nonce has been changed, start updating', { stakingModuleAddress, currentBlockHash, prevNonce, @@ -75,7 +75,7 @@ export class StakingModuleUpdaterService { } if (this.isTooMuchDiffBetweenBlocks(prevElMeta.blockNumber, currElMeta.number)) { - this.logger.log('Too much difference between the blocks, start indexing', { + this.logger.log('Too much difference between the blocks, start updating', { stakingModuleAddress, currentBlockHash, }); @@ -92,7 +92,7 @@ export class StakingModuleUpdaterService { } if (await this.isReorgDetected(updaterState, prevBlockHash, currentBlockHash)) { - this.logger.log('Reorg detected, start indexing', { stakingModuleAddress, currentBlockHash }); + this.logger.log('Reorg detected, start updating', { stakingModuleAddress, currentBlockHash }); await this.updateStakingModule( updaterState, @@ -129,7 +129,7 @@ export class StakingModuleUpdaterService { continue; } - this.logger.log('No changes have been detected in the module, indexing is not required', { + this.logger.log('No changes have been detected in the module, updating is not required', { stakingModuleAddress, currentBlockHash, }); diff --git a/src/jobs/keys-update/test/update-cases.spec.ts b/src/jobs/keys-update/test/update-cases.spec.ts index 1e6a7aad..11cbefa5 100644 --- a/src/jobs/keys-update/test/update-cases.spec.ts +++ b/src/jobs/keys-update/test/update-cases.spec.ts @@ -76,7 +76,7 @@ describe('update cases', () => { }); expect(mockUpdate).toBeCalledTimes(1); - expect(loggerService.log.mock.calls[1][0]).toBe('No past state found, start indexing'); + expect(loggerService.log.mock.calls[1][0]).toBe('No past state found, start updating'); }); it('More than 1 module processed', async () => { @@ -116,7 +116,7 @@ describe('update cases', () => { }); expect(mockUpdate).toBeCalledTimes(2); - expect(loggerService.log.mock.calls[1][0]).toBe('Nonce has been changed, start indexing'); + expect(loggerService.log.mock.calls[1][0]).toBe('Nonce has been changed, start updating'); }); it('Too much difference between the blocks', async () => { @@ -136,7 +136,7 @@ describe('update cases', () => { }); expect(mockUpdate).toBeCalledTimes(2); - expect(loggerService.log.mock.calls[1][0]).toBe('Too much difference between the blocks, start indexing'); + expect(loggerService.log.mock.calls[1][0]).toBe('Too much difference between the blocks, start updating'); }); it('Reorg detected', async () => { @@ -163,6 +163,6 @@ describe('update cases', () => { }); expect(mockUpdate).toBeCalledTimes(2); - expect(loggerService.log.mock.calls[1][0]).toBe('Reorg detected, start indexing'); + expect(loggerService.log.mock.calls[1][0]).toBe('Reorg detected, start updating'); }); }); From aa921b23f67830c3bd64aa6b51a46dcf7d113204 Mon Sep 17 00:00:00 2001 From: Eddort Date: Sun, 24 Dec 2023 16:11:08 +0100 Subject: [PATCH 38/48] fix: remove old e2e tests --- src/http/keys/keys.e2e-spec.ts | 35 ------------------- .../sr-modules-keys.e2e-spec.ts | 7 ---- .../sr-modules-operators.e2e-spec.ts | 11 ------ src/http/sr-modules/sr-modules.e2e-spec.ts | 8 ----- 4 files changed, 61 deletions(-) diff --git a/src/http/keys/keys.e2e-spec.ts b/src/http/keys/keys.e2e-spec.ts index 04b7cc75..785bf9dd 100644 --- a/src/http/keys/keys.e2e-spec.ts +++ b/src/http/keys/keys.e2e-spec.ts @@ -263,17 +263,6 @@ describe('KeyController (e2e)', () => { 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'); - 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); @@ -297,20 +286,6 @@ describe('KeyController (e2e)', () => { 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 pubkeys = [keys[0].key, keys[1].key]; - const resp = await request(app.getHttpServer()) - .post(`/v1/keys/find`) - .set('Content-Type', 'application/json') - .send({ pubkeys }); - 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); @@ -414,16 +389,6 @@ describe('KeyController (e2e)', () => { 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); diff --git a/src/http/sr-modules-keys/sr-modules-keys.e2e-spec.ts b/src/http/sr-modules-keys/sr-modules-keys.e2e-spec.ts index ecc5d802..67513de0 100644 --- a/src/http/sr-modules-keys/sr-modules-keys.e2e-spec.ts +++ b/src/http/sr-modules-keys/sr-modules-keys.e2e-spec.ts @@ -473,13 +473,6 @@ describe('SRModulesKeysController (e2e)', () => { 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 modules', async () => { - await elMetaStorageService.update(elMeta); - const resp = await request(app.getHttpServer()).get(`/v1/modules/keys`); - expect(resp.status).toEqual(425); - expect(resp.body).toEqual({ message: 'Too early response', statusCode: 425 }); - }); }); }); }); diff --git a/src/http/sr-modules-operators/sr-modules-operators.e2e-spec.ts b/src/http/sr-modules-operators/sr-modules-operators.e2e-spec.ts index 7ec2ea94..fc006576 100644 --- a/src/http/sr-modules-operators/sr-modules-operators.e2e-spec.ts +++ b/src/http/sr-modules-operators/sr-modules-operators.e2e-spec.ts @@ -146,17 +146,6 @@ describe('SRModuleOperatorsController (e2e)', () => { 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 operators - await operatorsStorageService.save(operators); - - const resp = await request(app.getHttpServer()).get('/v1/operators'); - 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 operators await operatorsStorageService.save(operators); diff --git a/src/http/sr-modules/sr-modules.e2e-spec.ts b/src/http/sr-modules/sr-modules.e2e-spec.ts index 410de1e7..a1ae4df2 100644 --- a/src/http/sr-modules/sr-modules.e2e-spec.ts +++ b/src/http/sr-modules/sr-modules.e2e-spec.ts @@ -123,14 +123,6 @@ describe('SRModulesController (e2e)', () => { await cleanDB(); }); - it('Should return too early response if there are no modules in database', async () => { - // lets save meta - await elMetaStorageService.update(elMeta); - const resp = await request(app.getHttpServer()).get('/v1/modules'); - 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 () => { await moduleStorageService.upsert(curatedModule, 1, ''); const resp = await request(app.getHttpServer()).get('/v1/modules'); From 506cdcdfa88ccf1522f23259bc850a63d47f0c87 Mon Sep 17 00:00:00 2001 From: Eddort Date: Sun, 24 Dec 2023 20:43:28 +0100 Subject: [PATCH 39/48] refactor: reorg test --- .../keys-update/test/detect-reorg.spec.ts | 20 +------------------ .../{ => test}/keys-update.fixtures.ts | 0 .../keys-update/test/update-cases.spec.ts | 2 +- 3 files changed, 2 insertions(+), 20 deletions(-) rename src/jobs/keys-update/{ => test}/keys-update.fixtures.ts (100%) diff --git a/src/jobs/keys-update/test/detect-reorg.spec.ts b/src/jobs/keys-update/test/detect-reorg.spec.ts index ab7a7521..61a07dde 100644 --- a/src/jobs/keys-update/test/detect-reorg.spec.ts +++ b/src/jobs/keys-update/test/detect-reorg.spec.ts @@ -75,25 +75,7 @@ describe('detect reorg', () => { .spyOn(executionProviderService, 'getFullBlock') .mockImplementationOnce(async () => ({ number: 1, hash: '0x1', timestamp: 1, parentHash: '0x0' } as any)); - expect(await updaterService.isReorgDetected(updaterState, '0x1', '0x1')).toBeFalsy(); - expect(updaterState.isReorgDetected).toBeFalsy(); - }); - - it('parent hash of the currentBlock matches the hash of the prevBlock', async () => { - const updaterState: UpdaterState = { - lastChangedBlockHash: '0x1', - isReorgDetected: false, - }; - - jest - .spyOn(executionProviderService, 'getFullBlock') - .mockImplementationOnce(async () => ({ number: 2, hash: '0x2', timestamp: 1, parentHash: '0x1' } as any)); - - jest - .spyOn(executionProviderService, 'getFullBlock') - .mockImplementationOnce(async () => ({ number: 1, hash: '0x1', timestamp: 1, parentHash: '0x0' } as any)); - - expect(await updaterService.isReorgDetected(updaterState, '0x1', '0x1')).toBeFalsy(); + expect(await updaterService.isReorgDetected(updaterState, '0x1', '0x2')).toBeFalsy(); expect(updaterState.isReorgDetected).toBeFalsy(); }); diff --git a/src/jobs/keys-update/keys-update.fixtures.ts b/src/jobs/keys-update/test/keys-update.fixtures.ts similarity index 100% rename from src/jobs/keys-update/keys-update.fixtures.ts rename to src/jobs/keys-update/test/keys-update.fixtures.ts diff --git a/src/jobs/keys-update/test/update-cases.spec.ts b/src/jobs/keys-update/test/update-cases.spec.ts index 11cbefa5..b95f0180 100644 --- a/src/jobs/keys-update/test/update-cases.spec.ts +++ b/src/jobs/keys-update/test/update-cases.spec.ts @@ -4,7 +4,7 @@ import { ExecutionProviderService } from 'common/execution-provider'; import { StakingRouterService } from 'staking-router-modules/staking-router.service'; import { ElMetaStorageService } from 'storage/el-meta.storage'; import { SRModuleStorageService } from 'storage/sr-module.storage'; -import { stakingModuleFixture, stakingModuleFixtures } from '../keys-update.fixtures'; +import { stakingModuleFixture, stakingModuleFixtures } from './keys-update.fixtures'; import { StakingModuleUpdaterService } from '../staking-module-updater.service'; describe('update cases', () => { From 079d15caea4ed2514b8eb0967ba376aa5c3561d2 Mon Sep 17 00:00:00 2001 From: Eddort Date: Sun, 24 Dec 2023 20:52:43 +0100 Subject: [PATCH 40/48] refactor: add comments to reorg check method --- .../keys-update/staking-module-updater.service.ts | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/src/jobs/keys-update/staking-module-updater.service.ts b/src/jobs/keys-update/staking-module-updater.service.ts index 8fa14a87..f55c0464 100644 --- a/src/jobs/keys-update/staking-module-updater.service.ts +++ b/src/jobs/keys-update/staking-module-updater.service.ts @@ -158,26 +158,30 @@ export class StakingModuleUpdaterService { if (updaterState.isReorgDetected) { return true; } - + // get full block data by hashes const currentBlock = await this.executionProvider.getFullBlock(currentBlockHash); const prevBlock = await this.executionProvider.getFullBlock(prevBlockHash); - + // prevBlock is a direct child of currentBlock + // there's no need to check deeper as we get the currentBlock by tag if (currentBlock.parentHash === prevBlock.hash) return false; // different hash but same number + // is a direct indication of reorganization, there's no need to look any deeper. if (currentBlock.hash !== prevBlock.hash && currentBlock.number === prevBlock.number) { updaterState.isReorgDetected = true; return true; } - + // get all blocks by block number + // block numbers are the interval between the current and previous blocks const blocks = await Promise.all( range(prevBlock.number, currentBlock.number + 1).map(async (bNumber) => { return await this.executionProvider.getFullBlock(bNumber); }), ); - + // compare hash from the first block if (blocks[0].hash !== prevBlockHash) return true; + // compare hash from the last block if (blocks[blocks.length - 1].hash !== currentBlockHash) return true; - + // check the integrity of the blockchain for (let i = 1; i < blocks.length; i++) { const previousBlock = blocks[i - 1]; const currentBlock = blocks[i]; From 453ac728301f7de91e78b81b23d27e3c6f04ce96 Mon Sep 17 00:00:00 2001 From: Eddort Date: Sun, 24 Dec 2023 22:26:10 +0100 Subject: [PATCH 41/48] fix: reorg spec arg --- src/jobs/keys-update/test/detect-reorg.spec.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/jobs/keys-update/test/detect-reorg.spec.ts b/src/jobs/keys-update/test/detect-reorg.spec.ts index 61a07dde..1fcc0abb 100644 --- a/src/jobs/keys-update/test/detect-reorg.spec.ts +++ b/src/jobs/keys-update/test/detect-reorg.spec.ts @@ -93,7 +93,7 @@ describe('detect reorg', () => { .spyOn(executionProviderService, 'getFullBlock') .mockImplementationOnce(async () => ({ number: 2, hash: '0x1', timestamp: 1, parentHash: '0x0' } as any)); - expect(await updaterService.isReorgDetected(updaterState, '0x1', '0x1')).toBeTruthy(); + expect(await updaterService.isReorgDetected(updaterState, '0x1', '0x2')).toBeTruthy(); expect(updaterState.isReorgDetected).toBeTruthy(); }); From 1dc39ecb2723c8d3e7bf5266837cb6eae7fc1cb0 Mon Sep 17 00:00:00 2001 From: Eddort Date: Mon, 25 Dec 2023 13:55:37 +0100 Subject: [PATCH 42/48] fix: wrong migration --- ...n20231220104403.ts => Migration20231225124800.ts} | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) rename src/migrations/{Migration20231220104403.ts => Migration20231225124800.ts} (63%) diff --git a/src/migrations/Migration20231220104403.ts b/src/migrations/Migration20231225124800.ts similarity index 63% rename from src/migrations/Migration20231220104403.ts rename to src/migrations/Migration20231225124800.ts index 00376807..83f26b0d 100644 --- a/src/migrations/Migration20231220104403.ts +++ b/src/migrations/Migration20231225124800.ts @@ -1,7 +1,12 @@ import { Migration } from '@mikro-orm/migrations'; -export class Migration20231220104403 extends Migration { +export class Migration20231225124800 extends Migration { async up(): Promise { + this.addSql('TRUNCATE registry_key'); + this.addSql('TRUNCATE registry_operator'); + this.addSql('TRUNCATE el_meta_entity'); + this.addSql('TRUNCATE sr_module_entity'); + this.addSql('alter table "el_meta_entity" add column "last_changed_block_hash" varchar(66) not null;'); this.addSql('alter table "registry_operator" add column "finalized_used_signing_keys" int not null;'); @@ -10,6 +15,11 @@ export class Migration20231220104403 extends Migration { } async down(): Promise { + this.addSql('TRUNCATE registry_key'); + this.addSql('TRUNCATE registry_operator'); + this.addSql('TRUNCATE el_meta_entity'); + this.addSql('TRUNCATE sr_module_entity'); + this.addSql('alter table "el_meta_entity" drop column "last_changed_block_hash";'); this.addSql('alter table "registry_operator" drop column "finalized_used_signing_keys";'); From 8b9aef1c94b8ac5154717921490f77beeedf023b Mon Sep 17 00:00:00 2001 From: Anna Mukharram Date: Tue, 9 Jan 2024 20:51:09 +0400 Subject: [PATCH 43/48] fix: metrics id --- src/jobs/keys-update/keys-update.service.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/jobs/keys-update/keys-update.service.ts b/src/jobs/keys-update/keys-update.service.ts index aeaebb06..893e439f 100644 --- a/src/jobs/keys-update/keys-update.service.ts +++ b/src/jobs/keys-update/keys-update.service.ts @@ -182,7 +182,7 @@ export class KeysUpdateService { this.prometheusService.registryNumberOfKeysBySRModuleAndOperator.set( { operator: operator.index, - srModuleId: module.id, + srModuleId: module.moduleId, used: 'true', }, operator.usedSigningKeys, @@ -191,7 +191,7 @@ export class KeysUpdateService { this.prometheusService.registryNumberOfKeysBySRModuleAndOperator.set( { operator: operator.index, - srModuleId: module.id, + srModuleId: module.moduleId, used: 'false', }, operator.totalSigningKeys - operator.usedSigningKeys, From e4b8831be7151681dec763b4f09266b418d3f99b Mon Sep 17 00:00:00 2001 From: Anna Mukharram Date: Tue, 9 Jan 2024 20:56:24 +0400 Subject: [PATCH 44/48] fix: metrics id --- src/jobs/keys-update/keys-update.service.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/jobs/keys-update/keys-update.service.ts b/src/jobs/keys-update/keys-update.service.ts index 893e439f..d030d58a 100644 --- a/src/jobs/keys-update/keys-update.service.ts +++ b/src/jobs/keys-update/keys-update.service.ts @@ -172,7 +172,7 @@ export class KeysUpdateService { const moduleInstance = this.stakingRouterService.getStakingRouterModuleImpl(module.type); // update nonce metric - this.prometheusService.registryNonce.set({ srModuleId: module.id }, module.nonce); + this.prometheusService.registryNonce.set({ srModuleId: module.moduleId }, module.nonce); // get operators const operators = await moduleInstance.getOperators(module.stakingModuleAddress); From 9e54b81cc78c1e19163250155a49084d6ed5dfb4 Mon Sep 17 00:00:00 2001 From: Anna Mukharram Date: Tue, 9 Jan 2024 21:27:02 +0400 Subject: [PATCH 45/48] fix: move reset above for loop --- src/jobs/keys-update/keys-update.service.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/jobs/keys-update/keys-update.service.ts b/src/jobs/keys-update/keys-update.service.ts index d030d58a..ab0d0a44 100644 --- a/src/jobs/keys-update/keys-update.service.ts +++ b/src/jobs/keys-update/keys-update.service.ts @@ -168,6 +168,8 @@ export class KeysUpdateService { this.prometheusService.registryLastUpdate.set(elMeta.timestamp); this.prometheusService.registryBlockNumber.set(elMeta.blockNumber); + this.prometheusService.registryNumberOfKeysBySRModuleAndOperator.reset(); + for (const module of stakingModules) { const moduleInstance = this.stakingRouterService.getStakingRouterModuleImpl(module.type); @@ -176,7 +178,6 @@ export class KeysUpdateService { // get operators const operators = await moduleInstance.getOperators(module.stakingModuleAddress); - this.prometheusService.registryNumberOfKeysBySRModuleAndOperator.reset(); operators.forEach((operator) => { this.prometheusService.registryNumberOfKeysBySRModuleAndOperator.set( From d4fdb5912660d82788925d30c6fcd5e47628672b Mon Sep 17 00:00:00 2001 From: infloop Date: Wed, 10 Jan 2024 18:41:45 +0300 Subject: [PATCH 46/48] chore: bump version --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 42213276..56105f73 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "lido-keys-api", - "version": "0.10.1", + "version": "0.10.2", "description": "Lido Node Operators keys service", "author": "Lido team", "private": true, From bc82967b8105357bfe67c2e405b8a108d65fe287 Mon Sep 17 00:00:00 2001 From: Eddort Date: Wed, 17 Jan 2024 12:32:08 +0100 Subject: [PATCH 47/48] fix: srmodules types --- src/http/db.fixtures.ts | 3 --- src/jobs/keys-update/test/keys-update.fixtures.ts | 2 -- .../contracts/staking-router/staking-router-fetch.service.ts | 1 - .../interfaces/staking-module.interface.ts | 2 -- 4 files changed, 8 deletions(-) diff --git a/src/http/db.fixtures.ts b/src/http/db.fixtures.ts index b59c5c2f..0a6c0d41 100644 --- a/src/http/db.fixtures.ts +++ b/src/http/db.fixtures.ts @@ -17,7 +17,6 @@ export const curatedModule: StakingModule = { lastDepositBlock: 9, exitedValidatorsCount: 0, active: true, - lastChangedBlockHash: '', }; export const dvtModule: StakingModule = { @@ -33,7 +32,6 @@ export const dvtModule: StakingModule = { lastDepositBlock: 10, exitedValidatorsCount: 0, active: true, - lastChangedBlockHash: '', }; export const updatedCuratedModule: StakingModule = { @@ -49,7 +47,6 @@ export const updatedCuratedModule: StakingModule = { lastDepositBlock: 10, exitedValidatorsCount: 1, active: false, - lastChangedBlockHash: '', }; export const srModules = [curatedModule, dvtModule]; diff --git a/src/jobs/keys-update/test/keys-update.fixtures.ts b/src/jobs/keys-update/test/keys-update.fixtures.ts index 5b70abfa..f5ef37d3 100644 --- a/src/jobs/keys-update/test/keys-update.fixtures.ts +++ b/src/jobs/keys-update/test/keys-update.fixtures.ts @@ -13,7 +13,6 @@ export const stakingModuleFixture: StakingModule = { exitedValidatorsCount: 10, type: 'curated', active: true, - lastChangedBlockHash: '', }; export const stakingModuleFixtures: StakingModule[] = [ @@ -31,6 +30,5 @@ export const stakingModuleFixtures: StakingModule[] = [ exitedValidatorsCount: 5, type: 'dvt', active: false, - lastChangedBlockHash: '', }, ]; diff --git a/src/staking-router-modules/contracts/staking-router/staking-router-fetch.service.ts b/src/staking-router-modules/contracts/staking-router/staking-router-fetch.service.ts index 92b86825..4e167654 100644 --- a/src/staking-router-modules/contracts/staking-router/staking-router-fetch.service.ts +++ b/src/staking-router-modules/contracts/staking-router/staking-router-fetch.service.ts @@ -64,7 +64,6 @@ export class StakingRouterFetchService { lastDepositBlock: stakingModule.lastDepositBlock.toNumber(), exitedValidatorsCount: stakingModule.exitedValidatorsCount.toNumber(), active: isActive, - lastChangedBlockHash: '', }; }), ); diff --git a/src/staking-router-modules/interfaces/staking-module.interface.ts b/src/staking-router-modules/interfaces/staking-module.interface.ts index 83a8ceb5..ac77a3ea 100644 --- a/src/staking-router-modules/interfaces/staking-module.interface.ts +++ b/src/staking-router-modules/interfaces/staking-module.interface.ts @@ -27,8 +27,6 @@ export interface StakingModule { type: string; //STAKING_MODULE_TYPE; // is module active active: boolean; - // last changed block hash - lastChangedBlockHash: string; } export interface StakingModuleInterface { From 0f318e1a859d014959bd75b45bd53992e78cd9e7 Mon Sep 17 00:00:00 2001 From: Eddort Date: Wed, 17 Jan 2024 14:48:13 +0100 Subject: [PATCH 48/48] test: covering reorg edge cases --- .../staking-module-updater.service.ts | 12 +++- .../keys-update/test/detect-reorg.spec.ts | 58 ++++++++++++++++++- 2 files changed, 66 insertions(+), 4 deletions(-) diff --git a/src/jobs/keys-update/staking-module-updater.service.ts b/src/jobs/keys-update/staking-module-updater.service.ts index f55c0464..f63ad67c 100644 --- a/src/jobs/keys-update/staking-module-updater.service.ts +++ b/src/jobs/keys-update/staking-module-updater.service.ts @@ -161,7 +161,7 @@ export class StakingModuleUpdaterService { // get full block data by hashes const currentBlock = await this.executionProvider.getFullBlock(currentBlockHash); const prevBlock = await this.executionProvider.getFullBlock(prevBlockHash); - // prevBlock is a direct child of currentBlock + // prevBlock is a direct parent of currentBlock // there's no need to check deeper as we get the currentBlock by tag if (currentBlock.parentHash === prevBlock.hash) return false; // different hash but same number @@ -178,9 +178,15 @@ export class StakingModuleUpdaterService { }), ); // compare hash from the first block - if (blocks[0].hash !== prevBlockHash) return true; + if (blocks[0].hash !== prevBlockHash) { + updaterState.isReorgDetected = true; + return true; + } // compare hash from the last block - if (blocks[blocks.length - 1].hash !== currentBlockHash) return true; + if (blocks[blocks.length - 1].hash !== currentBlockHash) { + updaterState.isReorgDetected = true; + return true; + } // check the integrity of the blockchain for (let i = 1; i < blocks.length; i++) { const previousBlock = blocks[i - 1]; diff --git a/src/jobs/keys-update/test/detect-reorg.spec.ts b/src/jobs/keys-update/test/detect-reorg.spec.ts index 1fcc0abb..1260713c 100644 --- a/src/jobs/keys-update/test/detect-reorg.spec.ts +++ b/src/jobs/keys-update/test/detect-reorg.spec.ts @@ -145,7 +145,63 @@ describe('detect reorg', () => { number: Number(blockHashOrBlockTag), hash: `0x${blockHashOrBlockTag}`, timestamp: 1, - parentHash: `0xSORRY`, + parentHash: blockHashOrBlockTag === 100 ? `0xSORRY` : `0x${Number(blockHashOrBlockTag) - 1}`, + } as any), + ); + + expect(await updaterService.isReorgDetected(updaterState, '0x1', '0x100')).toBeTruthy(); + expect(updaterState.isReorgDetected).toBeTruthy(); + }); + + it("first block from range response doesn't match with first block", async () => { + const updaterState: UpdaterState = { + lastChangedBlockHash: '0x1', + isReorgDetected: false, + }; + + jest + .spyOn(executionProviderService, 'getFullBlock') + .mockImplementationOnce(async () => ({ number: 100, hash: '0x100', timestamp: 1, parentHash: '0x99' } as any)); + + jest + .spyOn(executionProviderService, 'getFullBlock') + .mockImplementationOnce(async () => ({ number: 1, hash: '0x1', timestamp: 1, parentHash: '0x0' } as any)); + + jest.spyOn(executionProviderService, 'getFullBlock').mockImplementation( + async (blockHashOrBlockTag: string | number) => + ({ + number: Number(blockHashOrBlockTag), + hash: blockHashOrBlockTag === 1 ? `0xSORRY` : `0x${Number(blockHashOrBlockTag) - 1}`, + timestamp: 1, + parentHash: `0x${Number(blockHashOrBlockTag) - 1}`, + } as any), + ); + + expect(await updaterService.isReorgDetected(updaterState, '0x1', '0x100')).toBeTruthy(); + expect(updaterState.isReorgDetected).toBeTruthy(); + }); + + it("last block from range response doesn't match with last block", async () => { + const updaterState: UpdaterState = { + lastChangedBlockHash: '0x1', + isReorgDetected: false, + }; + + jest + .spyOn(executionProviderService, 'getFullBlock') + .mockImplementationOnce(async () => ({ number: 100, hash: '0x100', timestamp: 1, parentHash: '0x99' } as any)); + + jest + .spyOn(executionProviderService, 'getFullBlock') + .mockImplementationOnce(async () => ({ number: 1, hash: '0x1', timestamp: 1, parentHash: '0x0' } as any)); + + jest.spyOn(executionProviderService, 'getFullBlock').mockImplementation( + async (blockHashOrBlockTag: string | number) => + ({ + number: Number(blockHashOrBlockTag), + hash: blockHashOrBlockTag === 100 ? `0xSORRY` : `0x${Number(blockHashOrBlockTag) - 1}`, + timestamp: 1, + parentHash: `0x${Number(blockHashOrBlockTag) - 1}`, } as any), );