Skip to content

Commit 397b901

Browse files
committed
Merge pull request #5241
a206950 Introduce separate flushing modes (Pieter Wuille) 51ce901 Improve chainstate/blockindex disk writing policy (Pieter Wuille)
2 parents 97bef12 + a206950 commit 397b901

File tree

4 files changed

+75
-55
lines changed

4 files changed

+75
-55
lines changed

src/init.cpp

Lines changed: 3 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -150,14 +150,9 @@ void Shutdown()
150150

151151
{
152152
LOCK(cs_main);
153-
#ifdef ENABLE_WALLET
154-
if (pwalletMain)
155-
pwalletMain->SetBestChain(chainActive.GetLocator());
156-
#endif
157-
if (pblocktree)
158-
pblocktree->Flush();
159-
if (pcoinsTip)
160-
pcoinsTip->Flush();
153+
if (pcoinsTip != NULL) {
154+
FlushStateToDisk();
155+
}
161156
delete pcoinsTip;
162157
pcoinsTip = NULL;
163158
delete pcoinsdbview;

src/main.cpp

Lines changed: 67 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,12 @@ namespace {
130130

131131
// Number of preferrable block download peers.
132132
int nPreferredDownload = 0;
133+
134+
// Dirty block index entries.
135+
set<CBlockIndex*> setDirtyBlockIndex;
136+
137+
// Dirty block file entries.
138+
set<int> setDirtyFileInfo;
133139
} // anon namespace
134140

135141
//////////////////////////////////////////////////////////////////////////////
@@ -1137,11 +1143,6 @@ bool WriteBlockToDisk(CBlock& block, CDiskBlockPos& pos)
11371143
pos.nPos = (unsigned int)fileOutPos;
11381144
fileout << block;
11391145

1140-
// Flush stdio buffers and commit to disk before returning
1141-
fflush(fileout.Get());
1142-
if (!IsInitialBlockDownload())
1143-
FileCommit(fileout.Get());
1144-
11451146
return true;
11461147
}
11471148

@@ -1335,7 +1336,7 @@ void static InvalidBlockFound(CBlockIndex *pindex, const CValidationState &state
13351336
}
13361337
if (!state.CorruptionPossible()) {
13371338
pindex->nStatus |= BLOCK_FAILED_VALID;
1338-
pblocktree->WriteBlockIndex(CDiskBlockIndex(pindex));
1339+
setDirtyBlockIndex.insert(pindex);
13391340
setBlockIndexCandidates.erase(pindex);
13401341
InvalidChainFound(pindex);
13411342
}
@@ -1732,10 +1733,7 @@ bool ConnectBlock(const CBlock& block, CValidationState& state, CBlockIndex* pin
17321733
}
17331734

17341735
pindex->RaiseValidity(BLOCK_VALID_SCRIPTS);
1735-
1736-
CDiskBlockIndex blockindex(pindex);
1737-
if (!pblocktree->WriteBlockIndex(blockindex))
1738-
return state.Abort("Failed to write block index");
1736+
setDirtyBlockIndex.insert(pindex);
17391737
}
17401738

17411739
if (fTxIndex)
@@ -1759,26 +1757,68 @@ bool ConnectBlock(const CBlock& block, CValidationState& state, CBlockIndex* pin
17591757
return true;
17601758
}
17611759

