diff --git a/block-pool/index.js b/block-pool/index.js index 52069e10a..d6e6d39ff 100644 --- a/block-pool/index.js +++ b/block-pool/index.js @@ -15,27 +15,33 @@ class BlockPool { // Mapping of a block hash to the block's info (block, proposal tx, voting txs) // e.g. { []: { block, proposal, votes: { [
]: }, tallied } } - this.hashToBlockInfo = {}; + this.hashToBlockInfo = new Map(); // Mapping of a block hash to the invalid block's info. // e.g. { []: { block, proposal, votes: { [
]: } } } - this.hashToInvalidBlockInfo = {}; + this.hashToInvalidBlockInfo = new Map(); // Mapping of a block hash to the new db state this.hashToDb = new Map(); // Mapping of a block hash to a set of block hashes that extend the block. // e.g. { []: Set } - this.hashToNextBlockSet = {}; + this.hashToNextBlockSet = new Map(); // Mapping of an epoch to the hash of a block proposed at the epoch. // e.g. { []: } - this.epochToBlock = {}; + this.epochToBlock = new Map(); // Mapping of a number to a set of block hashes proposed for the number. // e.g. { []: Set } - this.numberToBlockSet = {}; + this.numberToBlockSet = new Map(); this.heighestSeenBlockNumber = -1; } getLongestNotarizedChainHeight() { - return this.longestNotarizedChainTips.length > 0 ? - this.hashToBlockInfo[this.longestNotarizedChainTips[0]].block.number : this.node.bc.lastBlockNumber(); + if (this.longestNotarizedChainTips.length === 0) { + return this.node.bc.lastBlockNumber(); + } + const blockInfo = this.hashToBlockInfo.get(this.longestNotarizedChainTips[0]); + if (!blockInfo || !blockInfo.block) { + return this.node.bc.lastBlockNumber(); + } + return blockInfo.block.number; } updateHighestSeenBlockNumber(blockNumber) { @@ -51,7 +57,7 @@ class BlockPool { updateLongestNotarizedChains() { const LOG_HEADER = 'updateLongestNotarizedChains'; const currentLongest = this.longestNotarizedChainTips.length ? - _get(this.hashToBlockInfo[this.longestNotarizedChainTips[0]], 'block.number') + _get(this.hashToBlockInfo.get(this.longestNotarizedChainTips[0]), 'block.number') : this.node.bc.lastBlockNumber(); if (currentLongest == undefined) { logger.error(`[${LOG_HEADER}] Notarized block's info is missing: ` + @@ -71,7 +77,7 @@ class BlockPool { const chain = []; const recordedInvalidBlockHashSet = new Set(); const finalizedBlock = this.node.bc.lastBlock(); - let currBlockWithInfo = this.hashToBlockInfo[blockHash]; + let currBlockWithInfo = this.hashToBlockInfo.get(blockHash); if (!currBlockWithInfo || !currBlockWithInfo.block || currBlockWithInfo.block.number <= finalizedBlock.number) { return { chain, recordedInvalidBlockHashSet }; @@ -85,7 +91,7 @@ class BlockPool { recordedInvalidBlockHashSet.add(invalidBlockHash); } } - currBlockWithInfo = this.hashToBlockInfo[block.last_hash]; + currBlockWithInfo = this.hashToBlockInfo.get(block.last_hash); } logger.debug(`[${LOG_HEADER}] currBlockWithInfo: ` + `${JSON.stringify(currBlockWithInfo, null, 2)}` + @@ -106,7 +112,7 @@ class BlockPool { const lastBlockNumber = this.node.bc.lastBlockNumber(); const lastFinalized = fromBlock ? fromBlock : lastBlockNumber < 1 ? { block: this.node.bc.lastBlock(), notarized: true } - : this.hashToBlockInfo[this.node.bc.lastBlock().hash]; + : this.hashToBlockInfo.get(this.node.bc.lastBlock().hash); logger.debug(`[${LOG_HEADER}] lastFinalized: ${JSON.stringify(lastFinalized, null, 2)}`); const chainList = []; this.dfsLongest(lastFinalized, [], chainList, withInfo); @@ -123,7 +129,7 @@ class BlockPool { } else { currentChain.push(currentNode.block); } - const nextBlockSet = this.hashToNextBlockSet[currentNode.block.hash]; + const nextBlockSet = this.hashToNextBlockSet.get(currentNode.block.hash); const blockNumber = currentNode.block.number; let longestNumber = chainList.length ? withInfo ? chainList[0][chainList[0].length - 1].block.number : @@ -145,7 +151,7 @@ class BlockPool { } for (const val of nextBlockSet) { - this.dfsLongest(this.hashToBlockInfo[val], currentChain, chainList, withInfo); + this.dfsLongest(this.hashToBlockInfo.get(val), currentChain, chainList, withInfo); } currentChain.pop(); logger.debug(`[${LOG_HEADER}] returning.. currentChain: ${JSON.stringify(currentChain, null, 2)}`); @@ -156,14 +162,14 @@ class BlockPool { // 2. ends with three blocks that have consecutive epoch numbers getFinalizableChain(isGenesisStart) { const genesisBlockHash = this.node.bc.genesisBlockHash; - const genesisBlockInfo = this.hashToBlockInfo[genesisBlockHash]; + const genesisBlockInfo = this.hashToBlockInfo.get(genesisBlockHash); const chainWithGenesisBlock = genesisBlockInfo ? [genesisBlockInfo.block] : []; if (isGenesisStart) { return chainWithGenesisBlock; } const lastFinalized = { block: this.node.bc.lastBlock(), notarized: true }; const chain = this.dfsFinalizable(lastFinalized, []); - if (chain.length === 0 && this.node.bc.lastBlockNumber() < 0 && this.hashToBlockInfo[genesisBlockHash]) { + if (chain.length === 0 && this.node.bc.lastBlockNumber() < 0 && this.hashToBlockInfo.has(genesisBlockHash)) { // When node first started (fetching from peers or loading from disk) return chainWithGenesisBlock; } @@ -177,7 +183,7 @@ class BlockPool { return BlockPool.endsWithThreeConsecutiveEpochs(currentChain) ? [...currentChain] : []; } currentChain.push(currentNode.block); - const nextBlockSet = this.hashToNextBlockSet[currentNode.block.hash]; + const nextBlockSet = this.hashToNextBlockSet.get(currentNode.block.hash); if (!nextBlockSet || !nextBlockSet.size) { if (BlockPool.endsWithThreeConsecutiveEpochs(currentChain)) { logger.debug(`[${LOG_HEADER}] No next blocks but found a finalizable chain`); @@ -192,7 +198,7 @@ class BlockPool { let res; let longest = BlockPool.endsWithThreeConsecutiveEpochs(currentChain) ? [...currentChain] : []; for (const blockHash of nextBlockSet) { - res = this.dfsFinalizable(this.hashToBlockInfo[blockHash], currentChain); + res = this.dfsFinalizable(this.hashToBlockInfo.get(blockHash), currentChain); if (res && BlockPool.endsWithThreeConsecutiveEpochs(res) && res.length > longest.length) { longest = res; } @@ -210,7 +216,7 @@ class BlockPool { // FIXME: return block that's on the longest & heaviest notarized chain getNotarizedBlockListByNumber(number) { - const blockArr = Object.values(this.hashToBlockInfo) + const blockArr = Array.from(this.hashToBlockInfo.values()) .filter((blockInfo) => !!blockInfo.block && blockInfo.block.number === number && blockInfo.proposal && blockInfo.notarized); return blockArr; @@ -218,18 +224,18 @@ class BlockPool { getNotarizedBlockByHash(hash) { const LOG_HEADER = 'getNotarizedBlockByHash'; - const blockInfo = this.hashToBlockInfo[hash]; + const blockInfo = this.hashToBlockInfo.get(hash); logger.debug(`[${LOG_HEADER}] blockInfo: ${JSON.stringify(blockInfo, null, 2)}`); return blockInfo && blockInfo.block && blockInfo.notarized ? blockInfo.block : null; } hasSeenBlock(blockHash) { - if (this.hashToBlockInfo[blockHash]) { - const blockInfo = this.hashToBlockInfo[blockHash]; - return blockInfo && blockInfo.block; - } else if (this.hashToInvalidBlockInfo[blockHash]) { - const blockInfo = this.hashToInvalidBlockInfo[blockHash]; - return blockInfo && blockInfo.block; + if (this.hashToBlockInfo.has(blockHash)) { + const blockInfo = this.hashToBlockInfo.get(blockHash); + return !!blockInfo.block; + } else if (this.hashToInvalidBlockInfo.has(blockHash)) { + const blockInfo = this.hashToInvalidBlockInfo.get(blockHash); + return !!blockInfo.block; } return false; } @@ -237,15 +243,15 @@ class BlockPool { checkEpochToBlockMap(block) { const LOG_HEADER = 'checkEpochToBlockMap'; // Check that there's no other block proposed at the same epoch - if (this.epochToBlock[block.epoch] && this.epochToBlock[block.epoch] !== block.hash) { - const conflict = this.hashToBlockInfo[this.epochToBlock[block.epoch]]; + if (this.epochToBlock.has(block.epoch) && this.epochToBlock.get(block.epoch) !== block.hash) { + const conflict = this.hashToBlockInfo.get(this.epochToBlock.get(block.epoch)); if (conflict && conflict.notarized) { logger.error(`[${LOG_HEADER}] Multiple blocks proposed for epoch ` + - `${block.epoch} (${block.hash}, ${this.epochToBlock[block.epoch]})`); + `${block.epoch} (${block.hash}, ${this.epochToBlock.get(block.epoch)})`); return false; } logger.info(`[${LOG_HEADER}] Multiple blocks proposed for epoch ` + - `${block.epoch} (${block.hash}, ${this.epochToBlock[block.epoch]}) BUT is not notarized`); + `${block.epoch} (${block.hash}, ${this.epochToBlock.get(block.epoch)}) BUT is not notarized`); // FIXME: remove info about the block that's currently this.epochToBlock[block.epoch] ? } return true; @@ -254,19 +260,19 @@ class BlockPool { addToHashBlockInfoMap(block, proposalTx) { const LOG_HEADER = 'addToHashBlockInfoMap'; const blockHash = block.hash; - if (!this.hashToBlockInfo[blockHash]) { - this.hashToBlockInfo[blockHash] = {}; + if (!this.hashToBlockInfo.has(blockHash)) { + this.hashToBlockInfo.set(blockHash, {}); } - const blockInfo = this.hashToBlockInfo[blockHash]; + const blockInfo = this.hashToBlockInfo.get(blockHash); if (CommonUtil.isEmpty(blockInfo.block)) { - this.hashToBlockInfo[blockHash].block = block; - this.hashToBlockInfo[blockHash].proposal = proposalTx; + blockInfo.block = block; + blockInfo.proposal = proposalTx; // We might have received some votes before the block itself if (!blockInfo.tallied && blockInfo.votes) { - this.hashToBlockInfo[blockHash].tallied = 0; + blockInfo.tallied = 0; blockInfo.votes.forEach((vote) => { if (block.validators[vote.address]) { - this.hashToBlockInfo[blockHash].tallied += _get(vote, 'tx_body.operation.value.stake'); + blockInfo.tallied += _get(vote, 'tx_body.operation.value.stake'); } }); this.tryUpdateNotarized(blockHash); @@ -282,13 +288,13 @@ class BlockPool { addToInvalidBlockInfoMap(block, proposalTx) { const LOG_HEADER = 'addToInvalidBlockInfoMap'; const blockHash = block.hash; - if (!this.hashToInvalidBlockInfo[blockHash]) { - this.hashToInvalidBlockInfo[blockHash] = {}; + if (!this.hashToInvalidBlockInfo.has(blockHash)) { + this.hashToInvalidBlockInfo.set(blockHash, {}); } - const blockInfo = this.hashToInvalidBlockInfo[blockHash]; + const blockInfo = this.hashToInvalidBlockInfo.get(blockHash); if (CommonUtil.isEmpty(blockInfo.block)) { - this.hashToInvalidBlockInfo[blockHash].block = block; - this.hashToInvalidBlockInfo[blockHash].proposal = proposalTx; + blockInfo.block = block; + blockInfo.proposal = proposalTx; logger.debug( `[${LOG_HEADER}] Invalid block added to the block pool: ${block.number} / ${block.epoch}`); } else { @@ -298,18 +304,18 @@ class BlockPool { } addToNumberToBlockSet(block) { - if (!this.numberToBlockSet[block.number]) { - this.numberToBlockSet[block.number] = new Set(); + if (!this.numberToBlockSet.has(block.number)) { + this.numberToBlockSet.set(block.number, new Set()); } - this.numberToBlockSet[block.number].add(block.hash); + this.numberToBlockSet.get(block.number).add(block.hash); } addToNextBlockSet(block) { const lastHash = block.last_hash; - if (!this.hashToNextBlockSet[lastHash]) { - this.hashToNextBlockSet[lastHash] = new Set(); + if (!this.hashToNextBlockSet.has(lastHash)) { + this.hashToNextBlockSet.set(lastHash, new Set()); } - this.hashToNextBlockSet[lastHash].add(block.hash); + this.hashToNextBlockSet.get(lastHash).add(block.hash); } addToHashToDbMap(blockHash, db) { @@ -327,12 +333,12 @@ class BlockPool { } this.addToHashBlockInfoMap(block, proposalTx); this.addToNumberToBlockSet(block); - this.epochToBlock[block.epoch] = blockHash; + this.epochToBlock.set(block.epoch, blockHash); this.addToNextBlockSet(block); this.tryUpdateNotarized(blockHash); // FIXME: update all descendants, not just the immediate ones - if (this.hashToNextBlockSet[blockHash]) { - for (const val of this.hashToNextBlockSet[blockHash]) { + if (this.hashToNextBlockSet.has(blockHash)) { + for (const val of this.hashToNextBlockSet.get(blockHash)) { this.tryUpdateNotarized(val); } } @@ -346,15 +352,15 @@ class BlockPool { hasVote(voteTx, blockHash, isAgainstVote) { if (isAgainstVote) { - if (!this.hashToInvalidBlockInfo[blockHash] || !this.hashToInvalidBlockInfo[blockHash].votes) { + if (!this.hashToInvalidBlockInfo.has(blockHash) || !this.hashToInvalidBlockInfo.get(blockHash).votes) { return false; } - return !!this.hashToInvalidBlockInfo[blockHash].votes.find((v) => v.hash === voteTx.hash); + return !!this.hashToInvalidBlockInfo.get(blockHash).votes.find((v) => v.hash === voteTx.hash); } else { - if (!this.hashToBlockInfo[blockHash] || !this.hashToBlockInfo[blockHash].votes) { + if (!this.hashToBlockInfo.has(blockHash) || !this.hashToBlockInfo.get(blockHash).votes) { return false; } - return !!this.hashToBlockInfo[blockHash].votes.find((v) => v.hash === voteTx.hash); + return !!this.hashToBlockInfo.get(blockHash).votes.find((v) => v.hash === voteTx.hash); } } @@ -377,62 +383,64 @@ class BlockPool { addVoteFor(voteTx, blockHash, stake) { const LOG_HEADER = 'addVoteFor'; - if (!this.hashToBlockInfo[blockHash]) { - this.hashToBlockInfo[blockHash] = {}; + if (!this.hashToBlockInfo.has(blockHash)) { + this.hashToBlockInfo.set(blockHash, {}); } - if (!this.hashToBlockInfo[blockHash].votes) { - this.hashToBlockInfo[blockHash].votes = []; + const blockInfo = this.hashToBlockInfo.get(blockHash); + if (!blockInfo.votes) { + blockInfo.votes = []; } if (this.hasVote(voteTx, blockHash, false)) { logger.debug(`[${LOG_HEADER}] Already have seen this vote`); return; } - this.hashToBlockInfo[blockHash].votes.push(voteTx); - if (this.hashToBlockInfo[blockHash].tallied === undefined) { - this.hashToBlockInfo[blockHash].tallied = 0; + blockInfo.votes.push(voteTx); + if (blockInfo.tallied === undefined) { + blockInfo.tallied = 0; } // Only counts if the voter was actually included as a validator in the block. // To know this, we need the block itself. - const block = this.hashToBlockInfo[blockHash].block; + const block = blockInfo.block; const voter = voteTx.address; logger.debug(`[${LOG_HEADER}] Voted for block: ${blockHash}`); if (stake > 0 && block && _get(block, `validators.${voter}.stake`) === stake) { - this.hashToBlockInfo[blockHash].tallied += stake; + blockInfo.tallied += stake; this.tryUpdateNotarized(blockHash); } } addVoteAgainst(voteTx, blockHash) { const LOG_HEADER = 'addVoteAgainst'; - if (!this.hashToInvalidBlockInfo[blockHash]) { - this.hashToInvalidBlockInfo[blockHash] = {}; + if (!this.hashToInvalidBlockInfo.has(blockHash)) { + this.hashToInvalidBlockInfo.set(blockHash, {}); } - if (!this.hashToInvalidBlockInfo[blockHash].votes) { - this.hashToInvalidBlockInfo[blockHash].votes = []; + const invalidBlockInfo = this.hashToInvalidBlockInfo.get(blockHash); + if (!invalidBlockInfo.votes) { + invalidBlockInfo.votes = []; } if (this.hasVote(voteTx, blockHash, true)) { logger.debug(`[${LOG_HEADER}] Already have seen this vote`); return; } - this.hashToInvalidBlockInfo[blockHash].votes.push(voteTx); + invalidBlockInfo.votes.push(voteTx); logger.debug(`[${LOG_HEADER}] Voted against block: ${blockHash}`); } addProposal(proposalTx, blockHash) { const LOG_HEADER = 'addProposal'; - if (!this.hashToBlockInfo[blockHash]) { - this.hashToBlockInfo[blockHash] = {}; - } else if (this.hashToBlockInfo[blockHash].proposal) { + if (!this.hashToBlockInfo.has(blockHash)) { + this.hashToBlockInfo.set(blockHash, {}); + } else if (this.hashToBlockInfo.get(blockHash).proposal) { logger.debug(`[${LOG_HEADER}] Already have seen this proposal`); return; } - this.hashToBlockInfo[blockHash].proposal = proposalTx; + this.hashToBlockInfo.get(blockHash).proposal = proposalTx; logger.debug(`[${LOG_HEADER}] Proposal tx for block added: ${blockHash}`); } tryUpdateNotarized(blockHash) { const LOG_HEADER = 'tryUpdateNotarized'; - const currentBlockInfo = this.hashToBlockInfo[blockHash]; + const currentBlockInfo = this.hashToBlockInfo.get(blockHash); if (!currentBlockInfo || !currentBlockInfo.block) { logger.info(`[${LOG_HEADER}] Current block is unavailable`); return; @@ -441,8 +449,8 @@ class BlockPool { return; } if (currentBlockInfo.block.number === 0) { - this.hashToBlockInfo[currentBlockInfo.block.hash].notarized = true; - this.updateLongestNotarizedChains(this.hashToBlockInfo[currentBlockInfo.block.hash]); + currentBlockInfo.notarized = true; + this.updateLongestNotarizedChains(); return; } const lastBlockNumber = currentBlockInfo.block.number - 1; @@ -451,10 +459,9 @@ class BlockPool { let prevBlock; if (lastFinalizedBlock && lastFinalizedBlock.number === lastBlockNumber) { prevBlock = lastFinalizedBlock; - } else if (_get(this.hashToBlockInfo[lastHash], 'block')) { - prevBlock = _get(this.hashToBlockInfo[lastHash], 'block'); } else { - prevBlock = this.node.bc.getBlockByHash(lastHash); + prevBlock = _get(this.hashToBlockInfo.get(lastHash), 'block') || + this.node.bc.getBlockByHash(lastHash); } if (!prevBlock) { logger.info(`[${LOG_HEADER}] Prev block is unavailable`); @@ -467,29 +474,31 @@ class BlockPool { currentBlockInfo.tallied >= totalAtStake * ConsensusConsts.MAJORITY) { logger.info(`[${LOG_HEADER}] Notarized block: ${currentBlockInfo.block.hash} ` + `(${currentBlockInfo.block.number} / ${currentBlockInfo.block.epoch})`); - this.hashToBlockInfo[blockHash].notarized = true; - this.updateLongestNotarizedChains(this.hashToBlockInfo[blockHash]); + currentBlockInfo.notarized = true; + this.updateLongestNotarizedChains(); } } cleanUpForBlockHash(blockHash) { - const block = _get(this.hashToBlockInfo[blockHash], 'block', null); - const blockProposal = _get(this.hashToBlockInfo[blockHash], 'proposal', null); - const blockConsensusTxs = _get(this.hashToBlockInfo[blockHash], 'votes', []); + const blockInfo = this.hashToBlockInfo.get(blockHash); + const block = _get(blockInfo, 'block', null); + const blockProposal = _get(blockInfo, 'proposal', null); + const blockConsensusTxs = _get(blockInfo, 'votes', []); if (blockProposal) { blockConsensusTxs.push(blockProposal); } - const invalidBlock = _get(this.hashToInvalidBlockInfo[blockHash], 'block', null); - const invalidBlockProposal = _get(this.hashToInvalidBlockInfo[blockHash], 'proposal', null); - const invalidBlockConsensusTxs = _get(this.hashToInvalidBlockInfo[blockHash], 'votes', []); + const invalidBlockInfo = this.hashToInvalidBlockInfo.get(blockHash); + const invalidBlock = _get(invalidBlockInfo, 'block', null); + const invalidBlockProposal = _get(invalidBlockInfo, 'proposal', null); + const invalidBlockConsensusTxs = _get(invalidBlockInfo, 'votes', []); if (invalidBlockProposal) { invalidBlockConsensusTxs.push(invalidBlockProposal); } this.node.tp.cleanUpConsensusTxs(block, blockConsensusTxs); this.node.tp.cleanUpConsensusTxs(invalidBlock, invalidBlockConsensusTxs); - delete this.hashToBlockInfo[blockHash]; - delete this.hashToInvalidBlockInfo[blockHash]; - delete this.hashToNextBlockSet[blockHash]; + this.hashToBlockInfo.delete(blockHash); + this.hashToInvalidBlockInfo.delete(blockHash); + this.hashToNextBlockSet.delete(blockHash); const db = this.hashToDb.get(blockHash); if (db) { db.destroyDb(); @@ -501,31 +510,29 @@ class BlockPool { cleanUpAfterFinalization(lastBlock, recordedInvalidBlocks) { const targetNumber = lastBlock.number; const maxInvalidBlocksOnMem = this.node.getBlockchainParam('consensus/max_invalid_blocks_on_mem'); - for (const blockNumber of Object.keys(this.numberToBlockSet)) { + for (const [blockNumber, blockHashSet] of this.numberToBlockSet.entries()) { const number = Number(blockNumber); if (number < targetNumber) { - const blockHashList = this.numberToBlockSet[blockNumber]; - for (const blockHash of blockHashList) { - if (this.hashToInvalidBlockInfo[blockHash]) { + for (const blockHash of blockHashSet) { + if (this.hashToInvalidBlockInfo.has(blockHash)) { if (recordedInvalidBlocks.has(blockHash) || number < targetNumber - maxInvalidBlocksOnMem) { this.cleanUpForBlockHash(blockHash); - this.numberToBlockSet[blockNumber].delete(blockHash); + blockHashSet.delete(blockHash); } } else { this.cleanUpForBlockHash(blockHash); - this.numberToBlockSet[blockNumber].delete(blockHash); + blockHashSet.delete(blockHash); } } - if (!this.numberToBlockSet[blockNumber].size) { - delete this.numberToBlockSet[blockNumber]; + if (blockHashSet.size === 0) { + this.numberToBlockSet.delete(blockNumber); } } } - for (const epoch of Object.keys(this.epochToBlock)) { + for (const [epoch, blockHash] of this.epochToBlock.entries()) { if (Number(epoch) < lastBlock.epoch) { - const blockHash = this.epochToBlock[epoch]; this.cleanUpForBlockHash(blockHash); - delete this.epochToBlock[epoch]; + this.epochToBlock.delete(epoch); } } this.updateLongestNotarizedChains(); @@ -542,7 +549,7 @@ class BlockPool { getValidLastVotes(lastBlock, blockNumber, blockTime, tempDb) { const LOG_HEADER = 'getValidLastVotes'; const chainId = this.node.getBlockchainParam('genesis/chain_id'); - const lastBlockInfo = this.hashToBlockInfo[lastBlock.hash]; + const lastBlockInfo = this.hashToBlockInfo.get(lastBlock.hash); logger.debug(`[${LOG_HEADER}] lastBlockInfo: ${JSON.stringify(lastBlockInfo, null, 2)}`); // FIXME(minsulee2 or liayoo): When I am behind and a newly coming node is ahead of me, // then I cannot get lastBlockInfo from the block-pool. So that, it is not able to create @@ -593,14 +600,14 @@ class BlockPool { const majority = totalAtStake * ConsensusConsts.MAJORITY; const evidence = {}; const offenses = {}; - for (const [blockHash, blockInfo] of Object.entries(this.hashToInvalidBlockInfo)) { + for (const [blockHash, blockInfo] of this.hashToInvalidBlockInfo.entries()) { if (recordedInvalidBlockHashSet.has(blockHash)) { continue; } if (!blockInfo.votes || !blockInfo.votes.length) { continue; } - const validBlockCandidate = this.hashToBlockInfo[blockHash]; + const validBlockCandidate = this.hashToBlockInfo.get(blockHash); const block = blockInfo.block || _get(validBlockCandidate, 'block'); const proposal = blockInfo.proposal || _get(validBlockCandidate, 'proposal'); if (!block || !proposal) { diff --git a/client/index.js b/client/index.js index 74e89c897..6bf06b8bc 100755 --- a/client/index.js +++ b/client/index.js @@ -418,7 +418,7 @@ app.get('/last_block', (req, res, next) => { app.get('/tx_pool', (req, res, next) => { const beginTime = Date.now(); - const result = node.tp.transactions; + const result = Object.fromEntries(node.tp.transactions); const latency = Date.now() - beginTime; trafficStatsManager.addEvent(TrafficEventTypes.CLIENT_API_GET, latency); res.status(200) @@ -429,7 +429,7 @@ app.get('/tx_pool', (req, res, next) => { app.get('/tx_tracker', (req, res, next) => { const beginTime = Date.now(); - const result = node.tp.transactionTracker; + const result = Object.fromEntries(node.tp.transactionTracker); const latency = Date.now() - beginTime; trafficStatsManager.addEvent(TrafficEventTypes.CLIENT_API_GET, latency); res.status(200) diff --git a/consensus/index.js b/consensus/index.js index 7865611eb..aba8378cd 100644 --- a/consensus/index.js +++ b/consensus/index.js @@ -284,7 +284,7 @@ class Consensus { logger.error(`[${LOG_HEADER}] Proposal is missing required fields: ${msg.value}`); return; } - if (this.node.tp.transactionTracker[proposalTx.hash]) { + if (this.node.tp.transactionTracker.has(proposalTx.hash)) { logger.debug(`[${LOG_HEADER}] Already have the proposal in my tx tracker`); return; } @@ -322,7 +322,7 @@ class Consensus { this.server.client.broadcastConsensusMessage(msg, tags); this.tryVoteForValidBlock(proposalBlock); } else if (msg.type === ConsensusMessageTypes.VOTE) { - if (this.node.tp.transactionTracker[msg.value.hash]) { + if (this.node.tp.transactionTracker.has(msg.value.hash)) { logger.debug(`[${LOG_HEADER}] Already have the vote in my tx tracker`); return; } @@ -508,7 +508,7 @@ class Consensus { static getPrevBlockInfo(number, lastHash, lastFinalizedBlock, bp) { if (number === 0) return { block: null }; const prevBlockInfo = lastFinalizedBlock && number === lastFinalizedBlock.number + 1 ? - { block: lastFinalizedBlock } : bp.hashToBlockInfo[lastHash]; + { block: lastFinalizedBlock } : bp.hashToBlockInfo.get(lastHash); if (!prevBlockInfo || !prevBlockInfo.block) { throw new ConsensusError({ code: ConsensusErrorCode.MISSING_PREV_BLOCK, @@ -630,17 +630,18 @@ class Consensus { for (const vote of lastVotes) { blockPool.addSeenVote(vote); } - if (!blockPool.hashToBlockInfo[lastHash] || !blockPool.hashToBlockInfo[lastHash].notarized) { + const lastBlockInfo = blockPool.hashToBlockInfo.get(lastHash); + if (!lastBlockInfo || !lastBlockInfo.notarized) { throw new ConsensusError({ code: ConsensusErrorCode.INVALID_LAST_VOTES_STAKES, message: `Block's last_votes don't correctly notarize its previous block of number ` + `${number - 1} with hash ${lastHash}:\n` + - `${JSON.stringify(blockPool.hashToBlockInfo[lastHash], null, 2)}`, + `${JSON.stringify(lastBlockInfo, null, 2)}`, level: 'error' }); } const prevBlockProposal = ConsensusUtil.filterProposalFromVotes(lastVotes); - if (number > 1 && (!prevBlockProposal || !blockPool.hashToBlockInfo[lastHash].proposal)) { // No proposalTx for the genesis block. + if (number > 1 && (!prevBlockProposal || !lastBlockInfo.proposal)) { // No proposalTx for the genesis block. throw new ConsensusError({ code: ConsensusErrorCode.MISSING_PROPOSAL_IN_LAST_VOTES, message: `Proposal block is missing its prev block's proposal in last_votes`, @@ -887,8 +888,8 @@ class Consensus { const blockHash = ConsensusUtil.getBlockHashFromConsensusTx(voteTx); const isAgainst = ConsensusUtil.isAgainstVoteTx(voteTx); const voteTimestamp = ConsensusUtil.getTimestampFromVoteTx(voteTx); - const blockInfo = this.node.bp.hashToBlockInfo[blockHash] || - this.node.bp.hashToInvalidBlockInfo[blockHash]; + const blockInfo = this.node.bp.hashToBlockInfo.get(blockHash) || + this.node.bp.hashToInvalidBlockInfo.get(blockHash); let block; if (blockInfo && blockInfo.block) { block = blockInfo.block; @@ -953,7 +954,7 @@ class Consensus { const epoch = this.epoch; if (this.votedForEpoch(epoch)) { logger.debug( - `[${LOG_HEADER}] Already voted for ${this.node.bp.epochToBlock[epoch]} ` + + `[${LOG_HEADER}] Already voted for ${this.node.bp.epochToBlock.get(epoch)} ` + `at epoch ${epoch} but trying to propose at the same epoch`); return; } @@ -1095,7 +1096,7 @@ class Consensus { logger.debug(`[${LOG_HEADER}] longestNotarizedChainTips: ` + `${JSON.stringify(this.node.bp.longestNotarizedChainTips, null, 2)}`); this.node.bp.longestNotarizedChainTips.forEach((chainTip) => { - const block = _.get(this.node.bp.hashToBlockInfo[chainTip], 'block'); + const block = _.get(this.node.bp.hashToBlockInfo.get(chainTip), 'block'); if (!block) return; if (block.epoch > candidate.epoch) candidate = block; }); @@ -1124,7 +1125,7 @@ class Consensus { !this.node.bp.hashToDb.has(blockHash)) { chain.unshift(currBlock); // previous block of currBlock - currBlock = _.get(this.node.bp.hashToBlockInfo[currBlock.last_hash], 'block'); + currBlock = _.get(this.node.bp.hashToBlockInfo.get(currBlock.last_hash), 'block'); if (!currBlock) { currBlock = this.node.bc.getBlockByHash(blockHash); } @@ -1153,7 +1154,7 @@ class Consensus { getValidatorsVotedFor(blockHash) { const LOG_HEADER = 'getValidatorsVotedFor'; - const blockInfo = this.node.bp.hashToBlockInfo[blockHash]; + const blockInfo = this.node.bp.hashToBlockInfo.get(blockHash); if (!blockInfo || !blockInfo.votes || !blockInfo.votes.length) { logger.error(`[${LOG_HEADER}] No validators voted`); throw Error('No validators voted'); @@ -1258,16 +1259,17 @@ class Consensus { } votedForEpoch(epoch) { - const blockHash = this.node.bp.epochToBlock[epoch]; + const blockHash = this.node.bp.epochToBlock.get(epoch); if (!blockHash) return false; - const blockInfo = this.node.bp.hashToBlockInfo[blockHash]; + const blockInfo = this.node.bp.hashToBlockInfo.get(blockHash); if (!blockInfo || !blockInfo.votes) return false; const myAddr = this.node.account.address; return blockInfo.votes.find((vote) => vote.address === myAddr) !== undefined; } votedForBlock(blockHash) { - const blockInfo = this.node.bp.hashToBlockInfo[blockHash] || this.node.bp.hashToInvalidBlockInfo[blockHash]; + const blockInfo = this.node.bp.hashToBlockInfo.get(blockHash) || + this.node.bp.hashToInvalidBlockInfo.get(blockHash); if (!blockInfo || !blockInfo.votes) return false; const myAddr = this.node.account.address; return blockInfo.votes.find((vote) => vote.address === myAddr) !== undefined; @@ -1325,15 +1327,15 @@ class Consensus { Object.assign({}, { epoch: this.epoch, proposer: this.proposer }, { state: this.state }); if (this.node.bp) { result.block_pool = { - hashToBlockInfo: this.node.bp.hashToBlockInfo, - hashToInvalidBlockInfo: this.node.bp.hashToInvalidBlockInfo, + hashToBlockInfo: Object.fromEntries(this.node.bp.hashToBlockInfo), + hashToInvalidBlockInfo: Object.fromEntries(this.node.bp.hashToInvalidBlockInfo), hashToDb: Array.from(this.node.bp.hashToDb.keys()), - hashToNextBlockSet: Object.keys(this.node.bp.hashToNextBlockSet) + hashToNextBlockSet: Array.from(this.node.bp.hashToNextBlockSet.keys()) .reduce((acc, curr) => { - return Object.assign(acc, {[curr]: [...this.node.bp.hashToNextBlockSet[curr]]}) + return Object.assign(acc, {[curr]: [...this.node.bp.hashToNextBlockSet.get(curr)]}) }, {}), - epochToBlock: Object.keys(this.node.bp.epochToBlock), - numberToBlockSet: Object.keys(this.node.bp.numberToBlockSet), + epochToBlock: Array.from(this.node.bp.epochToBlock.keys()), + numberToBlockSet: Array.from(this.node.bp.numberToBlockSet.keys()), longestNotarizedChainTips: this.node.bp.longestNotarizedChainTips } } diff --git a/node/index.js b/node/index.js index ab4df0a9c..3f80d1dd0 100644 --- a/node/index.js +++ b/node/index.js @@ -372,7 +372,7 @@ class BlockchainNode { getTransactionByHash(hash) { const LOG_HEADER = 'getTransactionByHash'; - const transactionInfo = this.tp.transactionTracker[hash]; + const transactionInfo = this.tp.transactionTracker.get(hash); if (!transactionInfo) { return null; } @@ -394,7 +394,7 @@ class BlockchainNode { transactionInfo.state === TransactionStates.PENDING) { const address = transactionInfo.address; transactionInfo.transaction = - _.find(this.tp.transactions[address], (tx) => tx.hash === hash) || null; + _.find(this.tp.transactions.get(address), (tx) => tx.hash === hash) || null; } return transactionInfo; } diff --git a/p2p/server.js b/p2p/server.js index 45ec1e06f..79063d4f4 100644 --- a/p2p/server.js +++ b/p2p/server.js @@ -274,7 +274,7 @@ class P2pServer { getTxStatus() { return { txPoolSize: this.node.tp.getPoolSize(), - txTrackerSize: Object.keys(this.node.tp.transactionTracker).length, + txTrackerSize: this.node.tp.transactionTracker.size, }; } @@ -578,7 +578,7 @@ class P2pServer { trafficStatsManager.addEvent(TrafficEventTypes.P2P_TAG_TX_LENGTH, txTags.length); trafficStatsManager.addEvent( TrafficEventTypes.P2P_TAG_TX_MAX_OCCUR, CommonUtil.countMaxOccurrences(txTags)); - if (this.node.tp.transactionTracker[tx.hash]) { + if (this.node.tp.transactionTracker.has(tx.hash)) { logger.debug(`[${LOG_HEADER}] Already have the transaction in my tx tracker`); const latency = Date.now() - beginTime; trafficStatsManager.addEvent(TrafficEventTypes.P2P_MESSAGE_SERVER, latency); diff --git a/test/integration/consensus.test.js b/test/integration/consensus.test.js index 4fe0b7516..64064b4e9 100644 --- a/test/integration/consensus.test.js +++ b/test/integration/consensus.test.js @@ -186,6 +186,7 @@ describe('Consensus', () => { ref: `/blockchain_params/consensus/max_num_validators`, value: MAX_NUM_VALIDATORS }, + gas_price: 0, timestamp: Date.now(), nonce: -1 }; diff --git a/test/integration/function.test.js b/test/integration/function.test.js index f902689cc..42b5c7059 100644 --- a/test/integration/function.test.js +++ b/test/integration/function.test.js @@ -926,6 +926,7 @@ describe('Native Function', () => { ref: `/developers/rest_functions/user_whitelist/${serviceUser}`, value: true }, + gas_price: 0, timestamp: 1628255843548, nonce: -1 }; @@ -4178,6 +4179,7 @@ describe('Native Function', () => { ref: `${checkoutHistoryBasePath}/${serviceUser}/1628255843548`, value: null, }, + gas_price: 0, timestamp: 1628255843548, nonce: -1 }; @@ -4220,6 +4222,7 @@ describe('Native Function', () => { } } }, + gas_price: 0, timestamp: 1628255843548, nonce: -1 }; @@ -4368,6 +4371,7 @@ describe('Native Function', () => { } } }, + gas_price: 0, timestamp: 1628255843550, nonce: -1 }; @@ -4971,6 +4975,7 @@ describe('Native Function', () => { ref: `${checkinHistoryBasePath}/${serviceUser}/1628255843548`, value: null, }, + gas_price: 0, timestamp, nonce: -1 }; @@ -5016,6 +5021,7 @@ describe('Native Function', () => { } } }, + gas_price: 0, timestamp, nonce: -1 }; @@ -5199,6 +5205,7 @@ describe('Native Function', () => { } } }, + gas_price: 0, timestamp: 1628255843548, nonce: -1 }; diff --git a/test/integration/node.test.js b/test/integration/node.test.js index 36f40fbe2..4d3c4fdf8 100644 --- a/test/integration/node.test.js +++ b/test/integration/node.test.js @@ -3011,6 +3011,7 @@ describe('Blockchain Node', () => { value: 'some other value', ref: `/apps/test/test_value/some/path` }, + gas_price: 0, timestamp: Date.now(), nonce: -1 }; @@ -3067,6 +3068,7 @@ describe('Blockchain Node', () => { value: 'some other value 2', ref: `/apps/test/test_value/some/path` }, + gas_price: 0, timestamp: Date.now(), nonce, // numbered nonce }; @@ -3119,6 +3121,7 @@ describe('Blockchain Node', () => { value: longText, ref: `/apps/test/test_long_text` }, + gas_price: 0, timestamp: Date.now(), nonce: -1 }; @@ -3147,6 +3150,7 @@ describe('Blockchain Node', () => { value: 'some other value', ref: `/apps/test/test_value/some/path` }, + gas_price: 0, timestamp: Date.now(), nonce: -1 }; @@ -3175,6 +3179,7 @@ describe('Blockchain Node', () => { value: 'some other value', ref: `/apps/test/test_value/some/path` }, + gas_price: 0, timestamp: Date.now(), // missing nonce }; @@ -3203,6 +3208,7 @@ describe('Blockchain Node', () => { value: 'some other value 3', ref: `/apps/test/test_value/some/path` }, + gas_price: 0, timestamp: Date.now(), nonce: -1 }; @@ -3241,6 +3247,7 @@ describe('Blockchain Node', () => { ref: "/apps/test/test_value/some400/path", value: "some other300 value", }, + gas_price: 0, timestamp: Date.now(), nonce: -1 }; @@ -3253,6 +3260,7 @@ describe('Blockchain Node', () => { ref: "/apps/test/test_value/some400/path2", value: 10 }, + gas_price: 0, timestamp: Date.now(), nonce: -1 }; @@ -3269,6 +3277,7 @@ describe('Blockchain Node', () => { ref: "/apps/test/test_value/some300/path", value: "some other300 value", }, + gas_price: 0, timestamp: Date.now(), nonce: -1 }, @@ -3278,6 +3287,7 @@ describe('Blockchain Node', () => { ref: "/apps/test/test_value/some300/path2", value: 10 }, + gas_price: 0, timestamp: Date.now(), nonce: -1 }, @@ -3287,6 +3297,7 @@ describe('Blockchain Node', () => { ref: "/apps/test/test_value/some300/path3", value: 10 }, + gas_price: 0, timestamp: Date.now(), nonce: -1 }, @@ -3304,6 +3315,7 @@ describe('Blockchain Node', () => { } } }, + gas_price: 0, timestamp: Date.now(), nonce: -1 }, @@ -3317,6 +3329,7 @@ describe('Blockchain Node', () => { } } }, + gas_price: 0, timestamp: Date.now(), nonce: -1 }, @@ -3337,6 +3350,7 @@ describe('Blockchain Node', () => { } } }, + gas_price: 0, timestamp: Date.now(), nonce: -1 }, @@ -3399,6 +3413,7 @@ describe('Blockchain Node', () => { } ] }, + gas_price: 0, timestamp: Date.now(), nonce: -1 } @@ -3432,6 +3447,7 @@ describe('Blockchain Node', () => { value: 'some other value', ref: `/apps/test/test_value/some/path` }, + gas_price: 0, timestamp: Date.now(), nonce: -1 }; @@ -3463,6 +3479,7 @@ describe('Blockchain Node', () => { value: 'some other value', ref: `/apps/test/test_value/some/path` }, + gas_price: 0, nonce: -1 }; const txList = []; @@ -3498,6 +3515,7 @@ describe('Blockchain Node', () => { value: 'some other value', ref: `/apps/test/test_value/some/path` }, + gas_price: 0, nonce: -1 }; const txList = []; @@ -3537,6 +3555,7 @@ describe('Blockchain Node', () => { value: 'some other value', ref: `/apps/test/test_value/some/path` }, + gas_price: 0, nonce: -1 }; @@ -3611,6 +3630,7 @@ describe('Blockchain Node', () => { ref: `/apps/test/test_long_text`, value: longText }, + gas_price: 0, timestamp: Date.now(), nonce: -1 }; @@ -3691,6 +3711,7 @@ describe('Blockchain Node', () => { value: 'some other value', ref: `/apps/test/test_value/some/path` }, + gas_price: 0, timestamp: Date.now(), // missing nonce }; @@ -3731,6 +3752,7 @@ describe('Blockchain Node', () => { value: 'some other value 3', ref: `/apps/test/test_value/some/path` }, + gas_price: 0, timestamp: Date.now(), nonce: -1 }; diff --git a/test/integration/sharding.test.js b/test/integration/sharding.test.js index 593e26e37..01f425baa 100644 --- a/test/integration/sharding.test.js +++ b/test/integration/sharding.test.js @@ -2031,6 +2031,7 @@ describe('Sharding', () => { value: 'some other value', ref: `/apps/test/test_value/some/path` }, + gas_price: 0, timestamp: Date.now(), nonce: -1 } @@ -2079,6 +2080,7 @@ describe('Sharding', () => { ref: `/apps/test/test_value/some/path`, is_global: false, }, + gas_price: 0, timestamp: Date.now(), nonce: -1 } @@ -2127,6 +2129,7 @@ describe('Sharding', () => { ref: `apps/afan/apps/test/test_value/some/path`, is_global: true, }, + gas_price: 0, timestamp: Date.now(), nonce: -1 } @@ -2176,6 +2179,7 @@ describe('Sharding', () => { value: 'some other value', ref: `/apps/test/test_value/some/path` }, + gas_price: 0, timestamp: Date.now(), nonce: -1 } @@ -2233,6 +2237,7 @@ describe('Sharding', () => { ref: `/apps/test/test_value/some/path`, is_global: false, }, + gas_price: 0, timestamp: Date.now(), nonce: -1 } @@ -2293,6 +2298,7 @@ describe('Sharding', () => { ref: `apps/afan/apps/test/test_value/some/path`, is_global: true, }, + gas_price: 0, timestamp: Date.now(), nonce: -1 } diff --git a/test/unit/block-pool.test.js b/test/unit/block-pool.test.js index 0121249a4..af9d6e42b 100644 --- a/test/unit/block-pool.test.js +++ b/test/unit/block-pool.test.js @@ -110,9 +110,9 @@ describe("BlockPool", () => { ); const blockPool = new BlockPool(node1); blockPool.addSeenBlock(block, proposalTx); - assert.deepEqual(blockPool.hashToBlockInfo[block.hash].block, block); - expect(blockPool.epochToBlock[block.epoch]).to.equal(block.hash); - expect(blockPool.hashToNextBlockSet[block.last_hash].has(block.hash)).to.equal(true); + assert.deepEqual(blockPool.hashToBlockInfo.get(block.hash).block, block); + expect(blockPool.epochToBlock.get(block.epoch)).to.equal(block.hash); + expect(blockPool.hashToNextBlockSet.get(block.last_hash).has(block.hash)).to.equal(true); }); it("Returns an empty array if there's no finalizable chain", () => { @@ -170,7 +170,7 @@ describe("BlockPool", () => { // block B (1,1) const blockB = createAndAddBlock(node1, blockPool, blockA, blockA.number + 1, blockA.epoch + 1); createAndAddVote(node1, blockPool, blockB); - expect(blockPool.hashToBlockInfo[blockB.hash].notarized).to.equal(true); + expect(blockPool.hashToBlockInfo.get(blockB.hash).notarized).to.equal(true); // block F (2,2) const blockF = createAndAddBlock(node1, blockPool, blockB, blockB.number + 1, blockB.epoch + 1); @@ -179,9 +179,9 @@ describe("BlockPool", () => { const blockC = createAndAddBlock(node1, blockPool, blockB, blockB.number + 1, blockB.epoch + 2); createAndAddVote(node1, blockPool, blockF); - expect(blockPool.hashToBlockInfo[blockF.hash].notarized).to.equal(true); + expect(blockPool.hashToBlockInfo.get(blockF.hash).notarized).to.equal(true); createAndAddVote(node1, blockPool, blockC); - expect(blockPool.hashToBlockInfo[blockC.hash].notarized).to.equal(true); + expect(blockPool.hashToBlockInfo.get(blockC.hash).notarized).to.equal(true); // block D (3,4) const blockD = createAndAddBlock(node1, blockPool, blockC, blockC.number + 1, blockC.epoch + 1); @@ -191,12 +191,12 @@ describe("BlockPool", () => { // block G (3,6) const blockG = createAndAddBlock(node1, blockPool, blockF, blockF.number + 1, blockF.epoch + 4); - expect(blockPool.hashToBlockInfo[blockG.hash].notarized).to.equal(undefined); + expect(blockPool.hashToBlockInfo.get(blockG.hash).notarized).to.equal(undefined); createAndAddVote(node1, blockPool, blockD); - expect(blockPool.hashToBlockInfo[blockD.hash].notarized).to.equal(true); + expect(blockPool.hashToBlockInfo.get(blockD.hash).notarized).to.equal(true); createAndAddVote(node1, blockPool, blockE); - expect(blockPool.hashToBlockInfo[blockE.hash].notarized).to.equal(true); + expect(blockPool.hashToBlockInfo.get(blockE.hash).notarized).to.equal(true); const finalizableChain = blockPool.getFinalizableChain(); assert.deepEqual(finalizableChain, [blockA, blockB, blockC, blockD, blockE]); diff --git a/test/unit/tx-pool.test.js b/test/unit/tx-pool.test.js index c80c4dada..98a8e2b26 100644 --- a/test/unit/tx-pool.test.js +++ b/test/unit/tx-pool.test.js @@ -56,7 +56,7 @@ describe('TransactionPool', () => { it('add a pending transaction', () => { node.tp.addTransaction(txToAdd); - const addedTx = node.tp.transactions[node.account.address].find((t) => t.hash === txToAdd.hash); + const addedTx = node.tp.transactions.get(node.account.address).find((t) => t.hash === txToAdd.hash); assert.deepEqual(eraseTxCreatedAt(addedTx), eraseTxCreatedAt(txToAdd)); const txInfo = node.getTransactionByHash(txToAdd.hash); expect(txInfo.state).to.equal(TransactionStates.PENDING); @@ -64,7 +64,7 @@ describe('TransactionPool', () => { it('add an executed transaction', () => { node.tp.addTransaction(txToAdd, true); - const addedTx = node.tp.transactions[node.account.address].find((t) => t.hash === txToAdd.hash); + const addedTx = node.tp.transactions.get(node.account.address).find((t) => t.hash === txToAdd.hash); assert.deepEqual(eraseTxCreatedAt(addedTx), eraseTxCreatedAt(txToAdd)); const txInfo = node.getTransactionByHash(txToAdd.hash); expect(txInfo.state).to.equal(TransactionStates.EXECUTED); @@ -91,8 +91,9 @@ describe('TransactionPool', () => { } // NOTE: Shuffle transactions and see if the transaction-pool can re-sort them according to // their proper ordering - node.tp.transactions[node.account.address] = - shuffleSeed.shuffle(node.tp.transactions[node.account.address]); + node.tp.transactions.set( + node.account.address, + shuffleSeed.shuffle(node.tp.transactions.get(node.account.address))); node2 = new BlockchainNode(); await setNodeForTesting(node2, 1); @@ -119,8 +120,9 @@ describe('TransactionPool', () => { } // NOTE: Shuffle transactions and see if the transaction-pool can re-sort them according to // their proper ordering - node.tp.transactions[nodes[j].account.address] = - shuffleSeed.shuffle(node.tp.transactions[nodes[j].account.address]); + node.tp.transactions.set( + nodes[j].account.address, + shuffleSeed.shuffle(node.tp.transactions.get(nodes[j].account.address))); } }); @@ -661,7 +663,7 @@ describe('TransactionPool', () => { node.tp.addTransaction(newTransactions[node.account.address][i]); } node.tp.cleanUpForNewBlock(block); - assert.deepEqual(newTransactions, node.tp.transactions); + assert.deepEqual(newTransactions, Object.fromEntries(node.tp.transactions)); }); it('cleanUpConsensusTxs()', async () => { @@ -719,7 +721,7 @@ describe('TransactionPool', () => { }] } for (const tx of [...lastVotes, evidenceTx, invalidProposal]) { - expect(node.tp.transactions[tx.address].includes(tx)).to.equal(true); + expect(node.tp.transactions.get(tx.address).includes(tx)).to.equal(true); } const receipts = txsToDummyReceipts(transactions); const block = Block.create( @@ -727,8 +729,8 @@ describe('TransactionPool', () => { node.account.address, {}, 0, 0); node.tp.cleanUpConsensusTxs(block); for (const tx of [...lastVotes, evidenceTx, invalidProposal]) { - if (node.tp.transactions[tx.address]) { - expect(node.tp.transactions[tx.address].includes(tx)).to.equal(false); + if (node.tp.transactions.has(tx.address)) { + expect(node.tp.transactions.get(tx.address).includes(tx)).to.equal(false); } } }); diff --git a/tx-pool/index.js b/tx-pool/index.js index 0745f1699..1c2cd71e3 100644 --- a/tx-pool/index.js +++ b/tx-pool/index.js @@ -15,8 +15,8 @@ const Transaction = require('./transaction'); class TransactionPool { constructor(node) { this.node = node; - this.transactions = {}; - this.transactionTracker = {}; + this.transactions = new Map(); + this.transactionTracker = new Map(); this.txCountTotal = 0; } @@ -27,21 +27,21 @@ class TransactionPool { logger.error(`Not executable transaction: ${JSON.stringify(tx)}`); return false; } - if (!(tx.address in this.transactions)) { - this.transactions[tx.address] = []; + if (!this.transactions.has(tx.address)) { + this.transactions.set(tx.address, []); } - this.transactions[tx.address].push(tx); - this.transactionTracker[tx.hash] = { + this.transactions.get(tx.address).push(tx); + this.transactionTracker.set(tx.hash, { state: isExecutedTx ? TransactionStates.EXECUTED : TransactionStates.PENDING, address: tx.address, - index: this.transactions[tx.address].length - 1, + index: this.transactions.get(tx.address).length - 1, timestamp: tx.tx_body.timestamp, is_executed: isExecutedTx, is_finalized: false, finalized_at: -1, tracked_at: tx.extra.created_at, executed_at: tx.extra.executed_at, - }; + }); this.txCountTotal++; logger.debug(`ADDING(${this.getPoolSize()}): ${JSON.stringify(tx)}`); return true; @@ -71,9 +71,9 @@ class TransactionPool { } isNotEligibleTransaction(tx) { - return (tx.address in this.transactions && - this.transactions[tx.address].find((trans) => trans.hash === tx.hash) !== undefined) || - tx.hash in this.transactionTracker; + return (this.transactions.has(tx.address) && + this.transactions.get(tx.address).find((trans) => trans.hash === tx.hash) !== undefined) || + this.transactionTracker.has(tx.hash); } static isCorrectlyNoncedOrTimestamped(txNonce, txTimestamp, accountNonce, accountTimestamp) { @@ -103,10 +103,10 @@ class TransactionPool { excludeTransactions = excludeTransactions.concat(block.transactions); }) } - for (const address in addrToTxList) { + for (const [address, txList] of addrToTxList.entries()) { // exclude transactions in excludeBlockList let filteredTransactions = _.differenceWith( - addrToTxList[address], + txList, excludeTransactions, (a, b) => { return a.hash === b.hash; @@ -114,11 +114,11 @@ class TransactionPool { // exclude consensus transactions filteredTransactions = TransactionPool.excludeConsensusTransactions(filteredTransactions); if (!filteredTransactions.length) { - delete addrToTxList[address]; + addrToTxList.delete(address); } else { - addrToTxList[address] = filteredTransactions; + addrToTxList.set(address, filteredTransactions); // sort transactions - addrToTxList[address].sort((a, b) => { + addrToTxList.get(address).sort((a, b) => { if (a.tx_body.nonce === b.tx_body.nonce) { if (a.tx_body.nonce >= 0) { // both with numbered nonces return 0; @@ -148,10 +148,10 @@ class TransactionPool { return null; } - const addrToTxList = JSON.parse(JSON.stringify(this.transactions)); + const addrToTxList = _.cloneDeep(this.transactions); TransactionPool.filterAndSortTransactions(addrToTxList, excludeBlockList); // Remove incorrectly nonced / timestamped transactions - for (const [addr, txList] of Object.entries(addrToTxList)) { + for (const [addr, txList] of addrToTxList.entries()) { const newTxList = []; for (let index = 0; index < txList.length; index++) { const tx = txList[index]; @@ -165,12 +165,12 @@ class TransactionPool { tempDb.updateAccountNonceAndTimestamp(addr, txNonce, txTimestamp); } } - addrToTxList[addr] = newTxList; + addrToTxList.set(addr, newTxList); } // Merge lists of transactions while ordering by gas price and timestamp. // Initial ordering by nonce is preserved. - const merged = TransactionPool.mergeMultipleSortedArrays(Object.values(addrToTxList)); + const merged = TransactionPool.mergeMultipleSortedArrays(Array.from(addrToTxList.values())); const checkedTxs = this.performBandwidthChecks(merged, tempDb); tempDb.destroyDb(); return checkedTxs; @@ -353,20 +353,18 @@ class TransactionPool { removeTimedOutTxsFromPool(blockTimestamp) { // Get timed-out transactions. const timedOutTxs = new Set(); - for (const address in this.transactions) { - this.transactions[address].forEach((tx) => { + for (const txList of this.transactions.values()) { + txList.forEach((tx) => { if (this.isTimedOutFromPool(tx.extra.created_at, blockTimestamp)) { timedOutTxs.add(tx.hash); } }); } // Remove transactions from the pool. - for (const address in this.transactions) { - const sizeBefore = this.transactions[address].length; - this.transactions[address] = this.transactions[address].filter((tx) => { - return !timedOutTxs.has(tx.hash); - }); - const sizeAfter = this.transactions[address].length; + for (const [address, txList] of this.transactions.entries()) { + const sizeBefore = txList.length; + this.transactions.set(address, txList.filter((tx) => !timedOutTxs.has(tx.hash))); + const sizeAfter = this.transactions.get(address).length; this.txCountTotal += sizeAfter - sizeBefore; } return timedOutTxs.size > 0; @@ -375,10 +373,9 @@ class TransactionPool { removeTimedOutTxsFromTracker(blockTimestamp) { // Remove transactions from transactionTracker. let removed = false; - for (const hash in this.transactionTracker) { - const txData = this.transactionTracker[hash]; + for (const [hash, txData] of this.transactionTracker.entries()) { if (this.isTimedOutFromTracker(txData.tracked_at, blockTimestamp)) { - delete this.transactionTracker[hash]; + this.transactionTracker.delete(hash); removed = true; } } @@ -386,27 +383,26 @@ class TransactionPool { } removeInvalidTxsFromPool(txs) { - const addrToTxSet = {}; + const addrToInvalidTxSet = new Map(); txs.forEach((tx) => { - const {address, hash} = tx; - if (!addrToTxSet[address]) { - addrToTxSet[address] = new Set(); + const { address, hash } = tx; + if (!addrToInvalidTxSet.has(address)) { + addrToInvalidTxSet.set(address, new Set()); } - addrToTxSet[address].add(hash); - const tracked = this.transactionTracker[hash]; + addrToInvalidTxSet.get(address).add(hash); + const tracked = this.transactionTracker.get(hash); if (tracked && tracked.state !== TransactionStates.FINALIZED) { - this.transactionTracker[hash].state = TransactionStates.FAILED; + tracked.state = TransactionStates.FAILED; } }); - for (const address in addrToTxSet) { - const sizeBefore = this.transactions[address].length; - if (this.transactions[address]) { - this.transactions[address] = this.transactions[address].filter((tx) => { - return !(addrToTxSet[address].has(tx.hash)); - }) + for (const [address, invalidTxSet] of addrToInvalidTxSet.entries()) { + if (this.transactions.has(address)) { + const txList = this.transactions.get(address); + const sizeBefore = txList.length; + this.transactions.set(address, txList.filter((tx) => !invalidTxSet.has(tx.hash))); + const sizeAfter = this.transactions.get(address).length; + this.txCountTotal += sizeAfter - sizeBefore; } - const sizeAfter = this.transactions[address].length; - this.txCountTotal += sizeAfter - sizeBefore; } } @@ -427,12 +423,12 @@ class TransactionPool { } updateTxPoolWithTxHashSet(txHashSet, addrToNonce, addrToTimestamp) { - for (const address in this.transactions) { + for (const [address, txList] of this.transactions.entries()) { // Remove transactions from the pool. const lastNonce = addrToNonce[address]; const lastTimestamp = addrToTimestamp[address]; - const sizeBefore = this.transactions[address].length; - this.transactions[address] = this.transactions[address].filter((tx) => { + const sizeBefore = txList.length; + this.transactions.set(address, txList.filter((tx) => { if (lastNonce !== undefined && tx.tx_body.nonce >= 0 && tx.tx_body.nonce <= lastNonce) { return false; } @@ -440,11 +436,11 @@ class TransactionPool { return false; } return !txHashSet.has(tx.hash); - }); - const sizeAfter = this.transactions[address].length; + })); + const sizeAfter = this.transactions.get(address).length; this.txCountTotal += sizeAfter - sizeBefore; - if (this.transactions[address].length === 0) { - delete this.transactions[address]; + if (sizeAfter === 0) { + this.transactions.delete(address); } } } @@ -470,7 +466,7 @@ class TransactionPool { for (const voteTx of block.last_votes) { const txTimestamp = voteTx.tx_body.timestamp; // voting txs with ordered nonces. - this.transactionTracker[voteTx.hash] = { + this.transactionTracker.set(voteTx.hash, { state: TransactionStates.FINALIZED, number: block.number, index: -1, @@ -480,7 +476,7 @@ class TransactionPool { is_finalized: true, finalized_at: finalizedAt, tracked_at: finalizedAt, - }; + }); inBlockTxs.add(voteTx.hash); } this.addEvidenceTxsToTxHashSet(inBlockTxs, block.evidence); @@ -489,7 +485,7 @@ class TransactionPool { const txNonce = tx.tx_body.nonce; const txTimestamp = tx.tx_body.timestamp; // Update transaction tracker. - this.transactionTracker[tx.hash] = { + this.transactionTracker.set(tx.hash, { state: TransactionStates.FINALIZED, number: block.number, index: i, @@ -499,7 +495,7 @@ class TransactionPool { is_finalized: true, finalized_at: finalizedAt, tracked_at: finalizedAt, - }; + }); inBlockTxs.add(tx.hash); const lastNonce = addrToNonce[tx.address]; const lastTimestamp = addrToTimestamp[tx.address]; @@ -521,7 +517,7 @@ class TransactionPool { } getPerAccountPoolSize(address) { - return this.transactions[address] ? this.transactions[address].length : 0; + return this.transactions.has(address) ? this.transactions.get(address).length : 0; } }