Skip to content
This repository has been archived by the owner on Sep 26, 2024. It is now read-only.

feat(frontend): Implementation of new design on Nodes page #613

Merged
merged 12 commits into from
May 18, 2021
Merged
11 changes: 7 additions & 4 deletions backend/src/config.js
Original file line number Diff line number Diff line change
Expand Up @@ -36,14 +36,17 @@ exports.regularSyncMissingNearcoreStateInterval =
parseInt(process.env.NEAR_REGULAR_SYNC_MISSING_NEARCORE_STATE_INTERVAL) ||
60000;

exports.regularQueryRPCInterval =
parseInt(process.env.NEAR_REGULAR_QUERY_RPC_INTERVAL) || 1000;
exports.regularPublishFinalityStatusInterval =
parseInt(process.env.NEAR_REGULAR_PUBLISH_FINALITY_STATUS_INTERVAL) || 1000;

exports.regularQueryStatsInterval =
parseInt(process.env.NEAR_REGULAR_QUERY_STATS_INTERVAL) || 1000;

exports.regularCheckNodeStatusInterval =
parseInt(process.env.NEAR_REGULAR_QUERY_NODE_INTERVAL) || 1000;
exports.regularPublishNetworkInfoInterval =
parseInt(process.env.NEAR_REGULAR_PUBLISH_NETWORK_INFO_INTERVAL) || 1000;

exports.regularFetchStakingPoolsInfoInterval =
parseInt(process.env.NEAR_REGULAR_FETCH_STAKING_POOLS_INFO_INTERVAL) || 15000;