1762-
// Update the on-disk chain state.
1763-
bool static WriteChainState(CValidationState &state, bool forceWrite=false) {
1760+
enum FlushStateMode {
1761+
FLUSH_STATE_IF_NEEDED,
1762+
FLUSH_STATE_PERIODIC,
1763+
FLUSH_STATE_ALWAYS
1764+
};
1765+
1766+
/**
1767+
* Update the on-disk chain state.
1768+
* The caches and indexes are flushed if either they're too large, forceWrite is set, or
1769+
* fast is not set and it's been a while since the last write.
1770+
*/
1771+
bool static FlushStateToDisk(CValidationState &state, FlushStateMode mode) {
1772+
LOCK(cs_main);
17641773
static int64_t nLastWrite = 0;
1765-
if (forceWrite || pcoinsTip->GetCacheSize() > nCoinCacheSize || (!IsInitialBlockDownload() && GetTimeMicros() > nLastWrite + 600*1000000)) {
1774+
if ((mode == FLUSH_STATE_ALWAYS) ||
1775+
((mode == FLUSH_STATE_PERIODIC || mode == FLUSH_STATE_IF_NEEDED) && pcoinsTip->GetCacheSize() > nCoinCacheSize) ||
1776+
(mode == FLUSH_STATE_PERIODIC && GetTimeMicros() > nLastWrite + DATABASE_WRITE_INTERVAL * 1000000)) {
17661777
// Typical CCoins structures on disk are around 100 bytes in size.
17671778
// Pushing a new one to the database can cause it to be written
17681779
// twice (once in the log, and once in the tables). This is already
17691780
// an overestimation, as most will delete an existing entry or
17701781
// overwrite one. Still, use a conservative safety factor of 2.
17711782
if (!CheckDiskSpace(100 * 2 * 2 * pcoinsTip->GetCacheSize()))
17721783
return state.Error("out of disk space");
1784+
// First make sure all block and undo data is flushed to disk.
17731785
FlushBlockFile();
1786+
// Then update all block file information (which may refer to block and undo files).
1787+
bool fileschanged = false;
1788+
for (set<int>::iterator it = setDirtyFileInfo.begin(); it != setDirtyFileInfo.end(); ) {
1789+
if (!pblocktree->WriteBlockFileInfo(*it, vinfoBlockFile[*it])) {
1790+
return state.Abort("Failed to write to block index");
1791+
}
1792+
fileschanged = true;
1793+
setDirtyFileInfo.erase(it++);
1794+
}
1795+
if (fileschanged && !pblocktree->WriteLastBlockFile(nLastBlockFile)) {
1796+
return state.Abort("Failed to write to block index");
1797+
}
1798+
for (set<CBlockIndex*>::iterator it = setDirtyBlockIndex.begin(); it != setDirtyBlockIndex.end(); ) {
1799+
if (!pblocktree->WriteBlockIndex(CDiskBlockIndex(*it))) {
1800+
return state.Abort("Failed to write to block index");
1801+
}
1802+
setDirtyBlockIndex.erase(it++);
1803+
}
17741804
pblocktree->Sync();
1805+
// Finally flush the chainstate (which may refer to block index entries).
17751806
if (!pcoinsTip->Flush())
17761807
return state.Abort("Failed to write to coin database");
1808+
// Update best block in wallet (so we can detect restored wallets).
1809+
if (mode != FLUSH_STATE_IF_NEEDED) {
1810+
g_signals.SetBestChain(chainActive.GetLocator());
1811+
}
17771812
nLastWrite = GetTimeMicros();
17781813
}
17791814
return true;
17801815
}
17811816

1817+
void FlushStateToDisk() {
1818+
CValidationState state;
1819+
FlushStateToDisk(state, FLUSH_STATE_ALWAYS);
1820+
}
1821+
17821822
// Update chainActive and related internal data structures.
17831823
void static UpdateTip(CBlockIndex *pindexNew) {
17841824
chainActive.SetTip(pindexNew);
@@ -1837,7 +1877,7 @@ bool static DisconnectTip(CValidationState &state) {
18371877
}
18381878
LogPrint("bench", "- Disconnect block: %.2fms\n", (GetTimeMicros() - nStart) * 0.001);
18391879
// Write the chain state to disk, if necessary.
1840-
if (!WriteChainState(state))
1880+
if (!FlushStateToDisk(state, FLUSH_STATE_IF_NEEDED))
18411881
return false;
18421882
// Resurrect mempool transactions from the disconnected block.
18431883
BOOST_FOREACH(const CTransaction &tx, block.vtx) {
@@ -1900,7 +1940,7 @@ bool static ConnectTip(CValidationState &state, CBlockIndex *pindexNew, CBlock *
19001940
int64_t nTime4 = GetTimeMicros(); nTimeFlush += nTime4 - nTime3;
19011941
LogPrint("bench", " - Flush: %.2fms [%.2fs]\n", (nTime4 - nTime3) * 0.001, nTimeFlush * 0.000001);
19021942
// Write the chain state to disk, if necessary.
1903-
if (!WriteChainState(state))
1943+
if (!FlushStateToDisk(state, FLUSH_STATE_IF_NEEDED))
19041944
return false;
19051945
int64_t nTime5 = GetTimeMicros(); nTimeChainState += nTime5 - nTime4;
19061946
LogPrint("bench", " - Writing chainstate: %.2fms [%.2fs]\n", (nTime5 - nTime4) * 0.001, nTimeChainState * 0.000001);
@@ -1919,10 +1959,6 @@ bool static ConnectTip(CValidationState &state, CBlockIndex *pindexNew, CBlock *
19191959
BOOST_FOREACH(const CTransaction &tx, pblock->vtx) {
19201960
SyncWithWallets(tx, pblock);
19211961
}
1922-
// Update best block in wallet (so we can detect restored wallets)
1923-
// Emit this signal after the SyncWithWallets signals as the wallet relies on that everything up to this point has been synced
1924-
if ((chainActive.Height() % 20160) == 0 || ((chainActive.Height() % 144) == 0 && !IsInitialBlockDownload()))
1925-
g_signals.SetBestChain(chainActive.GetLocator());
19261962

19271963
int64_t nTime6 = GetTimeMicros(); nTimePostConnect += nTime6 - nTime5; nTimeTotal += nTime6 - nTime1;
19281964
LogPrint("bench", " - Connect postprocess: %.2fms [%.2fs]\n", (nTime6 - nTime5) * 0.001, nTimePostConnect * 0.000001);
@@ -2043,9 +2079,6 @@ static bool ActivateBestChainStep(CValidationState &state, CBlockIndex *pindexMo
20432079
else
20442080
CheckForkWarningConditions();
20452081

2046-
if (!pblocktree->Flush())
2047-
return state.Abort("Failed to sync block index");
2048-
20492082
return true;
20502083
}
20512084

@@ -2086,11 +2119,16 @@ bool ActivateBestChain(CValidationState &state, CBlock *pblock) {
20862119
if (chainActive.Height() > (pnode->nStartingHeight != -1 ? pnode->nStartingHeight - 2000 : nBlockEstimate))
20872120
pnode->PushInventory(CInv(MSG_BLOCK, hashNewTip));
20882121
}
2089-
2122+
// Notify external listeners about the new tip.
20902123
uiInterface.NotifyBlockTip(hashNewTip);
20912124
}
20922125
} while(pindexMostWork != chainActive.Tip());
20932126

2127+
// Write changes periodically to disk, after relay.
2128+
if (!FlushStateToDisk(state, FLUSH_STATE_PERIODIC)) {
2129+
return false;
2130+
}
2131+
20942132
return true;
20952133
}
20962134

@@ -2123,8 +2161,7 @@ CBlockIndex* AddToBlockIndex(const CBlockHeader& block)
21232161
if (pindexBestHeader == NULL || pindexBestHeader->nChainWork < pindexNew->nChainWork)
21242162
pindexBestHeader = pindexNew;
21252163

2126-
// Ok if it fails, we'll download the header again next time.
2127-
pblocktree->WriteBlockIndex(CDiskBlockIndex(pindexNew));
2164+
setDirtyBlockIndex.insert(pindexNew);
21282165

21292166
return pindexNew;
21302167
}
@@ -2143,6 +2180,7 @@ bool ReceivedBlockTransactions(const CBlock &block, CValidationState& state, CBl
21432180
LOCK(cs_nBlockSequenceId);
21442181
pindexNew->nSequenceId = nBlockSequenceId++;
21452182
}
2183+
setDirtyBlockIndex.insert(pindexNew);
21462184

21472185
if (pindexNew->pprev == NULL || pindexNew->pprev->nChainTx) {
21482186
// If pindexNew is the genesis block or all parents are BLOCK_VALID_TRANSACTIONS.
@@ -2162,24 +2200,18 @@ bool ReceivedBlockTransactions(const CBlock &block, CValidationState& state, CBl
21622200
range.first++;
21632201
mapBlocksUnlinked.erase(it);
21642202
}
2165-
if (!pblocktree->WriteBlockIndex(CDiskBlockIndex(pindex)))
2166-
return state.Abort("Failed to write block index");
21672203
}
21682204
} else {
21692205
if (pindexNew->pprev && pindexNew->pprev->IsValid(BLOCK_VALID_TREE)) {
21702206
mapBlocksUnlinked.insert(std::make_pair(pindexNew->pprev, pindexNew));
21712207
}
2172-
if (!pblocktree->WriteBlockIndex(CDiskBlockIndex(pindexNew)))
2173-
return state.Abort("Failed to write block index");
21742208
}
21752209

21762210
return true;
21772211
}
21782212

