Skip to content

Commit d4e551a

Browse files
committed
Merge #10148: Use non-atomic flushing with block replay
176c021 [qa] Test non-atomic chainstate writes (Suhas Daftuar) d6af06d Dont create pcoinsTip until after ReplayBlocks. (Matt Corallo) eaca1b7 Random db flush crash simulator (Pieter Wuille) 0580ee0 Adapt memory usage estimation for flushing (Pieter Wuille) 013a56a Non-atomic flushing using the blockchain as replay journal (Pieter Wuille) b3a279c [MOVEONLY] Move LastCommonAncestor to chain (Pieter Wuille) Tree-SHA512: 47ccc62303f9075c44d2a914be75bd6969ff881a857a2ff1227f05ec7def6f4c71c46680c5a28cb150c814999526797dc05cf2701fde1369c06169f46eccddee
2 parents 416af3e + 176c021 commit d4e551a

File tree

14 files changed

+484
-43
lines changed

14 files changed

+484
-43
lines changed

contrib/devtools/check-doc.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@
2121
REGEX_ARG = re.compile(r'(?:map(?:Multi)?Args(?:\.count\(|\[)|Get(?:Bool)?Arg\()\"(\-[^\"]+?)\"')
2222
REGEX_DOC = re.compile(r'HelpMessageOpt\(\"(\-[^\"=]+?)(?:=|\")')
2323
# list unsupported, deprecated and duplicate args as they need no documentation
24-
SET_DOC_OPTIONAL = set(['-rpcssl', '-benchmark', '-h', '-help', '-socks', '-tor', '-debugnet', '-whitelistalwaysrelay', '-prematurewitness', '-walletprematurewitness', '-promiscuousmempoolflags', '-blockminsize'])
24+
SET_DOC_OPTIONAL = set(['-rpcssl', '-benchmark', '-h', '-help', '-socks', '-tor', '-debugnet', '-whitelistalwaysrelay', '-prematurewitness', '-walletprematurewitness', '-promiscuousmempoolflags', '-blockminsize', '-dbcrashratio'])
2525

2626
def main():
2727
used = check_output(CMD_GREP_ARGS, shell=True)

src/chain.cpp

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -148,3 +148,22 @@ int64_t GetBlockProofEquivalentTime(const CBlockIndex& to, const CBlockIndex& fr
148148
}
149149
return sign * r.GetLow64();
150150
}
151+
152+
/** Find the last common ancestor two blocks have.
153+
* Both pa and pb must be non-NULL. */
154+
const CBlockIndex* LastCommonAncestor(const CBlockIndex* pa, const CBlockIndex* pb) {
155+
if (pa->nHeight > pb->nHeight) {
156+
pa = pa->GetAncestor(pb->nHeight);
157+
} else if (pb->nHeight > pa->nHeight) {
158+
pb = pb->GetAncestor(pa->nHeight);
159+
}
160+
161+
while (pa != pb && pa && pb) {
162+
pa = pa->pprev;
163+
pb = pb->pprev;
164+
}
165+
166+
// Eventually all chain branches meet at the genesis block.
167+
assert(pa == pb);
168+
return pa;
169+
}

src/chain.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -362,6 +362,9 @@ class CBlockIndex
362362
arith_uint256 GetBlockProof(const CBlockIndex& block);
363363
/** Return the time it would take to redo the work difference between from and to, assuming the current hashrate corresponds to the difficulty at tip, in seconds. */
364364
int64_t GetBlockProofEquivalentTime(const CBlockIndex& to, const CBlockIndex& from, const CBlockIndex& tip, const Consensus::Params&);
365+
/** Find the forking point between two chain tips. */
366+
const CBlockIndex* LastCommonAncestor(const CBlockIndex* pa, const CBlockIndex* pb);
367+
365368

366369
/** Used to marshal pointers into hashes for db storage. */
367370
class CDiskBlockIndex : public CBlockIndex

src/coins.cpp

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212

1313
bool CCoinsView::GetCoin(const COutPoint &outpoint, Coin &coin) const { return false; }
1414
uint256 CCoinsView::GetBestBlock() const { return uint256(); }
15+
std::vector<uint256> CCoinsView::GetHeadBlocks() const { return std::vector<uint256>(); }
1516
bool CCoinsView::BatchWrite(CCoinsMap &mapCoins, const uint256 &hashBlock) { return false; }
1617
CCoinsViewCursor *CCoinsView::Cursor() const { return 0; }
1718

