Skip to content

Commit

Permalink
feat(stats): improve stats app resiliency
Browse files Browse the repository at this point in the history
feat: add try catch for stats

feat: improve stats app resiliency

fix: remove try catch from api

fix: avoid to confuse nodeService values

fix: add error handling by logging and sentry

fix: fix clean code

fix: add runOrLog function

fix: lint

fix: chagne logger name

fix: add error handling to node status

fix: add logging util

feat: improve speed to get stats

feat: add scope
  • Loading branch information
forbesus committed Oct 2, 2024
1 parent b39b057 commit 16e96ef
Show file tree
Hide file tree
Showing 6 changed files with 467 additions and 329 deletions.
3 changes: 2 additions & 1 deletion .commitlintrc.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@
"provider",
"deployment",
"certificate",
"dx"
"dx",
"stats"
]
]
}
Expand Down
40 changes: 27 additions & 13 deletions apps/api/src/routes/v1/dashboardData.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,16 @@
import { createRoute, OpenAPIHono, z } from "@hono/zod-openapi";

import { LoggerService } from "@src/core/services/logger/logger.service";
import { getBlocks } from "@src/services/db/blocksService";
import { getNetworkCapacity } from "@src/services/db/providerStatusService";
import { getDashboardData, getProviderGraphData } from "@src/services/db/statsService";
import { getTransactions } from "@src/services/db/transactionsService";
import { getChainStats } from "@src/services/external/apiNodeService";
import { createLoggingExecutor } from "@src/utils/logging";


const logger = new LoggerService({ context: "Dashboard" });
const runOrLog = createLoggingExecutor(logger)

const route = createRoute({
method: "get",
Expand Down Expand Up @@ -144,25 +150,33 @@ const route = createRoute({
});

export default new OpenAPIHono().openapi(route, async c => {
const chainStatsQuery = await getChainStats();
const dashboardData = await getDashboardData();
const networkCapacity = await getNetworkCapacity();
const networkCapacityStats = await getProviderGraphData("count");
const latestBlocks = await getBlocks(5);
const latestTransactions = await getTransactions(5);

const [{ now, compare }, chainStatsQuery, networkCapacity, networkCapacityStats, latestBlocks, latestTransactions] = await Promise.all([
runOrLog(getDashboardData),
runOrLog(getChainStats, {
bondedTokens: undefined,
totalSupply: undefined,
communityPool: undefined,
inflation: undefined,
stakingAPR: undefined
}),
runOrLog(getNetworkCapacity),
runOrLog(() => getProviderGraphData("count")),
runOrLog(() => getBlocks(5)),
runOrLog(() => getTransactions(5))
]);
const chainStats = {
height: latestBlocks[0].height,
transactionCount: latestBlocks[0].totalTransactionCount,
...chainStatsQuery
};
...chainStatsQuery,
height: latestBlocks && latestBlocks.length > 0 ? latestBlocks[0].height : undefined,
transactionCount: latestBlocks && latestBlocks.length > 0 ? latestBlocks[0].totalTransactionCount : undefined,
}

return c.json({
chainStats,
...dashboardData,
now,
compare,
networkCapacity,
networkCapacityStats,
latestBlocks,
latestTransactions
});
});
});
79 changes: 61 additions & 18 deletions apps/api/src/services/external/apiNodeService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import fetch from "node-fetch";
import { Op } from "sequelize";

