Skip to content

Commit 27d0b70

Browse files
committed
Merge #962: [Chain] Prune entire orphaned chains.
2054946 [Chain] Prune entire orphaned chains. (Zannick) Pull request description: ### Problem Pruned blocks sometimes cause other blocks in the index (mostly descendants of orphans) to have invalid parents, which may trip an assert `pindexWalk->pprev` (#692) on startup. Additionally, because block indexes *don't own their own hash values*, removing the index from `mapBlockIndex` where the hash is allocated may lead to a use-after-free corrupting the hash of a block written to disk (may be the cause of #930 (comment)). ### Solution Select pruning candidates in two rounds: first, based on height. Then, add all descendants of the blocks chosen in the first round. For the memory issue, add a `shared_ptr` field to CBlockIndex that provides the hash value when the block is removed from mapBlockIndex. This will manage the memory of the hash for the lifetime of the object (which is forever anyway) and allows for efficient sharing and copying as usual (unique_ptr does not). ### Testing Via regtest. See #692 (comment) Tree-SHA512: ef000928dff3ad45e214f07e23795d92b1a75c6221180982e02a82f28a69349975a46813a34d966c1bb73bd927d007ce892e0d85afe1cac055c1da3931e3ea5b
2 parents 5031c6c + 2054946 commit 27d0b70

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)