Skip to content

Commit 2054946

Browse files
committed
[Chain] Prune entire orphaned chains.
If the index contains forks other than the active fork, then removing one from an early part of the chain can cause startup to assert `pindexWalk->pprev` on the remaining blocks in the fork which no longer have parents in the index. (Issue #692) This is made likely by the pruning process that only prunes orphans based on height far enough from the best block. Solution: After finding out which index entries are old enough to be pruned, search the index for all entries that descend from any of those, and add them to be pruned. Additionally, CBlockIndex pointers are passed all over the codebase and are never deleted, but their hash values are pointers to the mapBlockIndex's keys. Normally this isn't a problem (unordered_map), until the data is erased and the memory reused.
1 parent 5031c6c commit 2054946

File tree

3 files changed

+63
-15
lines changed

3 files changed

+63
-15
lines changed

src/chain.cpp

+10-2
Original file line numberDiff line numberDiff line change
@@ -109,8 +109,8 @@ const CBlockIndex* CBlockIndex::GetAncestor(int height) const
109109
heightWalk = heightSkip;
110110
} else {
111111
if (!pindexWalk->pprev) {
112-
LogPrintf("%s: pindexWalk failed: Hash: %s, Current Height: %d, Working Height: %d\n",
113-
__func__, pindexWalk->phashBlock->GetHex(), height, nHeight);
112+
LogPrintf("%s: pindexWalk: Hash: %s, Looking for Height: %d, Current Height: %d, Working Height: %d, pindexWalk height: %d\n",
113+
__func__, pindexWalk->phashBlock->GetHex(), height, heightWalk, nHeight, pindexWalk->nHeight);
114114
}
115115
assert(pindexWalk->pprev);
116116
pindexWalk = pindexWalk->pprev;
@@ -275,6 +275,14 @@ uint256 CBlockIndex::GetProgPowHash(uint256& mix_hash) const
275275
return ProgPowHash(header, mix_hash);
276276
}
277277

278+
void CBlockIndex::CopyBlockHashIntoIndex()
279+
{
280+
if (phashBlock) {
281+
_phashBlock.reset(new uint256(*phashBlock));
282+
phashBlock = _phashBlock.get();
283+
}
284+
}
285+
278286
// We are going to be performing a block hash for RandomX. To see if we need to spin up a new
279287
// cache, we can first check to see if we can use the current validation cache
280288
uint256 GetRandomXBlockHash(const int32_t& height, const uint256& hash_blob ) {

src/chain.h

+8-2
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,9 @@
1515
#include <uint256.h>
1616
#include <libzerocoin/bignum.h>
1717

18-
#include <vector>
1918
#include <map>
19+
#include <memory>
20+
#include <vector>
2021

2122
/**
2223
* Maximum amount of time that a block timestamp is allowed to exceed the
@@ -178,8 +179,11 @@ enum BlockMemFlags: uint32_t {
178179
*/
179180
class CBlockIndex
180181
{
182+
private:
183+
//! owned pointer to the hash of the block, for use when erased from mapBlockIndex.
184+
std::shared_ptr<const uint256> _phashBlock;
181185
public:
182-
//! pointer to the hash of the block, if any. Memory is owned by mapBlockIndex
186+
//! pointer to the hash of the block, if any. Memory is owned by mapBlockIndex, if this index is in mapBlockIndex
183187
const uint256* phashBlock{nullptr};
184188

185189
//! pointer to the index of the predecessor of this block
@@ -572,6 +576,8 @@ class CBlockIndex
572576
//! Adds all accumulators to the block idnex given an accumulator map
573577
void AddAccumulator(AccumulatorMap mapAccumulator);
574578

579+
//! Copies the value of phashBlock (if any) into a new uint256 owned by the CBlockIndex.
580+
void CopyBlockHashIntoIndex();
575581
};
576582

577583
arith_uint256 GetBlockProof(const CBlockIndex& block);

src/validation.cpp

+45-11
Original file line numberDiff line numberDiff line change
@@ -3606,10 +3606,11 @@ void PruneStaleBlockIndexes()
36063606
// This isn't going to change while we're processing, so just leave and try again later.
36073607
if (!pindexBestHeader) return;
36083608

3609-
std::set<uint256> setDelete;
36103609
uint32_t irrelevantIndexes = 0;
36113610
int64_t lowestPrunedHeight = -1;
36123611
int64_t currentHeight;
3612+
std::set<const CBlockIndex*> setDelete;
3613+
std::set<const CBlockIndex*> setMaybeDelete;
36133614
std::vector<std::pair<uint256, CBlockIndex*>> checkPrunable;
36143615
{
36153616
LOCK(cs_mapblockindex);
@@ -3642,29 +3643,62 @@ void PruneStaleBlockIndexes()
36423643

36433644
// if it's also old enough, add it to the prune list.
36443645
if (currentHeight > static_cast<int>(pindex->nHeight + PRUNE_DEPTH)) {
3645-
setDelete.emplace(p.first);
3646+
setDelete.emplace(pindex);
36463647

36473648
// save the lowest height that we're purging, even if it's lower than the previous
36483649
// lastPrunedHeight
36493650
if (pindex->nHeight < lowestPrunedHeight || lowestPrunedHeight == -1) {
36503651
lowestPrunedHeight = pindex->nHeight;
36513652
}
3653+
} else if (!chainActive.Contains(pindex->pprev) && !IsAncestor(pindexBestHeader, pindex->pprev)) {
3654+
// Or, if an ancestor might be pruned, in which case we would have to prune, note it to check later.
3655+
setMaybeDelete.emplace(pindex);
3656+
}
3657+
}
3658+
}
3659+
3660+
int descendants = 0;
3661+
for (const CBlockIndex* pindex : setMaybeDelete) {
3662+
for (const CBlockIndex* prune : setDelete) {
3663+
if (IsAncestor(pindex, prune)) {
3664+
++descendants;
3665+
setDelete.emplace(pindex);
3666+
break;
36523667
}
36533668
}
36543669
}
36553670

