Skip to content

Commit 51ce901

Browse files
committed
Improve chainstate/blockindex disk writing policy
There are 3 pieces of data that are maintained on disk. The actual block and undo data, the block index (which can refer to positions on disk), and the chainstate (which refers to the best block hash). Earlier, there was no guarantee that blocks were written to disk before block index entries referring to them were written. This commit introduces dirty flags for block index data, and delays writing entries until the actual block data is flushed. With this stricter ordering in writes, it is now safe to not always flush after every block, so there is no need for the IsInitialBlockDownload() check there - instead we just write whenever enough time has passed or the cache size grows too large. Also updating the wallet's best known block is delayed until this is done, otherwise the wallet may end up referring to an unknown block. In addition, only do a write inside the block processing loop if necessary (because of cache size exceeded). Otherwise, move the writing to a point after processing is done, after relaying.
1 parent f24bcce commit 51ce901

File tree

4 files changed

+68
-55
lines changed

4 files changed

+68
-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: 60 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,61 @@ 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+
/**
1761+
* Update the on-disk chain state.
1762+
* The caches and indexes are flushed if either they're too large, forceWrite is set, or
1763+
* fast is not set and it's been a while since the last write.
1764+
*/
1765+
bool static FlushStateToDisk(CValidationState &state, bool fast = false, bool forceWrite = false) {
1766+
LOCK(cs_main);
17641767
static int64_t nLastWrite = 0;
1765-
if (forceWrite || pcoinsTip->GetCacheSize() > nCoinCacheSize || (!IsInitialBlockDownload() && GetTimeMicros() > nLastWrite + 600*1000000)) {
1768+
if (forceWrite || pcoinsTip->GetCacheSize() > nCoinCacheSize ||
1769+
(!fast && GetTimeMicros() > nLastWrite + DATABASE_WRITE_INTERVAL * 1000000)) {
17661770
// Typical CCoins structures on disk are around 100 bytes in size.
17671771
// Pushing a new one to the database can cause it to be written
17681772
// twice (once in the log, and once in the tables). This is already
17691773
// an overestimation, as most will delete an existing entry or
17701774
// overwrite one. Still, use a conservative safety factor of 2.
17711775
if (!CheckDiskSpace(100 * 2 * 2 * pcoinsTip->GetCacheSize()))
17721776
return state.Error("out of disk space");
1777+
// First make sure all block and undo data is flushed to disk.
17731778
FlushBlockFile();
1779+
// Then update all block file information (which may refer to block and undo files).
1780+
bool fileschanged = false;
1781+
for (set<int>::iterator it = setDirtyFileInfo.begin(); it != setDirtyFileInfo.end(); ) {
1782+
if (!pblocktree->WriteBlockFileInfo(*it, vinfoBlockFile[*it])) {
1783+
return state.Abort("Failed to write to block index");
1784+
}
1785+
fileschanged = true;
1786+
setDirtyFileInfo.erase(it++);
1787+
}
1788+
if (fileschanged && !pblocktree->WriteLastBlockFile(nLastBlockFile)) {
1789+
return state.Abort("Failed to write to block index");
1790+
}
1791+
for (set<CBlockIndex*>::iterator it = setDirtyBlockIndex.begin(); it != setDirtyBlockIndex.end(); ) {
1792+
if (!pblocktree->WriteBlockIndex(CDiskBlockIndex(*it))) {
1793+
return state.Abort("Failed to write to block index");
1794+
}
1795+
setDirtyBlockIndex.erase(it++);
1796+
}
17741797
pblocktree->Sync();
1798+
// Finally flush the chainstate (which may refer to block index entries).
17751799
if (!pcoinsTip->Flush())
17761800
return state.Abort("Failed to write to coin database");
1801+
// Update best block in wallet (so we can detect restored wallets).
1802+
if (forceWrite || !fast) {
1803+
g_signals.SetBestChain(chainActive.GetLocator());
1804+
}
17771805
nLastWrite = GetTimeMicros();
17781806
}
17791807
return true;
17801808
}
17811809

1810+
void FlushStateToDisk() {
1811+
CValidationState state;
1812+
FlushStateToDisk(state, false, true);
1813+
}
1814+
17821815
// Update chainActive and related internal data structures.
17831816
void static UpdateTip(CBlockIndex *pindexNew) {
17841817
chainActive.SetTip(pindexNew);
@@ -1837,7 +1870,7 @@ bool static DisconnectTip(CValidationState &state) {
18371870
}
18381871
LogPrint("bench", "- Disconnect block: %.2fms\n", (GetTimeMicros() - nStart) * 0.001);
18391872
// Write the chain state to disk, if necessary.
1840-
if (!WriteChainState(state))
1873+
if (!FlushStateToDisk(state, true))
18411874
return false;
18421875
// Resurrect mempool transactions from the disconnected block.
18431876
BOOST_FOREACH(const CTransaction &tx, block.vtx) {
@@ -1900,7 +1933,7 @@ bool static ConnectTip(CValidationState &state, CBlockIndex *pindexNew, CBlock *
19001933
int64_t nTime4 = GetTimeMicros(); nTimeFlush += nTime4 - nTime3;
19011934
LogPrint("bench", " - Flush: %.2fms [%.2fs]\n", (nTime4 - nTime3) * 0.001, nTimeFlush * 0.000001);
19021935
// Write the chain state to disk, if necessary.
1903-
if (!WriteChainState(state))
1936+
if (!FlushStateToDisk(state, true))
19041937
return false;
19051938
int64_t nTime5 = GetTimeMicros(); nTimeChainState += nTime5 - nTime4;
19061939
LogPrint("bench", " - Writing chainstate: %.2fms [%.2fs]\n", (nTime5 - nTime4) * 0.001, nTimeChainState * 0.000001);
@@ -1919,10 +1952,6 @@ bool static ConnectTip(CValidationState &state, CBlockIndex *pindexNew, CBlock *
19191952
BOOST_FOREACH(const CTransaction &tx, pblock->vtx) {
19201953
SyncWithWallets(tx, pblock);
19211954
}
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());
19261955

19271956
int64_t nTime6 = GetTimeMicros(); nTimePostConnect += nTime6 - nTime5; nTimeTotal += nTime6 - nTime1;
19281957
LogPrint("bench", " - Connect postprocess: %.2fms [%.2fs]\n", (nTime6 - nTime5) * 0.001, nTimePostConnect * 0.000001);
@@ -2043,9 +2072,6 @@ static bool ActivateBestChainStep(CValidationState &state, CBlockIndex *pindexMo
20432072
else
20442073
CheckForkWarningConditions();
20452074

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

@@ -2086,11 +2112,16 @@ bool ActivateBestChain(CValidationState &state, CBlock *pblock) {
20862112
if (chainActive.Height() > (pnode->nStartingHeight != -1 ? pnode->nStartingHeight - 2000 : nBlockEstimate))
20872113
pnode->PushInventory(CInv(MSG_BLOCK, hashNewTip));
20882114
}
2089-
2115+
// Notify external listeners about the new tip.
20902116
uiInterface.NotifyBlockTip(hashNewTip);
20912117
}
20922118
} while(pindexMostWork != chainActive.Tip());
20932119

2120+
// Write changes periodically to disk, after relay.
2121+
if (!FlushStateToDisk(state)) {
2122+
return false;
2123+
}
2124+
20942125
return true;
20952126
}
20962127

@@ -2123,8 +2154,7 @@ CBlockIndex* AddToBlockIndex(const CBlockHeader& block)
21232154
if (pindexBestHeader == NULL || pindexBestHeader->nChainWork < pindexNew->nChainWork)
21242155
pindexBestHeader = pindexNew;
21252156

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

21292159
return pindexNew;
21302160
}
@@ -2143,6 +2173,7 @@ bool ReceivedBlockTransactions(const CBlock &block, CValidationState& state, CBl
21432173
LOCK(cs_nBlockSequenceId);
21442174
pindexNew->nSequenceId = nBlockSequenceId++;
21452175
}
2176+
setDirtyBlockIndex.insert(pindexNew);
21462177

21472178
if (pindexNew->pprev == NULL || pindexNew->pprev->nChainTx) {
21482179
// If pindexNew is the genesis block or all parents are BLOCK_VALID_TRANSACTIONS.
@@ -2162,24 +2193,18 @@ bool ReceivedBlockTransactions(const CBlock &block, CValidationState& state, CBl
21622193
range.first++;
21632194
mapBlocksUnlinked.erase(it);
21642195
}
2165-
if (!pblocktree->WriteBlockIndex(CDiskBlockIndex(pindex)))
2166-
return state.Abort("Failed to write block index");
21672196
}
21682197
} else {
21692198
if (pindexNew->pprev && pindexNew->pprev->IsValid(BLOCK_VALID_TREE)) {
21702199
mapBlocksUnlinked.insert(std::make_pair(pindexNew->pprev, pindexNew));
21712200
}
2172-
if (!pblocktree->WriteBlockIndex(CDiskBlockIndex(pindexNew)))
2173-
return state.Abort("Failed to write block index");
21742201
}
21752202