@@ -25,6 +26,7 @@ CCoinsViewBacked::CCoinsViewBacked(CCoinsView *viewIn) : base(viewIn) { }
2526
bool CCoinsViewBacked::GetCoin(const COutPoint &outpoint, Coin &coin) const { return base->GetCoin(outpoint, coin); }
2627
bool CCoinsViewBacked::HaveCoin(const COutPoint &outpoint) const { return base->HaveCoin(outpoint); }
2728
uint256 CCoinsViewBacked::GetBestBlock() const { return base->GetBestBlock(); }
29+
std::vector<uint256> CCoinsViewBacked::GetHeadBlocks() const { return base->GetHeadBlocks(); }
2830
void CCoinsViewBacked::SetBackend(CCoinsView &viewIn) { base = &viewIn; }
2931
bool CCoinsViewBacked::BatchWrite(CCoinsMap &mapCoins, const uint256 &hashBlock) { return base->BatchWrite(mapCoins, hashBlock); }
3032
CCoinsViewCursor *CCoinsViewBacked::Cursor() const { return base->Cursor(); }
@@ -85,13 +87,14 @@ void CCoinsViewCache::AddCoin(const COutPoint &outpoint, Coin&& coin, bool possi
8587
cachedCoinsUsage += it->second.coin.DynamicMemoryUsage();
8688
}
8789

88-
void AddCoins(CCoinsViewCache& cache, const CTransaction &tx, int nHeight) {
90+
void AddCoins(CCoinsViewCache& cache, const CTransaction &tx, int nHeight, bool check) {
8991
bool fCoinbase = tx.IsCoinBase();
9092
const uint256& txid = tx.GetHash();
9193
for (size_t i = 0; i < tx.vout.size(); ++i) {
92-
// Pass fCoinbase as the possible_overwrite flag to AddCoin, in order to correctly
94+
bool overwrite = check ? cache.HaveCoin(COutPoint(txid, i)) : fCoinbase;
95+
// Always set the possible_overwrite flag to AddCoin for coinbase txn, in order to correctly
9396
// deal with the pre-BIP30 occurrences of duplicate coinbase transactions.
94-
cache.AddCoin(COutPoint(txid, i), Coin(tx.vout[i], nHeight, fCoinbase), fCoinbase);
97+
cache.AddCoin(COutPoint(txid, i), Coin(tx.vout[i], nHeight, fCoinbase), overwrite);
9598
}
9699
}
97100

src/coins.h

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -157,6 +157,12 @@ class CCoinsView
157157
//! Retrieve the block hash whose state this CCoinsView currently represents
158158
virtual uint256 GetBestBlock() const;
159159

160+
//! Retrieve the range of blocks that may have been only partially written.
161+
//! If the database is in a consistent state, the result is the empty vector.
162+
//! Otherwise, a two-element vector is returned consisting of the new and
163+
//! the old block hash, in that order.
164+
virtual std::vector<uint256> GetHeadBlocks() const;
165+
160166
//! Do a bulk modification (multiple Coin changes + BestBlock change).
161167
//! The passed mapCoins can be modified.
162168
virtual bool BatchWrite(CCoinsMap &mapCoins, const uint256 &hashBlock);
@@ -183,6 +189,7 @@ class CCoinsViewBacked : public CCoinsView
183189
bool GetCoin(const COutPoint &outpoint, Coin &coin) const override;
184190
bool HaveCoin(const COutPoint &outpoint) const override;
185191
uint256 GetBestBlock() const override;
192+
std::vector<uint256> GetHeadBlocks() const override;
186193
void SetBackend(CCoinsView &viewIn);
187194
bool BatchWrite(CCoinsMap &mapCoins, const uint256 &hashBlock) override;
188195
CCoinsViewCursor *Cursor() const override;
@@ -291,10 +298,12 @@ class CCoinsViewCache : public CCoinsViewBacked
291298
};
292299

293300
//! Utility function to add all of a transaction's outputs to a cache.
294-
// It assumes that overwrites are only possible for coinbase transactions,
301+
// When check is false, this assumes that overwrites are only possible for coinbase transactions.
302+
// When check is true, the underlying view may be queried to determine whether an addition is
303+
// an overwrite.
295304
// TODO: pass in a boolean to limit these possible overwrites to known
296305
// (pre-BIP34) cases.
297-
void AddCoins(CCoinsViewCache& cache, const CTransaction& tx, int nHeight);
306+
void AddCoins(CCoinsViewCache& cache, const CTransaction& tx, int nHeight, bool check = false);
298307

299308
//! Utility function to find any unspent output with a given txid.
300309
const Coin& AccessByTxid(const CCoinsViewCache& cache, const uint256& txid);

