From 0fe8010a10bafd67f9131b2da034fb9cd7fc5024 Mon Sep 17 00:00:00 2001 From: Pieter Wuille Date: Sun, 12 May 2013 15:50:22 +0200 Subject: [PATCH] Make FindBlockByHeight constant-time. Remove the pnext pointer in CBlockIndex, and replace it with a vBlockIndexByHeight vector (no effect on memory usage). pnext can now be replaced by vBlockIndexByHeight[nHeight+1], but FindBlockByHeight becomes constant-time. This also means the entire mapBlockIndex structure and the block index entries in it become purely blocktree-related data, and independent from the currently active chain, potentially allowing them to be protected by separate mutexes in the future. --- .../test-patches/bitcoind-comparison.patch | 2 +- src/main.cpp | 54 +++++++------------ src/main.h | 24 ++++----- src/rpcblockchain.cpp | 5 +- src/wallet.cpp | 2 +- 5 files changed, 36 insertions(+), 51 deletions(-) diff --git a/contrib/test-patches/bitcoind-comparison.patch b/contrib/test-patches/bitcoind-comparison.patch index 7464349b3c..f82b102e2a 100644 --- a/contrib/test-patches/bitcoind-comparison.patch +++ b/contrib/test-patches/bitcoind-comparison.patch @@ -3,9 +3,9 @@ index 04a8618..519429a 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -31,8 +31,8 @@ CTxMemPool mempool; - unsigned int nTransactionsUpdated = 0; map mapBlockIndex; + std::vector vBlockIndexByHeight; -uint256 hashGenesisBlock("0x000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f"); -static CBigNum bnProofOfWorkLimit(~uint256(0) >> 32); +uint256 hashGenesisBlock("0x0f9188f13cb7b2c71f2a335e3a4fc328bf5beb436012afca590b1a11466e2206"); diff --git a/src/main.cpp b/src/main.cpp index e2bed52787..5fa35f011b 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -31,6 +31,7 @@ CTxMemPool mempool; unsigned int nTransactionsUpdated = 0; map mapBlockIndex; +std::vector vBlockIndexByHeight; uint256 hashGenesisBlock("0x000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f"); static CBigNum bnProofOfWorkLimit(~uint256(0) >> 32); CBlockIndex* pindexGenesisBlock = NULL; @@ -1036,19 +1037,9 @@ bool GetTransaction(const uint256 &hash, CTransaction &txOut, uint256 &hashBlock static CBlockIndex* pblockindexFBBHLast; CBlockIndex* FindBlockByHeight(int nHeight) { - CBlockIndex *pblockindex; - if (nHeight < nBestHeight / 2) - pblockindex = pindexGenesisBlock; - else - pblockindex = pindexBest; - if (pblockindexFBBHLast && abs(nHeight - pblockindex->nHeight) > abs(nHeight - pblockindexFBBHLast->nHeight)) - pblockindex = pblockindexFBBHLast; - while (pblockindex->nHeight > nHeight) - pblockindex = pblockindex->pprev; - while (pblockindex->nHeight < nHeight) - pblockindex = pblockindex->pnext; - pblockindexFBBHLast = pblockindex; - return pblockindex; + if (nHeight >= (int)vBlockIndexByHeight.size()) + return NULL; + return vBlockIndexByHeight[nHeight]; } bool CBlock::ReadFromDisk(const CBlockIndex* pindex) @@ -1231,7 +1222,7 @@ void static InvalidBlockFound(CBlockIndex *pindex) { pblocktree->WriteBlockIndex(CDiskBlockIndex(pindex)); setBlockIndexValid.erase(pindex); InvalidChainFound(pindex); - if (pindex->pnext) { + if (pindex->GetNextInMainChain()) { CValidationState stateDummy; ConnectBestBlock(stateDummy); // reorganise away from the failed block } @@ -1271,7 +1262,7 @@ bool ConnectBestBlock(CValidationState &state) { if (pindexBest == NULL || pindexTest->nChainWork > pindexBest->nChainWork) vAttach.push_back(pindexTest); - if (pindexTest->pprev == NULL || pindexTest->pnext != NULL) { + if (pindexTest->pprev == NULL || pindexTest->GetNextInMainChain()) { reverse(vAttach.begin(), vAttach.end()); BOOST_FOREACH(CBlockIndex *pindexSwitch, vAttach) { boost::this_thread::interruption_point(); @@ -1849,15 +1840,10 @@ bool SetBestChain(CValidationState &state, CBlockIndex* pindexNew) // At this point, all changes have been done to the database. // Proceed by updating the memory structures. - // Disconnect shorter branch - BOOST_FOREACH(CBlockIndex* pindex, vDisconnect) - if (pindex->pprev) - pindex->pprev->pnext = NULL; - - // Connect longer branch + // Register new best chain + vBlockIndexByHeight.resize(pindexNew->nHeight + 1); BOOST_FOREACH(CBlockIndex* pindex, vConnect) - if (pindex->pprev) - pindex->pprev->pnext = pindex; + vBlockIndexByHeight[pindex->nHeight] = pindex; // Resurrect memory transactions that were in the disconnected branch BOOST_FOREACH(CTransaction& tx, vResurrect) { @@ -2609,12 +2595,12 @@ bool static LoadBlockIndexDB() nBestHeight = pindexBest->nHeight; nBestChainWork = pindexBest->nChainWork; - // set 'next' pointers in best chain + // register best chain CBlockIndex *pindex = pindexBest; - while(pindex != NULL && pindex->pprev != NULL) { - CBlockIndex *pindexPrev = pindex->pprev; - pindexPrev->pnext = pindex; - pindex = pindexPrev; + vBlockIndexByHeight.resize(pindexBest->nHeight + 1); + while(pindex != NULL) { + vBlockIndexByHeight[pindex->nHeight] = pindex; + pindex = pindex->pprev; } printf("LoadBlockIndexDB(): hashBestChain=%s height=%d date=%s\n", hashBestChain.ToString().c_str(), nBestHeight, @@ -2683,7 +2669,7 @@ bool VerifyDB() { CBlockIndex *pindex = pindexState; while (pindex != pindexBest) { boost::this_thread::interruption_point(); - pindex = pindex->pnext; + pindex = pindex->GetNextInMainChain(); CBlock block; if (!block.ReadFromDisk(pindex)) return error("VerifyDB() : *** block.ReadFromDisk failed at %d, hash=%s", pindex->nHeight, pindex->GetBlockHash().ToString().c_str()); @@ -2859,7 +2845,7 @@ void PrintBlockTree() vector& vNext = mapNext[pindex]; for (unsigned int i = 0; i < vNext.size(); i++) { - if (vNext[i]->pnext) + if (vNext[i]->GetNextInMainChain()) { swap(vNext[0], vNext[i]); break; @@ -3440,10 +3426,10 @@ bool static ProcessMessage(CNode* pfrom, string strCommand, CDataStream& vRecv) // Send the rest of the chain if (pindex) - pindex = pindex->pnext; + pindex = pindex->GetNextInMainChain(); int nLimit = 500; printf("getblocks %d to %s limit %d\n", (pindex ? pindex->nHeight : -1), hashStop.ToString().c_str(), nLimit); - for (; pindex; pindex = pindex->pnext) + for (; pindex; pindex = pindex->GetNextInMainChain()) { if (pindex->GetBlockHash() == hashStop) { @@ -3483,14 +3469,14 @@ bool static ProcessMessage(CNode* pfrom, string strCommand, CDataStream& vRecv) // Find the last block the caller has in the main chain pindex = locator.GetBlockIndex(); if (pindex) - pindex = pindex->pnext; + pindex = pindex->GetNextInMainChain(); } // we must use CBlocks, as CBlockHeaders won't include the 0x00 nTx count at the end vector vHeaders; int nLimit = 2000; printf("getheaders %d to %s\n", (pindex ? pindex->nHeight : -1), hashStop.ToString().c_str()); - for (; pindex; pindex = pindex->pnext) + for (; pindex; pindex = pindex->GetNextInMainChain()) { vHeaders.push_back(pindex->GetBlockHeader()); if (--nLimit <= 0 || pindex->GetBlockHash() == hashStop) diff --git a/src/main.h b/src/main.h index cba8421c85..020f235485 100644 --- a/src/main.h +++ b/src/main.h @@ -69,6 +69,7 @@ extern CScript COINBASE_FLAGS; extern CCriticalSection cs_main; extern std::map mapBlockIndex; +extern std::vector vBlockIndexByHeight; extern std::set setBlockIndexValid; extern uint256 hashGenesisBlock; extern CBlockIndex* pindexGenesisBlock; @@ -1589,10 +1590,8 @@ enum BlockStatus { /** The block chain is a tree shaped structure starting with the * genesis block at the root, with each block potentially having multiple - * candidates to be the next block. pprev and pnext link a path through the - * main/longest chain. A blockindex may have multiple pprev pointing back - * to it, but pnext will only point forward to the longest branch, or will - * be null if the block is not part of the longest chain. + * candidates to be the next block. A blockindex may have multiple pprev pointing + * to it, but at most one of them can be part of the currently active branch. */ class CBlockIndex { @@ -1603,9 +1602,6 @@ public: // pointer to the index of the predecessor of this block CBlockIndex* pprev; - // (memory only) pointer to the index of the *active* successor of this block - CBlockIndex* pnext; - // height of the entry in the chain. The genesis block has height 0 int nHeight; @@ -1643,7 +1639,6 @@ public: { phashBlock = NULL; pprev = NULL; - pnext = NULL; nHeight = 0; nFile = 0; nDataPos = 0; @@ -1664,7 +1659,6 @@ public: { phashBlock = NULL; pprev = NULL; - pnext = NULL; nHeight = 0; nFile = 0; nDataPos = 0; @@ -1733,7 +1727,11 @@ public: bool IsInMainChain() const { - return (pnext || this == pindexBest); + return nHeight < (int)vBlockIndexByHeight.size() && vBlockIndexByHeight[nHeight] == this; + } + + CBlockIndex *GetNextInMainChain() const { + return nHeight+1 >= (int)vBlockIndexByHeight.size() ? NULL : vBlockIndexByHeight[nHeight+1]; } bool CheckIndex() const @@ -1762,9 +1760,9 @@ public: const CBlockIndex* pindex = this; for (int i = 0; i < nMedianTimeSpan/2; i++) { - if (!pindex->pnext) + if (!pindex->GetNextInMainChain()) return GetBlockTime(); - pindex = pindex->pnext; + pindex = pindex->GetNextInMainChain(); } return pindex->GetMedianTimePast(); } @@ -1779,7 +1777,7 @@ public: std::string ToString() const { return strprintf("CBlockIndex(pprev=%p, pnext=%p, nHeight=%d, merkle=%s, hashBlock=%s)", - pprev, pnext, nHeight, + pprev, GetNextInMainChain(), nHeight, hashMerkleRoot.ToString().c_str(), GetBlockHash().ToString().c_str()); } diff --git a/src/rpcblockchain.cpp b/src/rpcblockchain.cpp index 11af1abf5d..b1b0c1ac16 100644 --- a/src/rpcblockchain.cpp +++ b/src/rpcblockchain.cpp @@ -65,8 +65,9 @@ Object blockToJSON(const CBlock& block, const CBlockIndex* blockindex) if (blockindex->pprev) result.push_back(Pair("previousblockhash", blockindex->pprev->GetBlockHash().GetHex())); - if (blockindex->pnext) - result.push_back(Pair("nextblockhash", blockindex->pnext->GetBlockHash().GetHex())); + CBlockIndex *pnext = blockindex->GetNextInMainChain(); + if (pnext) + result.push_back(Pair("nextblockhash", pnext->GetBlockHash().GetHex())); return result; } diff --git a/src/wallet.cpp b/src/wallet.cpp index c70ea20e88..1cc2483232 100644 --- a/src/wallet.cpp +++ b/src/wallet.cpp @@ -780,7 +780,7 @@ int CWallet::ScanForWalletTransactions(CBlockIndex* pindexStart, bool fUpdate) if (AddToWalletIfInvolvingMe(tx.GetHash(), tx, &block, fUpdate)) ret++; } - pindex = pindex->pnext; + pindex = pindex->GetNextInMainChain(); } } return ret;