Skip to content

Commit 02dfa4a

Browse files
committed
Non-atomic dbcache flushing with block replay
1 parent e58f855 commit 02dfa4a

File tree

7 files changed

+199
-19
lines changed

7 files changed

+199
-19
lines changed

src/coins.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ bool CCoins::Spend(uint32_t nPos)
4444
bool CCoinsView::GetCoins(const uint256 &txid, CCoins &coins) const { return false; }
4545
bool CCoinsView::HaveCoins(const uint256 &txid) const { return false; }
4646
uint256 CCoinsView::GetBestBlock() const { return uint256(); }
47+
std::vector<uint256> CCoinsView::GetHeadBlocks() const { return std::vector<uint256>(); }
4748
bool CCoinsView::BatchWrite(CCoinsMap &mapCoins, const uint256 &hashBlock) { return false; }
4849
CCoinsViewCursor *CCoinsView::Cursor() const { return 0; }
4950

@@ -52,6 +53,7 @@ CCoinsViewBacked::CCoinsViewBacked(CCoinsView *viewIn) : base(viewIn) { }
5253
bool CCoinsViewBacked::GetCoins(const uint256 &txid, CCoins &coins) const { return base->GetCoins(txid, coins); }
5354
bool CCoinsViewBacked::HaveCoins(const uint256 &txid) const { return base->HaveCoins(txid); }
5455
uint256 CCoinsViewBacked::GetBestBlock() const { return base->GetBestBlock(); }
56+
std::vector<uint256> CCoinsViewBacked::GetHeadBlocks() const { return base->GetHeadBlocks(); }
5557
void CCoinsViewBacked::SetBackend(CCoinsView &viewIn) { base = &viewIn; }
5658
bool CCoinsViewBacked::BatchWrite(CCoinsMap &mapCoins, const uint256 &hashBlock) { return base->BatchWrite(mapCoins, hashBlock); }
5759
CCoinsViewCursor *CCoinsViewBacked::Cursor() const { return base->Cursor(); }

src/coins.h

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -315,6 +315,11 @@ class CCoinsView
315315
//! Retrieve the block hash whose state this CCoinsView currently represents
316316
virtual uint256 GetBestBlock() const;
317317

318+
//! Retrieve the block hashes that delimit the subtree of blocks for which
319+
//! partial updates may have been written. The first entry is the intended
320+
//! tip. Only when GetBestBlock() is not set.
321+
virtual std::vector<uint256> GetHeadBlocks() const;
322+
318323
//! Do a bulk modification (multiple CCoins changes + BestBlock change).
319324
//! The passed mapCoins can be modified.
320325
virtual bool BatchWrite(CCoinsMap &mapCoins, const uint256 &hashBlock);
@@ -335,12 +340,13 @@ class CCoinsViewBacked : public CCoinsView
335340

336341
public:
337342
CCoinsViewBacked(CCoinsView *viewIn);
338-
bool GetCoins(const uint256 &txid, CCoins &coins) const;
339-
bool HaveCoins(const uint256 &txid) const;
340-
uint256 GetBestBlock() const;
343+
bool GetCoins(const uint256 &txid, CCoins &coins) const override;
344+
bool HaveCoins(const uint256 &txid) const override;
345+
uint256 GetBestBlock() const override;
346+
std::vector<uint256> GetHeadBlocks() const override;
341347
void SetBackend(CCoinsView &viewIn);
342-
bool BatchWrite(CCoinsMap &mapCoins, const uint256 &hashBlock);
343-
CCoinsViewCursor *Cursor() const;
348+
bool BatchWrite(CCoinsMap &mapCoins, const uint256 &hashBlock) override;
349+
CCoinsViewCursor *Cursor() const override;
344350
};
345351

346352

src/init.cpp

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -346,6 +346,9 @@ std::string HelpMessage(HelpMessageMode mode)
346346
#endif
347347
}
348348
strUsage += HelpMessageOpt("-datadir=<dir>", _("Specify data directory"));
349+
if (showDebug) {
350+
strUsage += HelpMessageOpt("-dbbatchsize", strprintf("Maximum database write batch size in MiB (default: %u)", nDefaultDbBatchSize));
351+
}
349352
strUsage += HelpMessageOpt("-dbcache=<n>", strprintf(_("Set database cache size in megabytes (%d to %d, default: %d)"), nMinDbCache, nMaxDbCache, nDefaultDbCache));
350353
if (showDebug)
351354
strUsage += HelpMessageOpt("-feefilter", strprintf("Tell other nodes to filter invs to us by our mempool min fee (default: %u)", DEFAULT_FEEFILTER));
@@ -1476,6 +1479,13 @@ bool AppInitMain(boost::thread_group& threadGroup, CScheduler& scheduler)
14761479
break;
14771480
}
14781481