exports.regularStatsInterval =
parseInt(process.env.NEAR_REGULAR_STATS_INTERVAL) || 3600000;
Expand Down
8 changes: 4 additions & 4 deletions backend/src/db-utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ const getSyncedGenesis = async (options) => {
const queryGenesisAccountCount = async () => {
return await querySingleRow(
[
`SELECT
`SELECT
COUNT(*)
FROM accounts
WHERE created_by_receipt_id IS NULL`,
Expand All @@ -45,7 +45,7 @@ const queryGenesisAccountCount = async () => {
};

// query for node information
const addNodeInfo = async (nodes) => {
const extendWithTelemetryInfo = async (nodes) => {
const accountArray = nodes.map((node) => node.account_id);
let nodesInfo = await queryRows([
`SELECT ip_address AS ipAddress, account_id AS accountId, node_id AS nodeId,
Expand Down Expand Up @@ -336,7 +336,7 @@ const queryDeletedAccountsCountAggregatedByDate = async () => {
DATE_TRUNC('day', TO_TIMESTAMP(receipts.included_in_block_timestamp / 1000000000)) AS date,
COUNT(accounts.deleted_by_receipt_id) AS deleted_accounts_count_by_date
FROM accounts
JOIN receipts ON receipts.receipt_id = accounts.deleted_by_receipt_id
JOIN receipts ON receipts.receipt_id = accounts.deleted_by_receipt_id
WHERE receipts.included_in_block_timestamp < (CAST(EXTRACT(EPOCH FROM DATE_TRUNC('day', NOW())) AS bigint) * 1000 * 1000 * 1000)
GROUP BY date
ORDER BY date`,
Expand Down Expand Up @@ -531,7 +531,7 @@ const queryPartnerUniqueUserAmount = async () => {

// node part
exports.queryOnlineNodes = queryOnlineNodes;
exports.addNodeInfo = addNodeInfo;
exports.extendWithTelemetryInfo = extendWithTelemetryInfo;
exports.pickOnlineValidatingNode = pickOnlineValidatingNode;

// genesis
Expand Down
132 changes: 104 additions & 28 deletions backend/src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,15 @@ const {
regularCheckGenesisInterval,
regularSyncNewNearcoreStateInterval,
regularSyncMissingNearcoreStateInterval,
regularQueryRPCInterval,
regularPublishFinalityStatusInterval,
regularQueryStatsInterval,
regularCheckNodeStatusInterval,
regularPublishNetworkInfoInterval,
regularFetchStakingPoolsInfoInterval,
regularStatsInterval,
} = require("./config");
const { DS_LEGACY_SYNC_BACKEND, DS_INDEXER_BACKEND } = require("./consts");

const { nearRpc, queryFinalTimestamp, queryNodeStats } = require("./near");
const { nearRpc, queryFinalBlock, queryEpochStats } = require("./near");

const {
syncNewNearcoreState,
Expand All @@ -26,7 +27,7 @@ const {
const { setupWamp, wampPublish } = require("./wamp");

const {
addNodeInfo,
extendWithTelemetryInfo,
queryOnlineNodes,
pickOnlineValidatingNode,
getSyncedGenesis,
Expand All @@ -52,6 +53,10 @@ const {
aggregateLiveAccountsCountByDate,
} = require("./stats");

let currentValidators = [];
let currentProposals = [];
let stakingPoolsInfo = new Map();

async function startLegacySync() {
console.log("Starting NEAR Explorer legacy syncing service...");

Expand Down Expand Up @@ -234,56 +239,127 @@ async function main() {
console.log("Starting WAMP worker...");
wamp.open();

// regular check finalTimesamp and publish to final-timestamp uri
const regularCheckFinalTimestamp = async () => {
// regularly publish the latest information about the height and timestamp of the final block
const regularPublishFinalityStatus = async () => {
console.log("Starting regular final timestamp check...");
try {
if (wamp.session) {
const finalTimestamp = await queryFinalTimestamp();
wampPublish("final-timestamp", { finalTimestamp }, wamp);
const finalBlock = await queryFinalBlock();
wampPublish(
"finality-status",
{
finalBlockTimestampNanosecond: finalBlock.header.timestamp_nanosec,
finalBlockHeight: finalBlock.header.height,
},
wamp
);
}
console.log("Regular final timestamp check is completed.");
} catch (error) {
console.warn("Regular final timestamp check crashed due to:", error);
}
setTimeout(regularCheckFinalTimestamp, regularQueryRPCInterval);
setTimeout(
regularPublishFinalityStatus,
regularPublishFinalityStatusInterval
);
};
setTimeout(regularCheckFinalTimestamp, 0);
setTimeout(regularPublishFinalityStatus, 0);

// regular check node status and publish to nodes uri
const regularCheckNodeStatus = async () => {
console.log("Starting regular node status check...");
// regularly publish information about validators, proposals, staking pools, and online nodes
const regularPublishNetworkInfo = async () => {
console.log("Starting regular network info publishing...");
try {
if (wamp.session) {
let { currentValidators, proposals } = await queryNodeStats();
let validators = await addNodeInfo(currentValidators);
let onlineValidatingNodes = pickOnlineValidatingNode(validators);
let onlineNodes = await queryOnlineNodes();
if (!onlineNodes) {
onlineNodes = [];
const epochStats = await queryEpochStats();
currentValidators = await extendWithTelemetryInfo(
epochStats.currentValidators
);
currentProposals = await extendWithTelemetryInfo(
epochStats.currentProposals
);
const onlineValidatingNodes = pickOnlineValidatingNode(
currentValidators
);
const onlineNodes = await queryOnlineNodes();

if (stakingPoolsInfo) {
currentValidators.forEach((validator) => {
const stakingPoolInfo = stakingPoolsInfo.get(validator.account_id);
if (stakingPoolInfo) {
validator.fee = stakingPoolInfo.fee;
validator.delegatorsCount = stakingPoolInfo.delegatorsCount;
}
});
currentProposals.forEach((validator) => {
const stakingPoolInfo = stakingPoolsInfo.get(validator.account_id);
if (stakingPoolInfo) {
validator.fee = stakingPoolInfo.fee;
validator.delegatorsCount = stakingPoolInfo.delegatorsCount;
}
});
}

wampPublish(
"nodes",
{ onlineNodes, validators, proposals, onlineValidatingNodes },
{
onlineNodes,
currentValidators,
currentProposals,
onlineValidatingNodes,
},
wamp
);
wampPublish(
"node-stats",
"network-stats",
{
validatorAmount: validators.length,
onlineNodeAmount: onlineNodes.length,
proposalAmount: proposals.length,
currentValidatorsCount: currentValidators.length,
currentProposalsCount: currentProposals.length,
onlineNodesCount: onlineNodes.length,
epochLength: epochStats.epochLength,
epochStartHeight: epochStats.epochStartHeight,
totalStake: epochStats.totalStake,
seatPrice: epochStats.seatPrice,
},
wamp
);
}
console.log("Regular node status check is completed.");
console.log("Regular regular network info publishing is completed.");
} catch (error) {
console.warn("Regular node status check crashed due to:", error);
console.warn(
"Regular regular network info publishing crashed due to:",
error
);
}
setTimeout(regularCheckNodeStatus, regularCheckNodeStatusInterval);
setTimeout(regularPublishNetworkInfo, regularPublishNetworkInfoInterval);
};
setTimeout(regularPublishNetworkInfo, 0);

// Periodic check of validators' staking pool fee and delegators count
const regularFetchStakingPoolsInfo = async () => {
const stakingPoolsAccountId = new Set([
...currentValidators.map(({ account_id }) => account_id),
...currentProposals.map(({ account_id }) => account_id),
]);

for (const stakingPoolAccountId of stakingPoolsAccountId) {
const fee = await nearRpc.callViewMethod(
stakingPoolAccountId,
"get_reward_fee_fraction",
{}
);
const delegatorsCount = await nearRpc.callViewMethod(
stakingPoolAccountId,
"get_number_of_accounts",
{}
);
stakingPoolsInfo.set(stakingPoolAccountId, { fee, delegatorsCount });
}
setTimeout(
regularFetchStakingPoolsInfo,
regularFetchStakingPoolsInfoInterval
);
};
setTimeout(regularCheckNodeStatus, 0);
setTimeout(regularFetchStakingPoolsInfo, 0);

if (isLegacySyncBackendEnabled) {
await startLegacySync();
Expand Down
62 changes: 49 additions & 13 deletions backend/src/near.js
Original file line number Diff line number Diff line change
@@ -1,25 +1,61 @@
const nearApi = require("near-api-js");

const BN = require("bn.js");

const { nearRpcUrl } = require("./config");

const nearRpc = new nearApi.providers.JsonRpcProvider(nearRpcUrl);

let seatPrice = null;
let totalStake = null;
let currentEpochStartHeight = null;

// TODO: Provide an equivalent method in near-api-js, so we don't need to hack it around.
nearRpc.callViewMethod = async function (contractName, methodName, args) {
const account = new nearApi.Account({ provider: this });
return await account.viewFunction(contractName, methodName, args);
};

const queryFinalTimestamp = async () => {
const finalBlock = await nearRpc.sendJsonRpc("block", { finality: "final" });
return finalBlock.header.timestamp_nanosec;
const queryFinalBlock = async () => {
return await nearRpc.sendJsonRpc("block", { finality: "final" });
};

const queryNodeStats = async () => {
let nodes = await nearRpc.sendJsonRpc("validators", [null]);
let proposals = nodes.current_proposals;
let currentValidators = getCurrentNodes(nodes);
return { currentValidators, proposals };
const queryEpochStats = async () => {
const networkProtocolConfig = await nearRpc.sendJsonRpc(
"EXPERIMENTAL_protocol_config",
{ finality: "final" }
);
const epochStatus = await nearRpc.sendJsonRpc("validators", [null]);
const numSeats =
networkProtocolConfig.num_block_producer_seats +
networkProtocolConfig.avg_hidden_validator_seats_per_shard.reduce(
(a, b) => a + b
);
const currentProposals = epochStatus.current_proposals;
const currentValidators = getCurrentNodes(epochStatus);
const { epoch_start_height: epochStartHeight } = epochStatus;
const { epoch_length: epochLength } = networkProtocolConfig;

if (currentEpochStartHeight !== epochStartHeight) {
// Update seat_price and total_stake each time when epoch starts
currentEpochStartHeight = epochStartHeight;
seatPrice = nearApi.validators
.findSeatPrice(epochStatus.current_validators, numSeats)
.toString();

totalStake = currentValidators
.reduce((acc, node) => acc.add(new BN(node.stake)), new BN(0))
.toString();
}

return {
epochLength,
epochStartHeight,
currentValidators,
currentProposals,
totalStake,
seatPrice,
};
};

const signNewValidators = (newValidators) => {
Expand All @@ -34,9 +70,9 @@ const signRemovedValidators = (removedValidators) => {
}
};

const getCurrentNodes = (nodes) => {
let currentValidators = nodes.current_validators;
let nextValidators = nodes.next_validators;
const getCurrentNodes = (epochStatus) => {
let currentValidators = epochStatus.current_validators;
let nextValidators = epochStatus.next_validators;
const {
newValidators,
removedValidators,
Expand All @@ -48,5 +84,5 @@ const getCurrentNodes = (nodes) => {
};

exports.nearRpc = nearRpc;
exports.queryFinalTimestamp = queryFinalTimestamp;
exports.queryNodeStats = queryNodeStats;
exports.queryFinalBlock = queryFinalBlock;
exports.queryEpochStats = queryEpochStats;
40 changes: 17 additions & 23 deletions frontend/cypress/integration/nodes-page.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,40 +10,34 @@ context("Nodes", () => {
it("Check for node page", () => {
cy.url().should("include", "/nodes/validators");
cy.get("#validator-node").contains("Validating");
cy.get("#online-node").contains("Online-nodes");
cy.get("#proposal-node").contains("Proposal-nodes");
// cy.get("#online-node").contains("Online-nodes");
cy.get("#proposal-node").contains("Proposed");
});

it("Check validators tab", () => {
cy.url().should("include", "/nodes/validators");
cy.get(".node-selector").should("have.class", "node-selected");
cy.wait(3000)
.get(".node-row .node-row-title")
.within(($el) => cy.get($el).should("exist", $el.text()));
cy.get(".node-row .node-row-text .row div").within(($el) =>
cy.get($el).should("exist", $el.text())
);
cy.get(".node-row .node-row-txid").should("exist");
cy.wait(3000).get(".validator-nodes-row td").should("exist");
});

it("Check online nodes tab", () => {
cy.get("#online-node").click();
cy.get(".node-selector").should("have.class", "node-selected");
cy.wait(3000)
.get(".node-row .node-row-title")
.within(($el) => cy.get($el).should("exist", $el.text()));
cy.get(".node-row .node-row-txid").within(($el) =>
cy.get($el).should("exist", $el.text())
);
});
// it("Check online nodes tab", () => {
// cy.get("#online-node").click();
// cy.get(".node-selector").should("have.class", "node-selected");
// cy.wait(3000)
// .get(".node-row .node-row-title")
// .within(($el) => cy.get($el).should("exist", $el.text()));
// cy.get(".node-row .node-row-txid").within(($el) =>
// cy.get($el).should("exist", $el.text())
// );
// });

it("Check proposal nodes tab", () => {
cy.get("#proposal-node").click();
cy.get(".node-selector").should("have.class", "node-selected");
});

it("Check nodes map tab", () => {
cy.get("#node-map").click();
cy.wait(10000).get(".mapBackground").should("exist");
});
// it("Check nodes map tab", () => {
// cy.get("#node-map").click();
// cy.wait(10000).get(".mapBackground").should("exist");
// });
});
Loading