21762203
return true;
21772204
}
21782205

21792206
bool FindBlockPos(CValidationState &state, CDiskBlockPos &pos, unsigned int nAddSize, unsigned int nHeight, uint64_t nTime, bool fKnown = false)
21802207
{
2181-
bool fUpdatedLast = false;
2182-
21832208
LOCK(cs_LastBlockFile);
21842209

21852210
unsigned int nFile = fKnown ? pos.nFile : nLastBlockFile;
@@ -2195,7 +2220,6 @@ bool FindBlockPos(CValidationState &state, CDiskBlockPos &pos, unsigned int nAdd
21952220
if (vinfoBlockFile.size() <= nFile) {
21962221
vinfoBlockFile.resize(nFile + 1);
21972222
}
2198-
fUpdatedLast = true;
21992223
}
22002224
pos.nFile = nFile;
22012225
pos.nPos = vinfoBlockFile[nFile].nSize;
@@ -2222,11 +2246,7 @@ bool FindBlockPos(CValidationState &state, CDiskBlockPos &pos, unsigned int nAdd
22222246
}
22232247
}
22242248

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

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

22462264
unsigned int nOldChunks = (pos.nPos + UNDOFILE_CHUNK_SIZE - 1) / UNDOFILE_CHUNK_SIZE;
22472265
unsigned int nNewChunks = (nNewSize + UNDOFILE_CHUNK_SIZE - 1) / UNDOFILE_CHUNK_SIZE;
@@ -2462,6 +2480,7 @@ bool AcceptBlock(CBlock& block, CValidationState& state, CBlockIndex** ppindex,
24622480
if ((!CheckBlock(block, state)) || !ContextualCheckBlock(block, state, pindex->pprev)) {
24632481
if (state.IsInvalid() && !state.CorruptionPossible()) {
24642482
pindex->nStatus |= BLOCK_FAILED_VALID;
2483+
setDirtyBlockIndex.insert(pindex);
24652484
}
24662485
return false;
24672486
}
@@ -3070,7 +3089,7 @@ bool InitBlockIndex() {
30703089
if (!ActivateBestChain(state, &block))
30713090
return error("LoadBlockIndex() : genesis block cannot be activated");
30723091
// Force a chainstate write so that when we VerifyDB in a moment, it doesnt check stale data
3073-
return WriteChainState(state, true);
3092+
return FlushStateToDisk(state, false, true);
30743093
} catch(std::runtime_error &e) {
30753094
return error("LoadBlockIndex() : failed to initialize block database: %s", e.what());
30763095
}
@@ -4641,11 +4660,6 @@ bool CBlockUndo::WriteToDisk(CDiskBlockPos &pos, const uint256 &hashBlock)
46414660
hasher << *this;
46424661
fileout << hasher.GetHash();
46434662

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

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)