src/init.cpp

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -336,6 +336,9 @@ std::string HelpMessage(HelpMessageMode mode)
336336
#endif
337337
}
338338
strUsage += HelpMessageOpt("-datadir=<dir>", _("Specify data directory"));
339+
if (showDebug) {
340+
strUsage += HelpMessageOpt("-dbbatchsize", strprintf("Maximum database write batch size in bytes (default: %u)", nDefaultDbBatchSize));
341+
}
339342
strUsage += HelpMessageOpt("-dbcache=<n>", strprintf(_("Set database cache size in megabytes (%d to %d, default: %d)"), nMinDbCache, nMaxDbCache, nDefaultDbCache));
340343
if (showDebug)
341344
strUsage += HelpMessageOpt("-feefilter", strprintf("Tell other nodes to filter invs to us by our mempool min fee (default: %u)", DEFAULT_FEEFILTER));
@@ -1373,7 +1376,6 @@ bool AppInitMain(boost::thread_group& threadGroup, CScheduler& scheduler)
13731376
pblocktree = new CBlockTreeDB(nBlockTreeDBCache, false, fReindex);
13741377
pcoinsdbview = new CCoinsViewDB(nCoinDBCache, false, fReindex || fReindexChainState);
13751378
pcoinscatcher = new CCoinsViewErrorCatcher(pcoinsdbview);
1376-
pcoinsTip = new CCoinsViewCache(pcoinscatcher);
13771379

13781380
if (fReindex) {
13791381
pblocktree->WriteReindexing(true);
@@ -1417,6 +1419,13 @@ bool AppInitMain(boost::thread_group& threadGroup, CScheduler& scheduler)
14171419
break;
14181420
}
14191421

