From d49b7d015003ff7eefcab9244cc99466c6ed6eeb Mon Sep 17 00:00:00 2001 From: Traian Anghel Date: Tue, 21 May 2024 18:12:07 +0300 Subject: [PATCH] Stakingv4 preparations (#1255) * add queue size in stake endpoint * implemented better heuristic * stake invalidation * invalidate stake every 30 seconds * exclude leaving validators in shard info * add stakingV4 devnet/mainnet configs * update stake.spec.ts * secondary sorting criteria * reverse sorting * cached nodes auctions, invalidated if staking v4 enabled * update activationEpoch --------- Co-authored-by: cfaur --- config/config.devnet.yaml | 4 ++++ config/config.mainnet.yaml | 4 ++++ .../cache.warmer/cache.warmer.service.ts | 13 ++++++++++- src/endpoints/nodes/node.service.ts | 23 +++++++++++++++---- src/endpoints/shards/shard.service.ts | 2 +- src/endpoints/stake/stake.service.ts | 4 +++- src/test/unit/services/stake.spec.ts | 2 ++ src/utils/cache.info.ts | 5 ++++ 8 files changed, 49 insertions(+), 8 deletions(-) diff --git a/config/config.devnet.yaml b/config/config.devnet.yaml index 16ffcd1c3..41faa15ea 100644 --- a/config/config.devnet.yaml +++ b/config/config.devnet.yaml @@ -55,6 +55,10 @@ features: admins: - '' jwtSecret: '' + stakingV4: + enabled: false + cronExpression: '*/5 * * * * *' + activationEpoch: 1043 nodeEpochsLeft: enabled: false transactionProcessor: diff --git a/config/config.mainnet.yaml b/config/config.mainnet.yaml index 02275e829..d06baaf85 100644 --- a/config/config.mainnet.yaml +++ b/config/config.mainnet.yaml @@ -56,6 +56,10 @@ features: admins: - '' jwtSecret: '' + stakingV4: + enabled: false + cronExpression: '*/5 * * * * *' + activationEpoch: 1391 nodeEpochsLeft: enabled: false transactionProcessor: diff --git a/src/crons/cache.warmer/cache.warmer.service.ts b/src/crons/cache.warmer/cache.warmer.service.ts index 6e0dce2dc..db6175a11 100644 --- a/src/crons/cache.warmer/cache.warmer.service.ts +++ b/src/crons/cache.warmer/cache.warmer.service.ts @@ -32,6 +32,7 @@ import { BlockService } from "src/endpoints/blocks/block.service"; import { PoolService } from "src/endpoints/pool/pool.service"; import * as JsonDiff from "json-diff"; import { QueryPagination } from "src/common/entities/query.pagination"; +import { StakeService } from "src/endpoints/stake/stake.service"; @Injectable() export class CacheWarmerService { @@ -64,6 +65,7 @@ export class CacheWarmerService { private readonly dataApiService: DataApiService, private readonly blockService: BlockService, private readonly poolService: PoolService, + private readonly stakeService: StakeService, ) { this.configCronJob( 'handleTokenAssetsInvalidations', @@ -137,8 +139,10 @@ export class CacheWarmerService { const auctions = await this.gatewayService.getValidatorAuctions(); await this.nodeService.processAuctions(nodes, auctions); - await this.invalidateKey(CacheInfo.Nodes.key, nodes, CacheInfo.Nodes.ttl); + + const nodesAuctions = await this.nodeService.getAllNodesAuctionsRaw(); + await this.invalidateKey(CacheInfo.NodesAuctions.key, nodesAuctions, CacheInfo.NodesAuctions.ttl); } @Lock({ name: 'Transaction pool invalidation', verbose: true }) @@ -206,6 +210,13 @@ export class CacheWarmerService { await this.invalidateKey(CacheInfo.Economics.key, economics, CacheInfo.Economics.ttl); } + @Cron(CronExpression.EVERY_30_SECONDS) + @Lock({ name: 'Stake invalidations', verbose: true }) + async handleStakeInvalidations() { + const stake = await this.stakeService.getGlobalStakeRaw(); + await this.invalidateKey(CacheInfo.GlobalStake.key, stake, CacheInfo.GlobalStake.ttl); + } + @Cron(CronExpression.EVERY_MINUTE) @Lock({ name: 'Accounts invalidations', verbose: true }) async handleAccountInvalidations() { diff --git a/src/endpoints/nodes/node.service.ts b/src/endpoints/nodes/node.service.ts index f309b5ca5..45710cdf3 100644 --- a/src/endpoints/nodes/node.service.ts +++ b/src/endpoints/nodes/node.service.ts @@ -721,12 +721,19 @@ export class NodeService { return nodes; } - async getNodesAuctions(pagination: QueryPagination, filter: NodeAuctionFilter): Promise { + async getAllNodesAuctions(): Promise { + return await this.cacheService.getOrSet( + CacheInfo.NodesAuctions.key, + async () => await this.getAllNodesAuctionsRaw(), + CacheInfo.NodesAuctions.ttl + ); + } + + async getAllNodesAuctionsRaw(): Promise { const allNodes = await this.getNodes(new QueryPagination({ size: 10000 }), new NodeFilter({ status: NodeStatus.auction })); const auctions = await this.gatewayService.getValidatorAuctions(); const auctionNodesMap = new Map(); - const { from, size } = pagination; for (const auction of auctions) { if (auction.nodes) { @@ -741,7 +748,7 @@ export class NodeService { const groupedNodes = allNodes.groupBy(node => (node.provider || node.owner) + ':' + (BigInt(node.stake).toString()) + (BigInt(node.topUp).toString()), true); - let nodesWithAuctionData: NodeAuction[] = []; + const nodesWithAuctionData: NodeAuction[] = []; for (const group of groupedNodes) { const node: Node = group.values[0]; @@ -772,15 +779,21 @@ export class NodeService { nodesWithAuctionData.push(nodeAuction); } + return nodesWithAuctionData; + } + + async getNodesAuctions(pagination: QueryPagination, filter: NodeAuctionFilter): Promise { + let nodesWithAuctionData = await this.getAllNodesAuctions(); + const sort = filter?.sort ?? NodeSortAuction.qualifiedStake; const order = !filter?.sort && !filter?.order ? SortOrder.desc : filter?.order; - nodesWithAuctionData = nodesWithAuctionData.sorted(node => Number(node[sort])); + nodesWithAuctionData = nodesWithAuctionData.sorted(node => Number(node[sort]), node => node.qualifiedAuctionValidators === 0 ? 0 : 1, node => 0 - node.droppedValidators); if (order === SortOrder.desc) { nodesWithAuctionData.reverse(); } - return nodesWithAuctionData.slice(from, size); + return nodesWithAuctionData.slice(pagination.from, pagination.size); } async deleteOwnersForAddressInCache(address: string): Promise { diff --git a/src/endpoints/shards/shard.service.ts b/src/endpoints/shards/shard.service.ts index 026c8b4b6..a18df68ac 100644 --- a/src/endpoints/shards/shard.service.ts +++ b/src/endpoints/shards/shard.service.ts @@ -36,7 +36,7 @@ export class ShardService { ({ type, shard, status }) => type === 'validator' && shard !== undefined && - [NodeStatus.eligible, NodeStatus.waiting, NodeStatus.leaving].includes(status ?? NodeStatus.unknown) + [NodeStatus.eligible, NodeStatus.waiting].includes(status ?? NodeStatus.unknown) ); const shards = validators.map(({ shard }) => shard).filter(shard => shard !== undefined).map(shard => shard ?? 0).distinct(); diff --git a/src/endpoints/stake/stake.service.ts b/src/endpoints/stake/stake.service.ts index 246783bb0..4a12b397a 100644 --- a/src/endpoints/stake/stake.service.ts +++ b/src/endpoints/stake/stake.service.ts @@ -59,7 +59,9 @@ export class StakeService { const totalStaked = BigInt(BigInt(totalBaseStaked) + BigInt(totalTopUp)).toString(); const totalObservers = await this.nodeService.getNodeCount(new NodeFilter({ type: NodeType.observer })); - if (!this.apiConfigService.isStakingV4Enabled()) { + const currentEpoch = await this.blockService.getCurrentEpoch(); + + if (!this.apiConfigService.isStakingV4Enabled() || currentEpoch < this.apiConfigService.getStakingV4ActivationEpoch()) { const queueSize = await this.nodeService.getNodeCount(new NodeFilter({ status: NodeStatus.queued })); return new GlobalStake({ totalValidators: validators.totalValidators, diff --git a/src/test/unit/services/stake.spec.ts b/src/test/unit/services/stake.spec.ts index e9fad5a2e..27dff3f53 100644 --- a/src/test/unit/services/stake.spec.ts +++ b/src/test/unit/services/stake.spec.ts @@ -45,6 +45,7 @@ describe('Stake Service', () => { getStakingContractAddress: jest.fn(), getAuctionContractAddress: jest.fn(), isStakingV4Enabled: jest.fn(), + getStakingV4ActivationEpoch: jest.fn(), }, }, { @@ -168,6 +169,7 @@ describe('Stake Service', () => { jest.spyOn(stakeService, 'getMinimumAuctionTopUp').mockResolvedValue(expectedMinimumAuctionQualifiedTopUp); jest.spyOn(stakeService, 'getNakamotoCoefficient').mockResolvedValue(expectedNakamotoCoefficient); jest.spyOn(apiConfigService, 'isStakingV4Enabled').mockReturnValue(true); + jest.spyOn(apiConfigService, 'getStakingV4ActivationEpoch').mockReturnValue(1395); jest.spyOn(nodeService, 'getNodeCount').mockResolvedValue(100); const result = await stakeService.getGlobalStakeRaw(); diff --git a/src/utils/cache.info.ts b/src/utils/cache.info.ts index 5dfd99b51..844bbb6b1 100644 --- a/src/utils/cache.info.ts +++ b/src/utils/cache.info.ts @@ -640,4 +640,9 @@ export class CacheInfo { ttl: Constants.oneMinute(), }; } + + static NodesAuctions: CacheInfo = { + key: 'nodesAuctions', + ttl: Constants.oneMinute(), + }; }