1482+
if (!ReplayBlocks(chainparams, pcoinsdbview)) {
1483+
strLoadError = _("Unable to replay blocks. You will need to rebuild the database using -reindex-chainstate.");
1484+
break;
1485+
}
1486+
pcoinsTip->SetBestBlock(pcoinsdbview->GetBestBlock()); // TODO: only initialize pcoinsTip after ReplayBlocks
1487+
LoadChainTip(chainparams);
1488+
14791489
if (!fReindex && chainActive.Tip() != NULL) {
14801490
uiInterface.InitMessage(_("Rewinding blocks..."));
14811491
if (!RewindBlockIndex(chainparams)) {

src/txdb.cpp

Lines changed: 36 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ static const char DB_TXINDEX = 't';
2020
static const char DB_BLOCK_INDEX = 'b';
2121

2222
static const char DB_BEST_BLOCK = 'B';
23+
static const char DB_HEAD_BLOCKS = 'H';
2324
static const char DB_FLAG = 'F';
2425
static const char DB_REINDEX_FLAG = 'R';
2526
static const char DB_LAST_BLOCK = 'l';
@@ -44,10 +45,33 @@ uint256 CCoinsViewDB::GetBestBlock() const {
4445
return hashBestChain;
4546
}
4647

48+
std::vector<uint256> CCoinsViewDB::GetHeadBlocks() const {
49+
std::vector<uint256> vhashHeadBlocks;
50+
if (!db.Read(DB_HEAD_BLOCKS, vhashHeadBlocks))
51+
return std::vector<uint256>();
52+
return vhashHeadBlocks;
53+
}
54+
4755
bool CCoinsViewDB::BatchWrite(CCoinsMap &mapCoins, const uint256 &hashBlock) {
4856
CDBBatch batch(db);
4957
size_t count = 0;
5058
size_t changed = 0;
59+
size_t batch_size = (size_t)GetArg("-dbbatchsize", nDefaultDbBatchSize) << 20;
60+
if (!hashBlock.IsNull()) {
61+
// Read existing heads from database.
62+
uint256 tip = GetBestBlock();
63+
std::vector<uint256> heads = GetHeadBlocks();
64+
// Construct a set with all existing heads, excluding the new tip.
65+
std::set<uint256> setHeads(heads.begin(), heads.end());
66+
if (setHeads.empty() || !tip.IsNull()) setHeads.insert(tip);
67+
setHeads.erase(hashBlock);
68+
// Construct a vector with the new tip first, and other heads afterwards.
69+
heads.assign(1, hashBlock);
70+
heads.insert(heads.end(), setHeads.begin(), setHeads.end());
71+
// Write to batch.
72+
batch.Erase(DB_BEST_BLOCK);
73+
batch.Write(DB_HEAD_BLOCKS, heads);
74+
}
5175
for (CCoinsMap::iterator it = mapCoins.begin(); it != mapCoins.end();) {
5276
if (it->second.flags & CCoinsCacheEntry::DIRTY) {
5377
if (it->second.coins.IsPruned())
@@ -59,12 +83,21 @@ bool CCoinsViewDB::BatchWrite(CCoinsMap &mapCoins, const uint256 &hashBlock) {
5983
count++;
6084
CCoinsMap::iterator itOld = it++;
6185
mapCoins.erase(itOld);
86+
if (batch.SizeEstimate() > batch_size) {
87+
LogPrint(BCLog::COINDB, "Writing partial batch of %.2f MiB\n", batch.SizeEstimate() * (1.0 / 1048576.0));
88+
db.WriteBatch(batch);
89+
batch.Clear();
90+
}
6291
}
63-
if (!hashBlock.IsNull())
92+
if (!hashBlock.IsNull()) {
93+
batch.Erase(DB_HEAD_BLOCKS);
6494
batch.Write(DB_BEST_BLOCK, hashBlock);
95+
}
6596

66-
LogPrint(BCLog::COINDB, "Committing %u changed transactions (out of %u) to coin database...\n", (unsigned int)changed, (unsigned int)count);
67-
return db.WriteBatch(batch);
97+
LogPrint(BCLog::COINDB, "Writing final batch of %.2f MiB\n", batch.SizeEstimate() * (1.0 / 1048576.0));
98+
bool ret = db.WriteBatch(batch);
99+
LogPrint(BCLog::COINDB, "Committed %u changed transactions (out of %u) to coin database...\n", (unsigned int)changed, (unsigned int)count);
100+
return ret;
68101
}
69102

70103
CBlockTreeDB::CBlockTreeDB(size_t nCacheSize, bool fMemory, bool fWipe) : CDBWrapper(GetDataDir() / "blocks" / "index", nCacheSize, fMemory, fWipe) {

src/txdb.h

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,14 +21,14 @@ class CBlockIndex;
2121
class CCoinsViewDBCursor;
2222
class uint256;
2323

24-
//! Compensate for extra memory peak (x1.5-x1.9) at flush time.
25-
static constexpr int DB_PEAK_USAGE_FACTOR = 2;
2624
//! No need to periodic flush if at least this much space still available.
27-
static constexpr int MAX_BLOCK_COINSDB_USAGE = 200 * DB_PEAK_USAGE_FACTOR;
25+
static constexpr int MAX_BLOCK_COINSDB_USAGE = 200;
2826
//! Always periodic flush if less than this much space still available.
29-
static constexpr int MIN_BLOCK_COINSDB_USAGE = 50 * DB_PEAK_USAGE_FACTOR;
27+
static constexpr int MIN_BLOCK_COINSDB_USAGE = 50;
3028
//! -dbcache default (MiB)
3129
static const int64_t nDefaultDbCache = 450;
30+
//! -dbbatchsize default (MiB)
31+
static const int64_t nDefaultDbBatchSize = 16;
3232
//! max. -dbcache (MiB)
3333
static const int64_t nMaxDbCache = sizeof(void*) > 4 ? 16384 : 1024;
3434
//! min. -dbcache (MiB)
@@ -78,6 +78,7 @@ class CCoinsViewDB : public CCoinsView
7878
bool GetCoins(const uint256 &txid, CCoins &coins) const;
7979
bool HaveCoins(const uint256 &txid) const;
8080
uint256 GetBestBlock() const;
81+
std::vector<uint256> GetHeadBlocks() const;
8182
bool BatchWrite(CCoinsMap &mapCoins, const uint256 &hashBlock);
8283
CCoinsViewCursor *Cursor() const;
8384
};

src/validation.cpp

Lines changed: 130 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,7 @@ namespace {
9494

9595
struct CBlockIndexWorkComparator
9696
{
97-
bool operator()(CBlockIndex *pa, CBlockIndex *pb) const {
97+
bool operator()(const CBlockIndex *pa, const CBlockIndex *pb) const {
9898
// First sort by most total work, ...
9999
if (pa->nChainWork > pb->nChainWork) return false;
100100
if (pa->nChainWork < pb->nChainWork) return true;
@@ -1506,7 +1506,6 @@ bool ApplyTxInUndo(const CTxInUndo& undo, CCoinsViewCache& view, const COutPoint
15061506
if (undo.nHeight != 0) {
15071507
// undo data contains height: this is the last output of the prevout tx being spent
15081508
if (!coins->IsPruned()) fClean = false;
1509-
coins->Clear();
15101509
coins->fCoinBase = undo.fCoinBase;
15111510
coins->nHeight = undo.nHeight;
15121511
coins->nVersion = undo.nVersion;
@@ -1968,7 +1967,7 @@ bool static FlushStateToDisk(CValidationState &state, FlushStateMode mode, int n
19681967
nLastSetChain = nNow;
19691968
}
19701969
int64_t nMempoolSizeMax = GetArg("-maxmempool", DEFAULT_MAX_MEMPOOL_SIZE) * 1000000;
1971-
int64_t cacheSize = pcoinsTip->DynamicMemoryUsage() * DB_PEAK_USAGE_FACTOR;
1970+
int64_t cacheSize = pcoinsTip->DynamicMemoryUsage();
19721971
int64_t nTotalSpace = nCoinCacheUsage + std::max<int64_t>(nMempoolSizeMax - nMempoolUsage, 0);
19731972
// The cache is large and we're within 10% and 200 MiB or 50% and 50MiB of the limit, but we have time now (not in the middle of a block processing).
19741973
bool fCacheLarge = mode == FLUSH_STATE_PERIODIC && cacheSize > std::min(std::max(nTotalSpace / 2, nTotalSpace - MIN_BLOCK_COINSDB_USAGE * 1024 * 1024),
@@ -3567,20 +3566,26 @@ bool static LoadBlockIndexDB(const CChainParams& chainparams)
35673566
pblocktree->ReadFlag("txindex", fTxIndex);
35683567
LogPrintf("%s: transaction index %s\n", __func__, fTxIndex ? "enabled" : "disabled");
35693568

3569+
LoadChainTip(chainparams);
3570+
return true;
3571+
}
3572+
3573+
void LoadChainTip(const CChainParams& chainparams)
3574+
{
3575+
if (chainActive.Tip() && chainActive.Tip()->GetBlockHash() == pcoinsTip->GetBestBlock()) return;
3576+
35703577
// Load pointer to end of best chain
35713578
BlockMap::iterator it = mapBlockIndex.find(pcoinsTip->GetBestBlock());
35723579
if (it == mapBlockIndex.end())
3573-
return true;
3580+
return;
35743581
chainActive.SetTip(it->second);
35753582

35763583
PruneBlockIndexCandidates();
35773584

3578-
LogPrintf("%s: hashBestChain=%s height=%d date=%s progress=%f\n", __func__,
3585+
LogPrintf("Loaded best chain: hashBestChain=%s height=%d date=%s progress=%f\n",
35793586
chainActive.Tip()->GetBlockHash().ToString(), chainActive.Height(),
35803587
DateTimeStrFormat("%Y-%m-%d %H:%M:%S", chainActive.Tip()->GetBlockTime()),
35813588
GuessVerificationProgress(chainparams.TxData(), chainActive.Tip()));
3582-
3583-
return true;
35843589
}
35853590

35863591
CVerifyDB::CVerifyDB()
@@ -3686,6 +3691,124 @@ bool CVerifyDB::VerifyDB(const CChainParams& chainparams, CCoinsView *coinsview,
36863691
return true;
36873692
}
36883693

3694+
/** Apply the effects of a block on the utxo cache, ignoring that it may already have been applied. */
3695+
static bool RollforwardBlock(const CBlockIndex* pindex, CCoinsViewCache& inputs, const CChainParams& params)
3696+
{
3697+
// TODO: merge with ConnectBlock
3698+
CBlock block;
3699+
if (!ReadBlockFromDisk(block, pindex, params.GetConsensus())) {
3700+
return error("ReplayBlock(): ReadBlockFromDisk failed at %d, hash=%s", pindex->nHeight, pindex->GetBlockHash().ToString());
3701+
}
3702+
3703+
for (const CTransactionRef& tx : block.vtx) {
3704+
if (!tx->IsCoinBase()) {
3705+
for (const CTxIn &txin : tx->vin) {
3706+
inputs.ModifyCoins(txin.prevout.hash)->Spend(txin.prevout.n);
3707+
}
3708+
}
3709+
// We cannot use ModifyNewCoins here, as the entry may exist in the cache already.
3710+
inputs.ModifyCoins(tx->GetHash())->FromTx(*tx, pindex->nHeight);
3711+
}
3712+
return true;
3713+
}
3714+
3715+
/** Unapply the effects of a block on the utxo cache, ignoring that it may already have been applied. */
3716+
static bool RollbackBlock(const CBlockIndex* pindex, CCoinsViewCache& inputs, const CChainParams& params)
3717+
{
3718+
// TODO: Merge with DisconnectBlock
3719+
CBlock block;
3720+
if (!ReadBlockFromDisk(block, pindex, params.GetConsensus())) {
3721+
return error("RollbackBlock(): ReadBlockFromDisk failed at %d, hash=%s", pindex->nHeight, pindex->GetBlockHash().ToString());
3722+
}
3723+
3724+
CBlockUndo blockUndo;
3725+
CDiskBlockPos pos = pindex->GetUndoPos();
3726+
if (pos.IsNull())
3727+
return error("RollbackBlock(): no undo data available");
3728+
if (!UndoReadFromDisk(blockUndo, pos, pindex->pprev->GetBlockHash()))
3729+
return error("RollbackBlock(): failure reading undo data");
3730+
3731+
if (blockUndo.vtxundo.size() + 1 != block.vtx.size())
3732+
return error("RollbackBlock(): block and undo data inconsistent");
3733+
3734+
for (int i = block.vtx.size() - 1; i >= 0; --i) {
3735+
const CTransaction& tx = *block.vtx[i];
3736+
inputs.ModifyCoins(tx.GetHash())->Clear();
3737+
if (i != 0) {
3738+
CTxUndo &txundo = blockUndo.vtxundo[i - 1];
3739+
if (txundo.vprevout.size() != tx.vin.size()) {
3740+
return error("RollbackBlock(): block and undo data inconsistent");
3741+
}
3742+
for (size_t n = 0; n < txundo.vprevout.size(); ++n) {
3743+
ApplyTxInUndo(txundo.vprevout[n], inputs, COutPoint(tx.GetHash(), n)); // Ignore return value
3744+
}
3745+
}
3746+
}
3747+
return true;
3748+
}
3749+
3750+
3751+
bool ReplayBlocks(const CChainParams& params, CCoinsView* view)
3752+
{
3753+
LOCK(cs_main);
3754+
3755+
CCoinsViewCache cache(view);
3756+
3757+
uint256 hashBest = view->GetBestBlock();
3758+
if (!hashBest.IsNull()) return true;
3759+
std::vector<uint256> hashHeads = view->GetHeadBlocks();
3760+
if (hashHeads.empty()) return true;
3761+
3762+
uiInterface.ShowProgress(_("Replaying blocks..."), 0);
3763+
LogPrintf("Replaying blocks\n");
3764+
3765+
std::vector<const CBlockIndex*> pindexHeads;
3766+
const CBlockIndex* pindexFork = nullptr;
3767+
bool fGenesis = false;
3768+
3769+
// Find last common ancestor of all heads.
3770+
for (const uint256& hash : hashHeads) {
3771+
if (hash.IsNull()) {
3772+
fGenesis = true;
3773+
continue;
3774+
}
3775+
auto it = mapBlockIndex.find(hash);
3776+
if (it == mapBlockIndex.end()) {
3777+
return error("ReplayBlocks(): chainstate boundaries not in block index");
3778+
}
3779+
pindexHeads.push_back(it->second);
3780+
pindexFork = pindexFork ? LastCommonAncestor(pindexFork, it->second) : it->second;
3781+
}
3782+
3783+
// Build a set of all blocks to rollback (ignoring the branch with the new tip, pindexHeads[0]).
3784+
std::set<const CBlockIndex*, CBlockIndexWorkComparator> vpindexRollback;
3785+
for (size_t i = 1; i < pindexHeads.size(); ++i) {
3786+
const CBlockIndex *pindexHead = pindexHeads[i];
3787+
while (fGenesis ? pindexHead != nullptr : pindexHead != pindexFork) {
3788+
vpindexRollback.insert(pindexHead);
3789+
pindexHead = pindexHead->pprev;
3790+
}
3791+
}
3792+
3793+
// Rollback all of those in order of decreasing work, deduplicated.
3794+
for (auto itRollback = vpindexRollback.rbegin(); itRollback != vpindexRollback.rend(); ++itRollback) {
3795+
LogPrintf("Rolling backing %s (%i)\n", (*itRollback)->GetBlockHash().ToString(), (*itRollback)->nHeight);
3796+
if (!RollbackBlock(*itRollback, cache, params)) return false;
3797+
}
3798+
3799+
// Roll forward from the forking point to the new tip.
3800+
int nForkHeight = fGenesis ? 1 : pindexFork->nHeight;
3801+
for (int nHeight = nForkHeight + 1; nHeight <= pindexHeads[0]->nHeight; ++nHeight) {
3802+
LogPrintf("Rolling forward %s (%i)\n", pindexHeads[0]->GetAncestor(nHeight)->nHeight, nHeight);
3803+
if (!RollforwardBlock(pindexHeads[0]->GetAncestor(nHeight), cache, params)) return false;
3804+
}
3805+
3806+
cache.SetBestBlock(pindexHeads[0]->GetBlockHash());
3807+
cache.Flush();
3808+
uiInterface.ShowProgress("", 100);
3809+
return true;
3810+
}
3811+
36893812
bool RewindBlockIndex(const CChainParams& params)
36903813
{
36913814
LOCK(cs_main);

src/validation.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -257,6 +257,8 @@ bool LoadExternalBlockFile(const CChainParams& chainparams, FILE* fileIn, CDiskB
257257
bool InitBlockIndex(const CChainParams& chainparams);
258258
/** Load the block tree and coins database from disk */
259259
bool LoadBlockIndex(const CChainParams& chainparams);
260+
/** Update the chain tip based on database information. */
261+
void LoadChainTip(const CChainParams& chainparams);
260262
/** Unload database information */
261263
void UnloadBlockIndex();
262264
/** Run an instance of the script checking thread */
@@ -518,6 +520,9 @@ class CVerifyDB {
518520
bool VerifyDB(const CChainParams& chainparams, CCoinsView *coinsview, int nCheckLevel, int nCheckDepth);
519521
};
520522

523+
/** Replay blocks that aren't fully applied to the database. */
524+
bool ReplayBlocks(const CChainParams& params, CCoinsView* view);
525+
521526
/** Find the last common block between the parameter chain and a locator. */
522527
CBlockIndex* FindForkInGlobalIndex(const CChain& chain, const CBlockLocator& locator);
523528

0 commit comments

Comments
 (0)