36563671
if (setDelete.size() > 0) {
3657-
LogPrintf("%s: Erasing %d of %d irrelevant indexes. LastPrunedHeight now %d.\n",
3658-
__func__, setDelete.size(), irrelevantIndexes, lowestPrunedHeight);
3672+
LogPrintf("%s: Erasing %d of %d irrelevant indexes, including %d descendants. LastPrunedHeight now %d.\n",
3673+
__func__, setDelete.size(), irrelevantIndexes, descendants, lowestPrunedHeight);
36593674

3660-
LOCK(cs_mapblockindex);
3661-
// Purge the ones we flagged
3662-
for (const uint256& hash : setDelete) {
3663-
mapBlockIndex.erase(hash);
3664-
}
3675+
LOCK(cs_mapblockindex);
3676+
// Purge the ones we flagged
3677+
for (const CBlockIndex* pindex : setDelete) {
3678+
auto p = mapBlockIndex.find(*pindex->phashBlock);
3679+
if (p != mapBlockIndex.end()) {
3680+
p->second->CopyBlockHashIntoIndex();
3681+
mapBlockIndex.erase(p);
3682+
}
3683+
}
36653684

3666-
// Step back a little for safety
3667-
lastPrunedHeight = lowestPrunedHeight;
3685+
// Step back a little for safety
3686+
lastPrunedHeight = lowestPrunedHeight;
3687+
3688+
// Double-check remaining unpruned indexes
3689+
for (const auto& p : mapBlockIndex) {
3690+
const CBlockIndex* pindex = p.second;
3691+
3692+
if (!chainActive.Contains(pindex)) {
3693+
if (setDelete.find(pindex->pprev) != setDelete.end()) {
3694+
LogPrintf("%s: DANGER: Potentially dangling pprev pointer at orphan hash=%s height=%d. Chain height=%d\n",
3695+
__func__, pindex->phashBlock->GetHex(), pindex->nHeight, chainActive.Height());
3696+
} else if (setDelete.find(pindex->pskip) != setDelete.end()) {
3697+
LogPrintf("%s: DANGER: Potentially dangling pskip pointer at orphan hash=%s height=%d. Chain height=%d\n",
3698+
__func__, pindex->phashBlock->GetHex(), pindex->nHeight, chainActive.Height());
3699+
}
3700+
}
3701+
}
36683702
}
36693703
}
36703704

0 commit comments

Comments
 (0)