import { cacheKeys, cacheResponse } from "@src/caching/helpers";
import { LoggerService } from "@src/core/services/logger/logger.service";
import { getTransactionByAddress } from "@src/services/db/transactionsService";
import {
CosmosGovProposalResponse,
Expand All @@ -28,45 +29,87 @@ import { CosmosMintInflationResponse } from "@src/types/rest/cosmosMintInflation
import { CosmosStakingPoolResponse } from "@src/types/rest/cosmosStakingPoolResponse";
import { coinToAsset } from "@src/utils/coin";
import { apiNodeUrl, averageBlockCountInAMonth, betaTypeVersion, betaTypeVersionMarket } from "@src/utils/constants";
import { createLoggingExecutor } from "@src/utils/logging";
import { getDeploymentRelatedMessages } from "../db/deploymentService";
import { getProviderList } from "../db/providerStatusService";

export async function getChainStats() {
const logger = new LoggerService({ context: "ApiNode" })
const runOrLog = createLoggingExecutor(logger)

const result = await cacheResponse(
60 * 5, // 5 minutes
cacheKeys.getChainStats,
async () => {
const bondedTokensQuery = axios.get<CosmosStakingPoolResponse>(`${apiNodeUrl}/cosmos/staking/v1beta1/pool`);
const supplyQuery = axios.get<CosmosBankSupplyResponse>(`${apiNodeUrl}/cosmos/bank/v1beta1/supply?pagination.limit=1000`);
const communityPoolQuery = axios.get<CosmosDistributionCommunityPoolResponse>(`${apiNodeUrl}/cosmos/distribution/v1beta1/community_pool`);
const inflationQuery = axios.get<CosmosMintInflationResponse>(`${apiNodeUrl}/cosmos/mint/v1beta1/inflation`);
const distributionQuery = axios.get<CosmosDistributionParamsResponse>(`${apiNodeUrl}/cosmos/distribution/v1beta1/params`);

const [bondedTokensResponse, supplyResponse, communityPoolResponse, inflationResponse, distributionResponse] = await Promise.all([
bondedTokensQuery,
supplyQuery,
communityPoolQuery,
inflationQuery,
distributionQuery
const bondedTokensAsPromised = await runOrLog(async () => {
const bondedTokensQuery = await axios.get<CosmosStakingPoolResponse>(
`${apiNodeUrl}/cosmos/staking/v1beta1/pool`
);
return parseInt(bondedTokensQuery.data.pool.bonded_tokens);
});

const totalSupplyAsPromised = await runOrLog(async () => {
const supplyQuery = await axios.get<CosmosBankSupplyResponse>(
`${apiNodeUrl}/cosmos/bank/v1beta1/supply?pagination.limit=1000`
);
return parseInt(
supplyQuery.data.supply.find((x) => x.denom === "uakt")?.amount || "0"
);
});

const communityPoolAsPromised = await runOrLog(async () => {
const communityPoolQuery = await axios.get<CosmosDistributionCommunityPoolResponse>(
`${apiNodeUrl}/cosmos/distribution/v1beta1/community_pool`
);
return parseFloat(
communityPoolQuery.data.pool.find((x) => x.denom === "uakt")?.amount || "0"
);
});

const inflationAsPromised = await runOrLog(async () => {
const inflationQuery = await axios.get<CosmosMintInflationResponse>(
`${apiNodeUrl}/cosmos/mint/v1beta1/inflation`
);
return parseFloat(inflationQuery.data.inflation || "0");
});

const communityTaxAsPromised = await runOrLog(async () => {
const distributionQuery = await axios.get<CosmosDistributionParamsResponse>(
`${apiNodeUrl}/cosmos/distribution/v1beta1/params`
);
return parseFloat(distributionQuery.data.params.community_tax || "0");
});

const [bondedTokens, totalSupply, communityPool, inflation, communityTax] = await Promise.all([
bondedTokensAsPromised,
totalSupplyAsPromised,
communityPoolAsPromised,
inflationAsPromised,
communityTaxAsPromised
]);

return {
communityPool: parseFloat(communityPoolResponse.data.pool.find(x => x.denom === "uakt").amount),
inflation: parseFloat(inflationResponse.data.inflation),
communityTax: parseFloat(distributionResponse.data.params.community_tax),
bondedTokens: parseInt(bondedTokensResponse.data.pool.bonded_tokens),
totalSupply: parseInt(supplyResponse.data.supply.find(x => x.denom === "uakt").amount)
communityPool,
inflation,
communityTax,
bondedTokens,
totalSupply,
};
},
true
);

let stakingAPR: number | undefined;
if (result.bondedTokens && result.bondedTokens > 0 && result.inflation && result.communityTax && result.totalSupply) {
stakingAPR = result.inflation * (1 - result.communityTax) * result.totalSupply / result.bondedTokens
}

return {
bondedTokens: result.bondedTokens,
totalSupply: result.totalSupply,
communityPool: result.communityPool,
inflation: result.inflation,
stakingAPR: (result.inflation * (1 - result.communityTax)) / (result.bondedTokens / result.totalSupply)
stakingAPR,
};
}

Expand Down
12 changes: 12 additions & 0 deletions apps/api/src/utils/logging.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { getSentry } from "@src/core/providers/sentry.provider";
import { LoggerService } from "@src/core/services/logger/logger.service";

export const createLoggingExecutor = (logger: LoggerService) => async <T>(cb: () => Promise<T>, defaultValue?: T): Promise<T> => {
try {
return await cb();
} catch (error) {
logger.error({ event: `Failed to fetch ${cb.name}`, error });
getSentry().captureException(error);
return defaultValue;
}
}
Loading

0 comments on commit 16e96ef

Please sign in to comment.