diff --git a/src/main.cpp b/src/main.cpp index 959b8e2..d809724 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1407,6 +1407,48 @@ uint256 WantedByOrphan(const CBlock* pblockOrphan) return pblockOrphan->hashPrevBlock; } +// Evict excess orphan blocks when limit is exceeded +// Returns number of orphans evicted +unsigned int LimitOrphanBlocks(unsigned int nMaxOrphans) +{ + unsigned int nEvicted = 0; + while (mapOrphanBlocks.size() > nMaxOrphans) + { + // Evict a random orphan + uint256 randomhash = GetRandHash(); + auto it = mapOrphanBlocks.lower_bound(randomhash); + if (it == mapOrphanBlocks.end()) + it = mapOrphanBlocks.begin(); + + if (it == mapOrphanBlocks.end()) + break; // No orphans to evict + + CBlock* pblockEvict = it->second; + uint256 evictHash = it->first; + + // Remove from by-prev index + for (auto range = mapOrphanBlocksByPrev.equal_range(pblockEvict->hashPrevBlock); + range.first != range.second; ++range.first) + { + if (range.first->second == pblockEvict) { + mapOrphanBlocksByPrev.erase(range.first); + break; + } + } + + setStakeSeenOrphan.erase(pblockEvict->GetProofOfStake()); + delete pblockEvict; + mapOrphanBlocks.erase(evictHash); + nEvicted++; + } + + if (nEvicted > 0) + printf("LimitOrphanBlocks: evicted %u orphan(s), %u remain\n", + nEvicted, (unsigned int)mapOrphanBlocks.size()); + + return nEvicted; +} + // miner's coin base reward int64_t GetProofOfWorkReward(int64_t nFees) { @@ -1500,9 +1542,13 @@ static unsigned int GetNextTargetRequired_(const CBlockIndex* pindexLast, bool f return bnTargetLimit.GetCompact(); // genesis block const CBlockIndex* pindexPrev = GetLastBlockIndex(pindexLast, fProofOfStake); + if (pindexPrev == NULL) + return bnTargetLimit.GetCompact(); // no previous block of this type if (pindexPrev->pprev == NULL) return bnTargetLimit.GetCompact(); // first block const CBlockIndex* pindexPrevPrev = GetLastBlockIndex(pindexPrev->pprev, fProofOfStake); + if (pindexPrevPrev == NULL) + return bnTargetLimit.GetCompact(); // no second previous block of this type if (pindexPrevPrev->pprev == NULL) return bnTargetLimit.GetCompact(); // second block @@ -2472,12 +2518,6 @@ bool static Reorganize(CTxDB& txdb, CBlockIndex* pindexNew) vResurrect.push_back(tx); } - // Remove disconnected PoS blocks from setStakeSeen so they don't - // block acceptance of valid blocks on the winning chain. - for (CBlockIndex* pindex : vDisconnect) - if (pindex->IsProofOfStake()) - setStakeSeen.erase(make_pair(pindex->prevoutStake, pindex->nStakeTime)); - // Connect longer branch vector vDelete; for (unsigned int i = 0; i < vConnect.size(); i++) @@ -2505,19 +2545,37 @@ bool static Reorganize(CTxDB& txdb, CBlockIndex* pindexNew) if (!txdb.TxnCommit()) return error("Reorganize() : TxnCommit failed"); - // Disconnect shorter branch + // ====================================================================== + // CRITICAL: All operations below this point must be in-memory only and + // should never fail. The DB transaction is committed, so we cannot abort. + // ====================================================================== + + // Disconnect shorter branch (in-memory only) for (CBlockIndex* pindex : vDisconnect) if (pindex->pprev) pindex->pprev->pnext = NULL; - // Connect longer branch + // Connect longer branch (in-memory only) for (CBlockIndex* pindex : vConnect) if (pindex->pprev) pindex->pprev->pnext = pindex; + // Remove disconnected PoS blocks from setStakeSeen so they don't + // block acceptance of valid blocks on the winning chain. + // This MUST happen after commit to maintain consistency. + for (CBlockIndex* pindex : vDisconnect) + if (pindex->IsProofOfStake()) + setStakeSeen.erase(make_pair(pindex->prevoutStake, pindex->nStakeTime)); + // Resurrect memory transactions that were in the disconnected branch + unsigned int nResurrected = 0; for (CTransaction& tx : vResurrect) - tx.AcceptToMemoryPool(txdb, false); + { + if (tx.AcceptToMemoryPool(txdb, false)) + nResurrected++; + } + if (nResurrected > 0) + printf("REORGANIZE: resurrected %u transactions to mempool\n", nResurrected); // Delete redundant memory transactions that are in the connected branch for (CTransaction& tx : vDelete) { @@ -2525,7 +2583,8 @@ bool static Reorganize(CTxDB& txdb, CBlockIndex* pindexNew) mempool.removeConflicts(tx); } - printf("REORGANIZE: done\n"); + printf("REORGANIZE: done (fork at height %d, %zu disconnected, %zu connected)\n", + pfork->nHeight, vDisconnect.size(), vConnect.size()); return true; } @@ -2715,6 +2774,9 @@ bool CBlock::SetBestChain(CTxDB& txdb, CBlockIndex* pindexNew) { printf("*** Initial block download complete at height %d ***\n", nBestHeight); + // Trim orphan blocks to normal limit now that IBD is done + LimitOrphanBlocks(MAX_ORPHAN_BLOCKS); + // Update wallet best chain locator now that IBD is done const CBlockLocator locator(pindexBest); ::SetBestChain(locator); @@ -3246,29 +3308,7 @@ bool ProcessBlock(CNode* pfrom, CBlock* pblock) // Allow more orphans during IBD so out-of-order blocks from parallel // downloads don't get evicted and re-requested. unsigned int nMaxOrphans = IsInitialBlockDownload() ? MAX_ORPHAN_BLOCKS_IBD : MAX_ORPHAN_BLOCKS; - if (mapOrphanBlocks.size() > nMaxOrphans) - { - // Evict a random orphan - uint256 randomhash = GetRandHash(); - auto it = mapOrphanBlocks.lower_bound(randomhash); - if (it == mapOrphanBlocks.end()) - it = mapOrphanBlocks.begin(); - CBlock* pblockEvict = it->second; - uint256 evictHash = it->first; - // Remove from by-prev index - for (auto range = mapOrphanBlocksByPrev.equal_range(pblockEvict->hashPrevBlock); - range.first != range.second; ++range.first) - { - if (range.first->second == pblockEvict) { - mapOrphanBlocksByPrev.erase(range.first); - break; - } - } - setStakeSeenOrphan.erase(pblockEvict->GetProofOfStake()); - delete pblockEvict; - mapOrphanBlocks.erase(evictHash); - printf("ProcessBlock: orphan eviction, %u orphans remain\n", (unsigned int)mapOrphanBlocks.size()); - } + LimitOrphanBlocks(nMaxOrphans); // Ask this guy to fill in what we're missing if (pfrom && pindexBest) diff --git a/src/main.h b/src/main.h index 76e7b64..8469702 100644 --- a/src/main.h +++ b/src/main.h @@ -136,6 +136,7 @@ bool IsInitialBlockDownload(); std::string GetWarnings(std::string strFor); bool GetTransaction(const uint256 &hash, CTransaction &tx, uint256 &hashBlock); uint256 WantedByOrphan(const CBlock* pblockOrphan); +unsigned int LimitOrphanBlocks(unsigned int nMaxOrphans); const CBlockIndex* GetLastBlockIndex(const CBlockIndex* pindex, bool fProofOfStake); void StakeMiner(CWallet *pwallet); void ResendWalletTransactions(bool fForce = false); diff --git a/src/rpcblockchain.cpp b/src/rpcblockchain.cpp index 841dccc..2258685 100644 --- a/src/rpcblockchain.cpp +++ b/src/rpcblockchain.cpp @@ -28,6 +28,9 @@ double GetDifficulty(const CBlockIndex* blockindex) blockindex = GetLastBlockIndex(pindexBest, false); } + if (blockindex == NULL) + return 1.0; + int nShift = (blockindex->nBits >> 24) & 0xff; double dDiff = diff --git a/src/txdb-leveldb.cpp b/src/txdb-leveldb.cpp index a95afa8..847a738 100644 --- a/src/txdb-leveldb.cpp +++ b/src/txdb-leveldb.cpp @@ -162,7 +162,9 @@ bool CTxDB::TxnCommit() delete activeBatch; activeBatch = NULL; if (!status.ok()) { - printf("LevelDB batch commit failure: %s\n", status.ToString().c_str()); + printf("ERROR: LevelDB batch commit failure: %s\n", status.ToString().c_str()); + printf("ERROR: This may indicate disk full, corruption, or permissions issue.\n"); + printf("ERROR: Chain state may be inconsistent - immediate investigation required!\n"); return false; } return true;