21792213
bool FindBlockPos(CValidationState &state, CDiskBlockPos &pos, unsigned int nAddSize, unsigned int nHeight, uint64_t nTime, bool fKnown = false)
21802214
{
2181-
bool fUpdatedLast = false;
2182-
21832215
LOCK(cs_LastBlockFile);
21842216

21852217
unsigned int nFile = fKnown ? pos.nFile : nLastBlockFile;
@@ -2195,7 +2227,6 @@ bool FindBlockPos(CValidationState &state, CDiskBlockPos &pos, unsigned int nAdd
21952227
if (vinfoBlockFile.size() <= nFile) {
21962228
vinfoBlockFile.resize(nFile + 1);
21972229
}
2198-
fUpdatedLast = true;
21992230
}
22002231
pos.nFile = nFile;
22012232
pos.nPos = vinfoBlockFile[nFile].nSize;
@@ -2222,11 +2253,7 @@ bool FindBlockPos(CValidationState &state, CDiskBlockPos &pos, unsigned int nAdd
22222253
}
22232254
}
22242255

2225-
if (!pblocktree->WriteBlockFileInfo(nLastBlockFile, vinfoBlockFile[nFile]))
2226-
return state.Abort("Failed to write file info");
2227-
if (fUpdatedLast)
2228-
pblocktree->WriteLastBlockFile(nLastBlockFile);
2229-
2256+
setDirtyFileInfo.insert(nFile);
22302257
return true;
22312258
}
22322259

