Skip to content

Commit

Permalink
Refactor NodeList module to improve performance and remove unnecessar…
Browse files Browse the repository at this point in the history
…y node lists code
  • Loading branch information
tanuj-shardeum committed May 6, 2024
1 parent de4fed0 commit e7ca7b9
Show file tree
Hide file tree
Showing 5 changed files with 43 additions and 73 deletions.
4 changes: 2 additions & 2 deletions src/API.ts
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,7 @@ export function registerRoutes(server: FastifyInstance<Server, IncomingMessage,

if (config.experimentalSnapshot) {
const data = {
nodeList: NodeList.getList(),
nodeList: [firstNode],
}
if (cycleRecordWithShutDownMode) {
// For restore network to start the network from the 'restart' mode
Expand All @@ -127,7 +127,7 @@ export function registerRoutes(server: FastifyInstance<Server, IncomingMessage,
res = Crypto.sign<P2P.FirstNodeResponse>(data)
} else {
res = Crypto.sign<P2P.FirstNodeResponse>({
nodeList: NodeList.getList(),
nodeList: [firstNode],
joinRequest: P2P.createArchiverJoinRequest(),
dataRequestCycle: Data.createDataRequest<P2PTypes.CycleCreatorTypes.CycleRecord>(
P2PTypes.SnapshotTypes.TypeNames.CYCLE,
Expand Down
4 changes: 2 additions & 2 deletions src/Data/AccountDataProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ export const validateAccountDataRequest = (
return { success: false, error: 'Invalid sign object attached' }
}
const nodePublicKey = sign.owner
if (!Object.prototype.hasOwnProperty.call(NodeList.byPublicKey, nodePublicKey)) {
if (!NodeList.byPublicKey.has(nodePublicKey)) {
return { success: false, error: 'This node is not found in the nodelist!' }
}
if (!servingValidators.has(nodePublicKey) && servingValidators.size >= config.maxValidatorsToServe) {
Expand Down Expand Up @@ -143,7 +143,7 @@ export const validateAccountDataByListRequest = (
return { success: false, error: 'Invalid sign object attached' }
}
const nodePublicKey = sign.owner
if (!Object.prototype.hasOwnProperty.call(NodeList.byPublicKey, nodePublicKey)) {
if (!NodeList.byPublicKey.has(nodePublicKey)) {
return { success: false, error: 'This node is not found in the nodelist!' }
}
// TODO: Add max limit check for accountIds list query
Expand Down
2 changes: 1 addition & 1 deletion src/Data/Cycles.ts
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ export async function processCycles(cycles: P2PTypes.CycleCreatorTypes.CycleData
if (currentNetworkMode === 'shutdown') {
Logger.mainLogger.debug(Date.now(), `❌ Shutdown Cycle Record received at Cycle #: ${cycle.counter}`)
await Utils.sleep(currentCycleDuration)
NodeList.clearNodeListCache()
NodeList.clearNodeLists()
await clearDataSenders()
setShutdownCycleRecord(cycle)
NodeList.toggleFirstNode()
Expand Down
6 changes: 3 additions & 3 deletions src/Data/Data.ts
Original file line number Diff line number Diff line change
Expand Up @@ -470,7 +470,7 @@ export async function replaceDataSender(publicKey: NodeList.ConsensusNodeInfo['p
}
unsubscribeDataSender(publicKey)
// eslint-disable-next-line security/detect-object-injection
const node = NodeList.byPublicKey[publicKey]
const node = NodeList.byPublicKey.get(publicKey)
if (node) {
const nodeIndex = NodeList.activeListByIdSorted.findIndex((node) => node.publicKey === publicKey)
if (nodeIndex > -1) {
Expand Down Expand Up @@ -540,7 +540,7 @@ export function addDataSender(sender: DataSender): void {

async function getConsensusRadius(): Promise<number> {
// If there is no node, return existing currentConsensusRadius
if (NodeList.getList().length === 0) return currentConsensusRadius
if (NodeList.isEmpty()) return currentConsensusRadius

// Define the query function to get the network config from a node
const queryFn = async (node): Promise<object> => {
Expand All @@ -567,7 +567,7 @@ async function getConsensusRadius(): Promise<number> {

// Get the list of 10 max random active nodes or the first node if no active nodes are available
const nodes =
NodeList.getActiveNodeCount() > 0 ? NodeList.getRandomActiveNodes(10) : NodeList.getList().slice(0, 1)
NodeList.getActiveNodeCount() > 0 ? NodeList.getRandomActiveNodes(10) : [NodeList.getFirstNode()]

// Use robustQuery to get the consensusRadius from multiple nodes
const tallyItem = await robustQuery(
Expand Down
100 changes: 35 additions & 65 deletions src/NodeList.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,15 +64,19 @@ export const fromP2PTypesNode = (node: P2PTypes.NodeListTypes.Node): JoinedConse

// STATE

const list: ConsensusNodeInfo[] = []
const standbyList: Map<string, ConsensusNodeInfo> = new Map()
const syncingList: Map<string, ConsensusNodeInfo> = new Map()
const activeList: Map<string, ConsensusNodeInfo> = new Map()

// Map to get node public key by node Id
const byId: Map<string, string> = new Map()

// Map to get node info by public key, stores all nodes
export const byPublicKey: Map<string, ConsensusNodeInfo> = new Map()

// Array of active nodes sorted by id
export let activeListByIdSorted: ConsensusNodeInfo[] = []
export let byPublicKey: { [publicKey: string]: ConsensusNodeInfo } = {}
let byIpPort: { [ipPort: string]: ConsensusNodeInfo } = {}
export let byId: { [id: string]: ConsensusNodeInfo } = {}
let publicKeyToId: { [publicKey: string]: string } = {}

export let foundFirstNode = false

export type SignedNodeList = {
Expand All @@ -86,38 +90,28 @@ export const realUpdatedTimes: Map<string, number> = new Map()

// METHODS

function getIpPort(node: Node): string {
if (node.ip && node.port) {
return node.ip + ':' + node.port
} else if (node.externalIp && node.externalPort) {
return node.externalIp + ':' + node.externalPort
}
return ''
}

export function isEmpty(): boolean {
return list.length <= 0
return byPublicKey.size === 0
}

type Node = ConsensusNodeInfo | JoinedConsensor

export function addNodes(status: NodeStatus, nodes: Node[]): void {
if (nodes.length === 0) return
Logger.mainLogger.debug(`Adding ${status} nodes to the list`, nodes.length, nodes)

for (const node of nodes) {
const ipPort = getIpPort(node)

// If node not in lists, add it
// eslint-disable-next-line security/detect-object-injection
if (byPublicKey[node.publicKey] === undefined && byIpPort[ipPort] === undefined) {
list.push(node)
if (!byPublicKey.has(node.publicKey)) {
const key = node.publicKey
switch (status) {
case NodeStatus.SYNCING:
if (standbyList.has(key)) standbyList.delete(key)
if (activeList.has(key)) {
activeList.delete(key)
activeListByIdSorted = activeListByIdSorted.filter((node) => node.publicKey === key)
activeListByIdSorted = activeListByIdSorted.filter((node) => node.publicKey !== key)
}
if (syncingList.has(key)) break
syncingList.set(node.publicKey, node)
Expand All @@ -135,18 +129,16 @@ export function addNodes(status: NodeStatus, nodes: Node[]): void {
break
}
/* eslint-disable security/detect-object-injection */
byPublicKey[node.publicKey] = node
byIpPort[ipPort] = node
byPublicKey.set(node.publicKey, node)
/* eslint-enable security/detect-object-injection */
}

// If an id is given, update its id
if (node.id) {
const entry = byPublicKey[node.publicKey]
const entry = byPublicKey.get(node.publicKey)
if (entry) {
entry.id = node.id
publicKeyToId[node.publicKey] = node.id
byId[node.id] = node
byId.set(node.id, node.publicKey)
}
}
}
Expand All @@ -155,13 +147,11 @@ export function refreshNodes(status: NodeStatus, nodes: ConsensusNodeInfo[] | Jo
if (nodes.length === 0) return
Logger.mainLogger.debug('Refreshing nodes', nodes.length, nodes)
for (const node of nodes) {
const ipPort = getIpPort(node)

// If node not in lists, add it
// eslint-disable-next-line security/detect-object-injection
if (byPublicKey[node.publicKey] === undefined && byIpPort[ipPort] === undefined) {
if (!byPublicKey.has(node.publicKey)) {
Logger.mainLogger.debug('adding new node during refresh', node.publicKey)
list.push(node)
switch (status) {
case NodeStatus.SYNCING:
syncingList.set(node.publicKey, node)
Expand All @@ -176,18 +166,16 @@ export function refreshNodes(status: NodeStatus, nodes: ConsensusNodeInfo[] | Jo
break
}

byPublicKey[node.publicKey] = node
byPublicKey.set(node.publicKey, node)
// eslint-disable-next-line security/detect-object-injection
byIpPort[ipPort] = node
}

// If an id is given, update its id
if (node.id) {
const entry = byPublicKey[node.publicKey]
const entry = byPublicKey.get(node.publicKey)
if (entry) {
entry.id = node.id
publicKeyToId[node.publicKey] = node.id
byId[node.id] = node
byId.set(node.id, node.publicKey)
}
}
}
Expand All @@ -196,38 +184,23 @@ export function refreshNodes(status: NodeStatus, nodes: ConsensusNodeInfo[] | Jo
export function removeNodes(publicKeys: string[]): void {
if (publicKeys.length > 0) Logger.mainLogger.debug('Removing nodes', publicKeys)
// Efficiently remove nodes from nodelist
const keysToDelete: Map<ConsensusNodeInfo['publicKey'], boolean> = new Map()

for (const key of publicKeys) {
// eslint-disable-next-line security/detect-object-injection
if (byPublicKey[key] === undefined) {
if (!byPublicKey.has(key)) {
console.warn(`removeNodes: publicKey ${key} not in nodelist`)
continue
}
keysToDelete.set(key, true)
/* eslint-disable security/detect-object-injection */
delete byIpPort[getIpPort(byPublicKey[key])]
delete byPublicKey[key]
const id = publicKeyToId[key]
activeListByIdSorted = activeListByIdSorted.filter((node) => node.id !== id)
delete byId[id]
delete publicKeyToId[key]
syncingList.delete(key)
activeList.delete(key)
standbyList.delete(key)
const id = byPublicKey.get(key).id
activeListByIdSorted = activeListByIdSorted.filter((node) => node.id !== byPublicKey.get(key).id)
byId.delete(id)
byPublicKey.delete(key)
/* eslint-enable security/detect-object-injection */
}

if (keysToDelete.size > 0) {
let key: string
for (let i = list.length - 1; i > -1; i--) {
// eslint-disable-next-line security/detect-object-injection
key = list[i].publicKey
if (keysToDelete.has(key)) {
list.splice(i, 1)
if (syncingList.has(key)) syncingList.delete(key)
else if (activeList.has(key)) activeList.delete(key)
else if (standbyList.has(key)) standbyList.delete(key)
}
}
}
}

export const addStandbyNodes = (nodes: ConsensusNodeInfo[]): void => {
Expand All @@ -251,7 +224,7 @@ export function setStatus(status: Exclude<NodeStatus, NodeStatus.STANDBY>, publi
Logger.mainLogger.debug(`Updating status ${status} for nodes`, publicKeys)
for (const key of publicKeys) {
// eslint-disable-next-line security/detect-object-injection
const node = byPublicKey[key]
const node = byPublicKey.get(key)
if (node === undefined) {
console.warn(`setStatus: publicKey ${key} not in nodelist`)
continue
Expand Down Expand Up @@ -281,8 +254,8 @@ export function setStatus(status: Exclude<NodeStatus, NodeStatus.STANDBY>, publi
}
}

export function getList(): ConsensusNodeInfo[] {
return list
export function getFirstNode(): ConsensusNodeInfo | undefined {
return byPublicKey.values().next().value;
}

export function getActiveList(id_sorted = true): ConsensusNodeInfo[] {
Expand Down Expand Up @@ -317,7 +290,7 @@ export const getCachedNodeList = (): SignedNodeList => {

for (let index = 0; index < config.N_RANDOM_NODELIST_BUCKETS; index++) {
// If we dont have any active nodes, send back the first node in our list
const nodeList = nodeCount < 1 ? getList().slice(0, 1) : getRandomActiveNodes(nodeCount)
const nodeList = nodeCount < 1 ? [getFirstNode()] : getRandomActiveNodes(nodeCount)
const sortedNodeList = [...nodeList].sort(byAscendingNodeId)
const signedSortedNodeList = Crypto.sign({
nodeList: sortedNodeList,
Expand Down Expand Up @@ -427,7 +400,7 @@ export function changeNodeListInRestore(): void {
}

/** Resets/Cleans all the NodeList associated Maps and Array variables/caches */
export function clearNodeListCache(): void {
export function clearNodeLists(): void {
try {
activeNodescache.clear()
fullNodesCache.clear()
Expand All @@ -436,11 +409,8 @@ export function clearNodeListCache(): void {
realUpdatedTimes.clear()
cacheUpdatedTimes.clear()

list.length = 0
byId = {}
byIpPort = {}
byPublicKey = {}
publicKeyToId = {}
byId.clear()
byPublicKey.clear()
activeListByIdSorted = []
} catch (e) {
Logger.mainLogger.error('Error thrown in clearNodeListCache', e)
Expand Down

0 comments on commit e7ca7b9

Please sign in to comment.