1422+
if (!ReplayBlocks(chainparams, pcoinsdbview)) {
1423+
strLoadError = _("Unable to replay blocks. You will need to rebuild the database using -reindex-chainstate.");
1424+
break;
1425+
}
1426+
pcoinsTip = new CCoinsViewCache(pcoinscatcher);
1427+
LoadChainTip(chainparams);
1428+
14201429
if (!fReindex && chainActive.Tip() != NULL) {
14211430
uiInterface.InitMessage(_("Rewinding blocks..."));
14221431
if (!RewindBlockIndex(chainparams)) {

src/net_processing.cpp

Lines changed: 0 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -452,25 +452,6 @@ bool PeerHasHeader(CNodeState *state, const CBlockIndex *pindex)
452452
return false;
453453
}
454454

455-
/** Find the last common ancestor two blocks have.
456-
* Both pa and pb must be non-NULL. */
457-
const CBlockIndex* LastCommonAncestor(const CBlockIndex* pa, const CBlockIndex* pb) {
458-
if (pa->nHeight > pb->nHeight) {
459-
pa = pa->GetAncestor(pb->nHeight);
460-
} else if (pb->nHeight > pa->nHeight) {
461-
pb = pb->GetAncestor(pa->nHeight);
462-
}
463-
464-
while (pa != pb && pa && pb) {
465-
pa = pa->pprev;
466-
pb = pb->pprev;
467-
}
468-
469-
// Eventually all chain branches meet at the genesis block.
470-
assert(pa == pb);
471-
return pa;
472-
}
473-
474455
/** Update pindexLastCommonBlock and add not-in-flight missing successors to vBlocks, until it has
475456
* at most count entries. */
476457
void FindNextBlocksToDownload(NodeId nodeid, unsigned int count, std::vector<const CBlockIndex*>& vBlocks, NodeId& nodeStaller, const Consensus::Params& consensusParams) {

src/txdb.cpp

Lines changed: 49 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,10 @@
77

88
#include "chainparams.h"
99
#include "hash.h"
10+
#include "random.h"
1011
#include "pow.h"
1112
#include "uint256.h"
13+
#include "util.h"
1214

1315
#include <stdint.h>
1416

@@ -21,6 +23,7 @@ static const char DB_TXINDEX = 't';
2123
static const char DB_BLOCK_INDEX = 'b';
2224

2325
static const char DB_BEST_BLOCK = 'B';
26+
static const char DB_HEAD_BLOCKS = 'H';
2427
static const char DB_FLAG = 'F';
2528
static const char DB_REINDEX_FLAG = 'R';
2629
static const char DB_LAST_BLOCK = 'l';
@@ -68,10 +71,39 @@ uint256 CCoinsViewDB::GetBestBlock() const {
6871
return hashBestChain;
6972
}
7073

74+
std::vector<uint256> CCoinsViewDB::GetHeadBlocks() const {
75+
std::vector<uint256> vhashHeadBlocks;
76+
if (!db.Read(DB_HEAD_BLOCKS, vhashHeadBlocks)) {
77+
return std::vector<uint256>();
78+
}
79+
return vhashHeadBlocks;
80+
}
81+
7182
bool CCoinsViewDB::BatchWrite(CCoinsMap &mapCoins, const uint256 &hashBlock) {
7283
CDBBatch batch(db);
7384
size_t count = 0;
7485
size_t changed = 0;
86+
size_t batch_size = (size_t)GetArg("-dbbatchsize", nDefaultDbBatchSize);
87+
int crash_simulate = GetArg("-dbcrashratio", 0);
88+
assert(!hashBlock.IsNull());
89+
90+
uint256 old_tip = GetBestBlock();
91+
if (old_tip.IsNull()) {
92+
// We may be in the middle of replaying.
93+
std::vector<uint256> old_heads = GetHeadBlocks();
94+
if (old_heads.size() == 2) {
95+
assert(old_heads[0] == hashBlock);
96+
old_tip = old_heads[1];
97+
}
98+
}
99+
100+
// In the first batch, mark the database as being in the middle of a
101+
// transition from old_tip to hashBlock.
102+
// A vector is used for future extensibility, as we may want to support
103+
// interrupting after partial writes from multiple independent reorgs.
104+
batch.Erase(DB_BEST_BLOCK);
105+
batch.Write(DB_HEAD_BLOCKS, std::vector<uint256>{hashBlock, old_tip});
106+
75107
for (CCoinsMap::iterator it = mapCoins.begin(); it != mapCoins.end();) {
76108
if (it->second.flags & CCoinsCacheEntry::DIRTY) {
77109
CoinEntry entry(&it->first);
@@ -84,10 +116,25 @@ bool CCoinsViewDB::BatchWrite(CCoinsMap &mapCoins, const uint256 &hashBlock) {
84116
count++;
85117
CCoinsMap::iterator itOld = it++;
86118
mapCoins.erase(itOld);
119+
if (batch.SizeEstimate() > batch_size) {
120+
LogPrint(BCLog::COINDB, "Writing partial batch of %.2f MiB\n", batch.SizeEstimate() * (1.0 / 1048576.0));
121+
db.WriteBatch(batch);
122+
batch.Clear();
123+
if (crash_simulate) {
124+
static FastRandomContext rng;
125+
if (rng.randrange(crash_simulate) == 0) {
126+
LogPrintf("Simulating a crash. Goodbye.\n");
127+
_Exit(0);
128+
}
129+
}
130+
}
87131
}
88-
if (!hashBlock.IsNull())
89-
batch.Write(DB_BEST_BLOCK, hashBlock);
90132

133+
// In the last batch, mark the database as consistent with hashBlock again.
134+
batch.Erase(DB_HEAD_BLOCKS);
135+
batch.Write(DB_BEST_BLOCK, hashBlock);
136+
137+
LogPrint(BCLog::COINDB, "Writing final batch of %.2f MiB\n", batch.SizeEstimate() * (1.0 / 1048576.0));
91138
bool ret = db.WriteBatch(batch);
92139
LogPrint(BCLog::COINDB, "Committed %u changed transaction outputs (out of %u) to coin database...\n", (unsigned int)changed, (unsigned int)count);
93140
return ret;

src/txdb.h

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,12 +19,12 @@ class CBlockIndex;
1919
class CCoinsViewDBCursor;
2020
class uint256;
2121

22-
//! Compensate for extra memory peak (x1.5-x1.9) at flush time.
23-
static constexpr int DB_PEAK_USAGE_FACTOR = 2;
2422
//! No need to periodic flush if at least this much space still available.
25-
static constexpr int MAX_BLOCK_COINSDB_USAGE = 10 * DB_PEAK_USAGE_FACTOR;
23+
static constexpr int MAX_BLOCK_COINSDB_USAGE = 10;
2624
//! -dbcache default (MiB)
2725
static const int64_t nDefaultDbCache = 450;
26+
//! -dbbatchsize default (bytes)
27+
static const int64_t nDefaultDbBatchSize = 16 << 20;
2828
//! max. -dbcache (MiB)
2929
static const int64_t nMaxDbCache = sizeof(void*) > 4 ? 16384 : 1024;
3030
//! min. -dbcache (MiB)
@@ -74,6 +74,7 @@ class CCoinsViewDB : public CCoinsView
7474
bool GetCoin(const COutPoint &outpoint, Coin &coin) const override;
7575
bool HaveCoin(const COutPoint &outpoint) const override;
7676
uint256 GetBestBlock() const override;
77+
std::vector<uint256> GetHeadBlocks() const override;
7778
bool BatchWrite(CCoinsMap &mapCoins, const uint256 &hashBlock) override;
7879
CCoinsViewCursor *Cursor() const override;
7980

0 commit comments

Comments
 (0)