@@ -2239,9 +2266,7 @@ bool FindUndoPos(CValidationState &state, int nFile, CDiskBlockPos &pos, unsigne
22392266
unsigned int nNewSize;
22402267
pos.nPos = vinfoBlockFile[nFile].nUndoSize;
22412268
nNewSize = vinfoBlockFile[nFile].nUndoSize += nAddSize;
2242-
if (!pblocktree->WriteBlockFileInfo(nLastBlockFile, vinfoBlockFile[nLastBlockFile])) {
2243-
return state.Abort("Failed to write block info");
2244-
}
2269+
setDirtyFileInfo.insert(nFile);
22452270

22462271
unsigned int nOldChunks = (pos.nPos + UNDOFILE_CHUNK_SIZE - 1) / UNDOFILE_CHUNK_SIZE;
22472272
unsigned int nNewChunks = (nNewSize + UNDOFILE_CHUNK_SIZE - 1) / UNDOFILE_CHUNK_SIZE;
@@ -2462,6 +2487,7 @@ bool AcceptBlock(CBlock& block, CValidationState& state, CBlockIndex** ppindex,
24622487
if ((!CheckBlock(block, state)) || !ContextualCheckBlock(block, state, pindex->pprev)) {
24632488
if (state.IsInvalid() && !state.CorruptionPossible()) {
24642489
pindex->nStatus |= BLOCK_FAILED_VALID;
2490+
setDirtyBlockIndex.insert(pindex);
24652491
}
24662492
return false;
24672493
}
@@ -3070,7 +3096,7 @@ bool InitBlockIndex() {
30703096
if (!ActivateBestChain(state, &block))
30713097
return error("LoadBlockIndex() : genesis block cannot be activated");
30723098
// Force a chainstate write so that when we VerifyDB in a moment, it doesnt check stale data
3073-
return WriteChainState(state, true);
3099+
return FlushStateToDisk(state, FLUSH_STATE_ALWAYS);
30743100
} catch(std::runtime_error &e) {
30753101
return error("LoadBlockIndex() : failed to initialize block database: %s", e.what());
30763102
}
@@ -4641,11 +4667,6 @@ bool CBlockUndo::WriteToDisk(CDiskBlockPos &pos, const uint256 &hashBlock)
46414667
hasher << *this;
46424668
fileout << hasher.GetHash();
46434669

4644-
// Flush stdio buffers and commit to disk before returning
4645-
fflush(fileout.Get());
4646-
if (!IsInitialBlockDownload())
4647-
FileCommit(fileout.Get());
4648-
46494670
return true;
46504671
}
46514672

src/main.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,8 @@ static const unsigned int MAX_HEADERS_RESULTS = 2000;
9494
* degree of disordering of blocks on disk (which make reindexing and in the future perhaps pruning
9595
* harder). We'll probably want to make this a per-peer adaptive value at some point. */
9696
static const unsigned int BLOCK_DOWNLOAD_WINDOW = 1024;
97+
/** Time to wait (in seconds) between writing blockchain state to disk. */
98+
static const unsigned int DATABASE_WRITE_INTERVAL = 3600;
9799

98100
/** "reject" message codes **/
99101
static const unsigned char REJECT_MALFORMED = 0x01;
@@ -201,6 +203,8 @@ bool AbortNode(const std::string &msg, const std::string &userMessage="");
201203
bool GetNodeStateStats(NodeId nodeid, CNodeStateStats &stats);
202204
/** Increase a node's misbehavior score. */
203205
void Misbehaving(NodeId nodeid, int howmuch);
206+
/** Flush all state, indexes and buffers to disk. */
207+
void FlushStateToDisk();
204208

205209

206210
/** (try to) add transaction to memory pool **/

src/rpcblockchain.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -319,7 +319,7 @@ Value gettxoutsetinfo(const Array& params, bool fHelp)
319319
Object ret;
320320

321321
CCoinsStats stats;
322-
pcoinsTip->Flush();
322+
FlushStateToDisk();
323323
if (pcoinsTip->GetStats(stats)) {
324324
ret.push_back(Pair("height", (int64_t)stats.nHeight));
325325
ret.push_back(Pair("bestblock", stats.hashBlock.GetHex()));

0 commit comments

Comments
 (0)