diff --git a/indexer/src/cache/init.ts b/indexer/src/cache/init.ts index 97c320a6..e9afad97 100644 --- a/indexer/src/cache/init.ts +++ b/indexer/src/cache/init.ts @@ -16,7 +16,12 @@ import { ResolverContext } from '../kadena-server/config/apollo-server-config'; import NodeCache from 'node-cache'; -import { HASH_RATE_AND_TOTAL_DIFFICULTY_KEY, NETWORK_STATISTICS_KEY, NODE_INFO_KEY } from './keys'; +import { + COUNTERS_OF_EACH_CHAIN_KEY, + HASH_RATE_AND_TOTAL_DIFFICULTY_KEY, + NETWORK_STATISTICS_KEY, + NODE_INFO_KEY, +} from './keys'; import { HashRateAndTotalDifficulty } from '../kadena-server/repository/application/network-repository'; /** @@ -108,6 +113,22 @@ export default async function initCache(context: ResolverContext) { } } + /** + * Fetches and caches information about the blockchain node + * + * This includes node version, connectivity status, and other + * node-specific information that helps monitor the node's health. + */ + + async function getCountersOfEachChain() { + try { + const counters = await networkRepository.getCountersOfEachChain(); + MEMORY_CACHE.set(COUNTERS_OF_EACH_CHAIN_KEY, counters); + } catch (err) { + console.error('[ERROR][CACHE][CONN_TIMEOUT] Failed to get counters of each chain', err); + } + } + // Initialize the hash rate cache with a default value // The -1 value indicates that real data hasn't been loaded yet MEMORY_CACHE.set(HASH_RATE_AND_TOTAL_DIFFICULTY_KEY, { @@ -124,6 +145,7 @@ export default async function initCache(context: ResolverContext) { await getNetworkStatistics(); await getNodeInfo(); await getHashRateAndTotalDifficulty(); + await getCountersOfEachChain(); }; // Populate cache with initial data diff --git a/indexer/src/cache/keys.ts b/indexer/src/cache/keys.ts index 9e09194f..1c7cb857 100644 --- a/indexer/src/cache/keys.ts +++ b/indexer/src/cache/keys.ts @@ -24,3 +24,9 @@ export const NETWORK_STATISTICS_KEY = 'NETWORK_STATISTICS_KEY'; * Used to store and retrieve information about the blockchain node */ export const NODE_INFO_KEY = 'NODE_INFO_KEY'; + +/** + * Key for caching counters of each chain data + * Used to store and retrieve information about the numbers of entities in each chain + */ +export const COUNTERS_OF_EACH_CHAIN_KEY = 'COUNTERS_OF_EACH_CHAIN_KEY'; diff --git a/indexer/src/kadena-server/config/graphql-types.ts b/indexer/src/kadena-server/config/graphql-types.ts index 662f6053..e0e72d95 100644 --- a/indexer/src/kadena-server/config/graphql-types.ts +++ b/indexer/src/kadena-server/config/graphql-types.ts @@ -131,6 +131,14 @@ export type ContinuationPayload = { step?: Maybe; }; +export type CountersOfEachChain = { + __typename?: 'CountersOfEachChain'; + blocksCount: Scalars['Int']['output']; + chainId: Scalars['String']['output']; + totalGasUsed: Scalars['String']['output']; + transactionCount: Scalars['Int']['output']; +}; + /** DEX metrics including TVL, volume, and pool count */ export type DexMetrics = { __typename?: 'DexMetrics'; @@ -394,6 +402,7 @@ export type NetworkInfo = { apiVersion: Scalars['String']['output']; /** The number of circulating coins. */ coinsInCirculation: Scalars['Float']['output']; + countersOfEachChain: Array; genesisHeights: Array; /** The network hash rate. */ networkHashRate: Scalars['Float']['output']; @@ -1551,6 +1560,7 @@ export type ResolversTypes = { Boolean: ResolverTypeWrapper; ChartDataPoint: ResolverTypeWrapper; ContinuationPayload: ResolverTypeWrapper; + CountersOfEachChain: ResolverTypeWrapper; DateTime: ResolverTypeWrapper; Decimal: ResolverTypeWrapper; DexMetrics: ResolverTypeWrapper; @@ -1846,6 +1856,7 @@ export type ResolversParentTypes = { Boolean: Scalars['Boolean']['output']; ChartDataPoint: ChartDataPoint; ContinuationPayload: ContinuationPayload; + CountersOfEachChain: CountersOfEachChain; DateTime: Scalars['DateTime']['output']; Decimal: Scalars['Decimal']['output']; DexMetrics: DexMetrics; @@ -2196,6 +2207,18 @@ export type ContinuationPayloadResolvers< __isTypeOf?: IsTypeOfResolverFn; }; +export type CountersOfEachChainResolvers< + ContextType = any, + ParentType extends + ResolversParentTypes['CountersOfEachChain'] = ResolversParentTypes['CountersOfEachChain'], +> = { + blocksCount?: Resolver; + chainId?: Resolver; + totalGasUsed?: Resolver; + transactionCount?: Resolver; + __isTypeOf?: IsTypeOfResolverFn; +}; + export interface DateTimeScalarConfig extends GraphQLScalarTypeConfig { name: 'DateTime'; @@ -2509,6 +2532,11 @@ export type NetworkInfoResolvers< > = { apiVersion?: Resolver; coinsInCirculation?: Resolver; + countersOfEachChain?: Resolver< + Array, + ParentType, + ContextType + >; genesisHeights?: Resolver, ParentType, ContextType>; networkHashRate?: Resolver; networkHost?: Resolver; @@ -3537,6 +3565,7 @@ export type Resolvers = { BlockTransactionsConnectionEdge?: BlockTransactionsConnectionEdgeResolvers; ChartDataPoint?: ChartDataPointResolvers; ContinuationPayload?: ContinuationPayloadResolvers; + CountersOfEachChain?: CountersOfEachChainResolvers; DateTime?: GraphQLScalarType; Decimal?: GraphQLScalarType; DexMetrics?: DexMetricsResolvers; diff --git a/indexer/src/kadena-server/config/schema.graphql b/indexer/src/kadena-server/config/schema.graphql index 0d036e55..292b2e9d 100644 --- a/indexer/src/kadena-server/config/schema.graphql +++ b/indexer/src/kadena-server/config/schema.graphql @@ -1050,6 +1050,14 @@ type NetworkInfo { nodePackageVersion: String! nodeServiceDate: DateTime nodeLatestBehaviorHeight: Int! + countersOfEachChain: [CountersOfEachChain!]! @complexity(value: 1) +} + +type CountersOfEachChain { + chainId: String! + blocksCount: Int! + transactionCount: Int! + totalGasUsed: String! } type GenesisHeight { diff --git a/indexer/src/kadena-server/repository/application/network-repository.ts b/indexer/src/kadena-server/repository/application/network-repository.ts index 6ab2f927..b7a3ee6d 100644 --- a/indexer/src/kadena-server/repository/application/network-repository.ts +++ b/indexer/src/kadena-server/repository/application/network-repository.ts @@ -21,7 +21,18 @@ export interface GetNodeInfo { nodeLatestBehaviorHeight: number; } -type AllInfo = NetworkStatistics & HashRateAndTotalDifficulty & GetNodeInfo; +export interface CountersOfEachChain { + chainId: string; + blocksCount: number; + transactionCount: number; + totalGasUsed: string; +} + +type AllInfo = NetworkStatistics & + HashRateAndTotalDifficulty & + GetNodeInfo & { + countersOfEachChain: CountersOfEachChain[]; + }; export type CurrentChainHeights = Record; @@ -31,4 +42,5 @@ export default interface NetworkRepository { getNodeInfo(): Promise; getAllInfo(): Promise; getCurrentChainHeights(): Promise; + getCountersOfEachChain(): Promise; } diff --git a/indexer/src/kadena-server/repository/infra/repository/network-db-repository.ts b/indexer/src/kadena-server/repository/infra/repository/network-db-repository.ts index e0f00b90..fda132ce 100644 --- a/indexer/src/kadena-server/repository/infra/repository/network-db-repository.ts +++ b/indexer/src/kadena-server/repository/infra/repository/network-db-repository.ts @@ -10,6 +10,7 @@ import { Op } from 'sequelize'; import BlockModel from '../../../../models/block'; import NetworkRepository, { + CountersOfEachChain, CurrentChainHeights, GetNodeInfo, HashRateAndTotalDifficulty, @@ -25,7 +26,11 @@ import { rootPgPool } from '../../../../config/database'; import { nodeInfoValidator } from '../schema-validator/node-info-validator'; import { getRequiredEnvString } from '../../../../utils/helpers'; import { MEMORY_CACHE } from '../../../../cache/init'; -import { HASH_RATE_AND_TOTAL_DIFFICULTY_KEY, NETWORK_STATISTICS_KEY } from '../../../../cache/keys'; +import { + COUNTERS_OF_EACH_CHAIN_KEY, + HASH_RATE_AND_TOTAL_DIFFICULTY_KEY, + NETWORK_STATISTICS_KEY, +} from '../../../../cache/keys'; import { getCirculationNumber } from '../../../../utils/coin-circulation'; // Configuration values from environment variables @@ -232,6 +237,27 @@ export default class NetworkDbRepository implements NetworkRepository { return output; } + async getCountersOfEachChain(): Promise { + const countersQuery = ` + SELECT "chainId", "canonicalBlocks", "canonicalTransactions", "totalGasUsed" + FROM "Counters" + ORDER BY "chainId" + `; + + const { rows } = await rootPgPool.query(countersQuery); + + const output = rows.map(row => { + return { + chainId: row.chainId, + blocksCount: row.canonicalBlocks, + transactionCount: row.canonicalTransactions, + totalGasUsed: row.totalGasUsed, + }; + }); + + return output; + } + /** * Retrieves all network information from cache * @@ -247,7 +273,15 @@ export default class NetworkDbRepository implements NetworkRepository { const HashRateAndTotalDifficulty = MEMORY_CACHE.get( HASH_RATE_AND_TOTAL_DIFFICULTY_KEY, ) as HashRateAndTotalDifficulty; + const countersOfEachChain = MEMORY_CACHE.get( + COUNTERS_OF_EACH_CHAIN_KEY, + ) as CountersOfEachChain[]; - return { ...nodeInfo, ...networkStatistics, ...HashRateAndTotalDifficulty }; + return { + ...nodeInfo, + ...networkStatistics, + ...HashRateAndTotalDifficulty, + countersOfEachChain, + }; } }