Skip to content

Commit

Permalink
Avoid multiple calls per second to fetch the superminority. Update co…
Browse files Browse the repository at this point in the history
…mments overall too
  • Loading branch information
catalinred committed Sep 9, 2024
1 parent 8db6e1e commit ff58c22
Showing 1 changed file with 66 additions and 95 deletions.
161 changes: 66 additions & 95 deletions src/hooks/useTransactionStats.js
Original file line number Diff line number Diff line change
@@ -1,67 +1,75 @@
import { useCallback, useEffect, useState } from "react";
import { makeRPCCall } from "../utils/rpcUtils";

/**
* After how many time the performance updates should be queried.
*
* @type {number}
*/
export const PERF_UPDATE_SEC = 5;

/**
* How long back should be queried.
* @type {number}
*/
export const SAMPLE_HISTORY_HOURS = 6;
// Constants for performance and sample history
export const PERF_UPDATE_SEC = 5; // Performance update interval in seconds
export const SAMPLE_HISTORY_HOURS = 6; // Hours to query for historical data

/**
* Current transaction costs.
*
* @type {string}
*/
export const TRANSACTION_COST = "$0.00025";

/**
* The JSON-RPC Mainnet-Endpoint to query stats from in Preview / Production.
*
* @type {string}
*/
export const MAINNET_ENDPOINT = "https://explorer-api.mainnet-beta.solana.com";
/**
* The JSON-RPC Endpoint to query stats from in Development.
*
* @type {string}
*/

const rpcNodeURL = process.env.NEXT_PUBLIC_RPC_ENDPOINT || "";
// RPC Node URL
const rpcNodeURL = process.env.NEXT_PUBLIC_RPC_ENDPOINT;
if (!rpcNodeURL) {
console.warn("Warning: RPC_ENDPOINT is not set.");
console.warn("Warning: NEXT_PUBLIC_RPC_ENDPOINT is not set.");
}

// Test for AbortController support.
// Check for AbortController support
const isAbortControllerSupported =
typeof window !== "undefined" && window.hasOwnProperty("AbortController");
const noOp = () => null;

/**
* Initializes an AbortController if supported.
*
* @returns {AbortController|{abort: (function(): null), signal: {}}}
*/
// Initializes an AbortController if supported
const initAbortController = () =>
isAbortControllerSupported
? new AbortController()
: { abort: noOp, signal: {} };

/**
* Fetches the superminority count.
*
* @returns {Promise<number>} The count of superminority validators.
*/
export const fetchSuperminority = async () => {
try {
const voteAccounts = await makeRPCCall({
method: "getVoteAccounts",
rpcNodeURL,
});

// Sort validators by stake in descending order
const sortedValidators = voteAccounts.result.current.sort((a, b) =>
Number(BigInt(b.activatedStake) - BigInt(a.activatedStake)),
);

const totalStake = sortedValidators.reduce(
(sum, validator) => sum + BigInt(validator.activatedStake),
BigInt(0),
);
const oneThirdStake = totalStake / BigInt(3);

let cumulativeStake = BigInt(0);
let superminorityCount = 0;

for (const validator of sortedValidators) {
if (cumulativeStake > oneThirdStake) break;
cumulativeStake += BigInt(validator.activatedStake);
superminorityCount++;
}

return superminorityCount; // Return the count of superminority validators
} catch (error) {
console.error("Error fetching superminority:", error);
return null; // Return null in case of error
}
};

/**
* Hook to return current transaction statistics from the JSON-RPC endpoint.
*
* @param {boolean} visible Only fire new queries when visible.
* @param {number} performanceUpdateSeconds Delay before next query.
* @param {number} sampleHistoryHours How many hours (60min.) the query should go back.
* @param {boolean} getLiveTransactionCount
* @param {boolean} getCurrentValidatorNodes
* @returns {{availableStats: boolean, avgTps: number, validators: number, totalTransactionCount: number}}
* @param {boolean} visible Only fire new queries when visible.
* @param {number} performanceUpdateSeconds Delay before next query.
* @param {number} sampleHistoryHours How many hours (60min.) the query should go back.
* @param {boolean} getLiveTransactionCount
* @param {boolean} getCurrentValidatorNodes
* @returns {{availableStats: boolean, avgTps: number, validators: number, totalTransactionCount: number, superminority: number}}
*/
export const useTransactionStats = ({
visible,
Expand All @@ -79,9 +87,6 @@ export const useTransactionStats = ({
const getRPCData = useCallback(
async (getValidatorNodes, getTransactionCount, abortSignal) => {
try {
// Do *not* batch these queries.
// Batching has been disabled on the homepage's API endpoint.

if (rpcNodeURL) {
await Promise.all([
(async () => {
Expand All @@ -91,7 +96,7 @@ export const useTransactionStats = ({
params: [60 * sampleHistoryHours],
rpcNodeURL,
});
// Calculate transactions per second for each sample.
// Calculate average transactions per second
const short = recentPerformanceSamples.result.reduce(
(shortResults, sample) => {
if (sample.numTransactions !== 0) {
Expand All @@ -103,7 +108,6 @@ export const useTransactionStats = ({
},
[],
);
// Use latest sample as average transactions per second.
const avgTps = Math.round(short[0]);
setAvgTps(avgTps);
setAvailableStats(true);
Expand Down Expand Up @@ -138,33 +142,37 @@ export const useTransactionStats = ({
if (error.name === "AbortError" || error.name === "TypeError") {
return;
}
console.error("Error fetching RPC data:", error);
}
},
[sampleHistoryHours],
);

// Load dynamic statistics only when the component is visible.
// Load statistics only when the component is visible
useEffect(() => {
// Get average transactions per second & validator node count
// (abort controlled when unmounted, so no state update will happen).
const abortController = initAbortController();
if (visible) {
getRPCData(
getCurrentValidatorNodes,
getLiveTransactionCount,
abortController.signal,
);

// Fetch superminority count
const fetchSuperminorityData = async () => {
const count = await fetchSuperminority();
setSuperminority(count);
};
fetchSuperminorityData();
}
// Set an interval to call JSON-RPC periodically.

const interval = setInterval(() => {
getRPCData(false, getLiveTransactionCount, abortController.signal);
}, performanceUpdateSeconds * 1000);

return () => {
abortController.abort();
// Clear interval to prevent multiple calls to setInterval.
if (interval) {
clearInterval(interval);
}
clearInterval(interval);
};
}, [
visible,
Expand All @@ -174,43 +182,6 @@ export const useTransactionStats = ({
getRPCData,
]);

const fetchSuperminority = async () => {
try {
const voteAccounts = await makeRPCCall({
method: "getVoteAccounts",
rpcNodeURL,
});

// Sort validators by stake in descending order
const sortedValidators = voteAccounts.result.current.sort((a, b) =>
Number(BigInt(b.activatedStake) - BigInt(a.activatedStake)),
);

const totalStake = sortedValidators.reduce(
(sum, validator) => sum + BigInt(validator.activatedStake),
BigInt(0),
);
const oneThirdStake = totalStake / BigInt(3);

let cumulativeStake = BigInt(0);
let superminorityCount = 0;

for (const validator of sortedValidators) {
if (cumulativeStake > oneThirdStake) break;
cumulativeStake += BigInt(validator.activatedStake);
superminorityCount++;
}

setSuperminority(superminorityCount);
} catch (error) {
console.error("Error fetching superminority:", error);
}
};

if (visible) {
fetchSuperminority();
}

return {
availableStats,
avgTps,
Expand Down

0 comments on commit ff58c22

Please sign in to comment.