比特币源码解析(22)
来源:互联网 发布:windows xp vol key 编辑:程序博客网 时间:2024/06/07 06:12
0x01 AppInitMain Step 7: load block chain
计算缓存大小
fReindex = gArgs.GetBoolArg("-reindex", false); bool fReindexChainState = gArgs.GetBoolArg("-reindex-chainstate", false); // cache size calculations int64_t nTotalCache = (gArgs.GetArg("-dbcache", nDefaultDbCache) << 20); nTotalCache = std::max(nTotalCache, nMinDbCache << 20); // total cache cannot be less than nMinDbCache nTotalCache = std::min(nTotalCache, nMaxDbCache << 20); // total cache cannot be greater than nMaxDbcache int64_t nBlockTreeDBCache = nTotalCache / 8; nBlockTreeDBCache = std::min(nBlockTreeDBCache, (gArgs.GetBoolArg("-txindex", DEFAULT_TXINDEX) ? nMaxBlockDBAndTxIndexCache : nMaxBlockDBCache) << 20); nTotalCache -= nBlockTreeDBCache; int64_t nCoinDBCache = std::min(nTotalCache / 2, (nTotalCache / 4) + (1 << 23)); // use 25%-50% of the remainder for disk cache nCoinDBCache = std::min(nCoinDBCache, nMaxCoinsDBCache << 20); // cap total coins db cache nTotalCache -= nCoinDBCache; nCoinCacheUsage = nTotalCache; // the rest goes to in-memory cache int64_t nMempoolSizeMax = gArgs.GetArg("-maxmempool", DEFAULT_MAX_MEMPOOL_SIZE) * 1000000; LogPrintf("Cache configuration:\n"); LogPrintf("* Using %.1fMiB for block index database\n", nBlockTreeDBCache * (1.0 / 1024 / 1024)); LogPrintf("* Using %.1fMiB for chain state database\n", nCoinDBCache * (1.0 / 1024 / 1024)); LogPrintf("* Using %.1fMiB for in-memory UTXO set (plus up to %.1fMiB of unused mempool space)\n", nCoinCacheUsage * (1.0 / 1024 / 1024), nMempoolSizeMax * (1.0 / 1024 / 1024));
-reindex
:从磁盘上的blk*.dat
中重建chain state
和block index
。
-reindex-chainstate
:从当前的区块索引中建立chain state
。
-dbcache
:设置数据库缓存大小,单位为MB,默认值为450.
-txindex
:维护完整的交易索引,主要是被getrawtransaction
这个rpc调用来使用,默认不启用。
-maxmempool
:设置交易内存池的最大大小,单位为MB,默认值为300。
首先从命令行中获取两个参数,这两个重索引默认都是不启用。接下来开始计算缓存的大小,首先是总的缓存大小用nTotalCache
表示,通过-dbcache
参数设置,然后这个值要取在nMinDbCache
和nMaxDbCache
之间。接下来计算nBlockTreeDBCache
和nCoinDBCache
以及nCoinCacheUsage
,并且nTotalCache = nBlockTreeDBCache +nCoinDBCache + nCoinCacheUsage
。
加载区块索引
接下来是一个很长的while
循环语句,这个循环用来,我们一点一点来进行分析。
bool fLoaded = false; while (!fLoaded && !fRequestShutdown) { bool fReset = fReindex; std::string strLoadError; uiInterface.InitMessage(_("Loading block index...")); nStart = GetTimeMillis(); do { try { UnloadBlockIndex(); delete pcoinsTip; delete pcoinsdbview; delete pcoinscatcher; delete pblocktree;
首先设置了一个标记变量fLoaded
表示索引加载是否成功,如果执行完循环体发现此变量还是false
并且没有请求关闭程序的话,那么就再执行一遍。由于此循环体可能不止执行一遍,所以先调用UnloadBlockIndex()
来清除上次循环可能设置的一些变量,这个函数的实现如下,
UnloadBlockIndex
// May NOT be used after any connections are up as much// of the peer-processing logic assumes a consistent// block index statevoid UnloadBlockIndex(){ LOCK(cs_main); // 线程安全访问 setBlockIndexCandidates.clear(); // chainActive.SetTip(nullptr); pindexBestInvalid = nullptr; pindexBestHeader = nullptr; mempool.clear(); mapBlocksUnlinked.clear(); vinfoBlockFile.clear(); nLastBlockFile = 0; nBlockSequenceId = 1; setDirtyBlockIndex.clear(); setDirtyFileInfo.clear(); versionbitscache.Clear(); for (int b = 0; b < VERSIONBITS_NUM_BITS; b++) { warningcache[b].clear(); } for (BlockMap::value_type& entry : mapBlockIndex) { delete entry.second; } mapBlockIndex.clear(); //维护所有的区块索引 //mapBlockIndex类型为unordered_map<uint256, CBlockIndex*, BlockHasher> fHavePruned = false;}
写入重索引
pblocktree = new CBlockTreeDB(nBlockTreeDBCache, false, fReset); if (fReset) { pblocktree->WriteReindexing(true); //If we're reindexing in prune mode, wipe away unusable block files and all undo data files if (fPruneMode) CleanupBlockRevFiles(); } if (fRequestShutdown) break;
接下来创建一个CBlockTreeDB
类,这个类是用来向/blocks/index/*
下面的文件进行读写操作。然后判断fReset
是否为true
,这个变量也就是-reindex
用来设定是否重新创建所有的索引,如果为true
,那么就调用CBlockTreeDB
中的WriteReindexing
向数据库中写入数据,具体的调用过程如下:
bool CBlockTreeDB::WriteReindexing(bool fReindexing) { if (fReindexing) return Write(DB_REINDEX_FLAG, '1'); else return Erase(DB_REINDEX_FLAG);}// WriteReindexing再调用从CDBWrapper中继承的Write template <typename K, typename V> bool Write(const K& key, const V& value, bool fSync = false) { CDBBatch batch(*this); batch.Write(key, value); return WriteBatch(batch, fSync); }//Write再调用同类中的WriteBatch实现向leveldb数据库中写入数据// 其中的pdb就是leveldb数据库指针bool CDBWrapper::WriteBatch(CDBBatch& batch, bool fSync){ leveldb::Status status = pdb->Write(fSync ? syncoptions : writeoptions, &batch.batch); dbwrapper_private::HandleError(status); return true;}
接下来的fPruneMode
参数在http://blog.csdn.net/pure_lady/article/details/77982837#t1已经介绍过,是用来修剪已确认的区块的,这里如果在启用了重索引,那么就得先删除已验证的区块信息。CleanupBlockRevFiles()
的实现如下:
// If we're using -prune with -reindex, then delete block files that will be ignored by the// reindex. Since reindexing works by starting at block file 0 and looping until a blockfile// is missing, do the same here to delete any later block files after a gap. Also delete all// rev files since they'll be rewritten by the reindex anyway. This ensures that vinfoBlockFile// is in sync with what's actually on disk by the time we start downloading, so that pruning// works correctly.void CleanupBlockRevFiles(){ std::map<std::string, fs::path> mapBlockFiles; // Glob all blk?????.dat and rev?????.dat files from the blocks directory. // Remove the rev files immediately and insert the blk file paths into an // ordered map keyed by block file index. LogPrintf("Removing unusable blk?????.dat and rev?????.dat files for -reindex with -prune\n"); fs::path blocksdir = GetDataDir() / "blocks"; for (fs::directory_iterator it(blocksdir); it != fs::directory_iterator(); it++) { if (is_regular_file(*it) && it->path().filename().string().length() == 12 && it->path().filename().string().substr(8,4) == ".dat") { if (it->path().filename().string().substr(0,3) == "blk") mapBlockFiles[it->path().filename().string().substr(3,5)] = it->path(); else if (it->path().filename().string().substr(0,3) == "rev") remove(it->path()); } } // Remove all block files that aren't part of a contiguous set starting at // zero by walking the ordered map (keys are block file indices) by // keeping a separate counter. Once we hit a gap (or if 0 doesn't exist) // start removing block files. int nContigCounter = 0; for (const std::pair<std::string, fs::path>& item : mapBlockFiles) { if (atoi(item.first) == nContigCounter) { nContigCounter++; continue; } remove(item.second); }}
首先解释下开头的注释,如果我们将-reindex
和-prune
一起用,那么就将重索引时不考虑的一些区块文件直接删除。因为重索引是从0号区块一直连续的读取,直到某一个区块信息缺失就停止读取,缺失的区块之后所有的区块都会被直接删除。同时还需要删除rev
文件,因为这些文件在重索引时会重新生成,关于rev
文件的介绍,可以参考之前说过的http://blog.csdn.net/pure_lady/article/details/77982837#t1。根据注释的内容来看,这个函数要做的就是删除某个缺失的区块之后所有的区块数据,以及rev
开头的文件。那么接下来的代码就比较容易看懂了:先将所有的文件和对应的路径保存到一个map
中,然后用一个变量nContigCounter
从0开始计数,直到遇到第一个不一致的文件名,就从这个开始删除。
LoadBlockIndex
// LoadBlockIndex will load fTxIndex from the db, or set it if// we're reindexing. It will also load fHavePruned if we've// ever removed a block file from disk.// Note that it also sets fReindex based on the disk flag!// From here on out fReindex and fReset mean something different!if (!LoadBlockIndex(chainparams)) { strLoadError = _("Error loading block database"); break;}
解释下注释:LoadBlockIndex
首先将从数据库中加载fTxIndex
变量,如果是在进行重索引那么就从命令行读取fTxIndex
的值。另外如果我们之前删除过区块文件,那么这里还会架子啊fHavePruned
变量,同时还会根据磁盘上的标记来设置fReindex
变量,并且从此往后fReindex
和fReset
就表示不同的含义。我们再来看看LoadBlockIndex
的实现:
bool LoadBlockIndex(const CChainParams& chainparams){ // Load block index from databases bool needs_init = fReindex; if (!fReindex) { bool ret = LoadBlockIndexDB(chainparams); if (!ret) return false; needs_init = mapBlockIndex.empty(); } if (needs_init) { // Everything here is for *new* reindex/DBs. Thus, though // LoadBlockIndexDB may have set fReindex if we shut down // mid-reindex previously, we don't check fReindex and // instead only check it prior to LoadBlockIndexDB to set // needs_init. LogPrintf("Initializing databases...\n"); // Use the provided setting for -txindex in the new database fTxIndex = gArgs.GetBoolArg("-txindex", DEFAULT_TXINDEX); pblocktree->WriteFlag("txindex", fTxIndex); } return true;}
首先参数chainparams
在之前介绍过,是根据不同的网络Main
,RegTest
,TestNet
三个不同的参数设置静态写好的参数。然后检查fReindex
变量,如果设置了这个变量,那么之后会进行重新索引,这里也就没有必要先加载索引了;如果没有设置fReindex
,那么这里就是首次加载也是唯一的加载索引的地方。所谓加载索引,就是将/blocks/index/*
中的文件加载到内存,实现时就是通过LoadBlockIndexDB()
并将结果保存在变量mapBlockIndex
中,如果加载成功,那么mapBlockIndex
就不为空,needs_init
也就为false
。
合法性检测
// 检查mapBlockIndex中是否加载了创世块 if (!mapBlockIndex.empty() && mapBlockIndex.count(chainparams.GetConsensus().hashGenesisBlock) == 0) return InitError(_("Incorrect or no genesis block found. Wrong datadir for network?")); // 检查txindex的状态,因为在上一个函数(LoadBlockIndex)中如果设置了reindex, //那么fTxindex也会被重置 if (fTxIndex != gArgs.GetBoolArg("-txindex", DEFAULT_TXINDEX)) { strLoadError = _("You need to rebuild the database using -reindex to change -txindex"); break; } // 检查-prune的状态,因为用户可能会手动删除一些文件,然后 // 现在又想在未删除的模式中运行 if (fHavePruned && !fPruneMode) { strLoadError = _("You need to rebuild the database using -reindex to go back to unpruned mode. This will redownload the entire blockchain"); break; } // 如果没有设置初始化,并且创世块加载失败 if (!fReindex && !LoadGenesisBlock(chainparams)) { strLoadError = _("Error initializing block database"); break; }
- 比特币源码解析(22)
- 比特币源码解析(1)
- 比特币源码解析(2)
- 比特币源码解析(3)
- 比特币源码解析(4)
- 比特币源码解析(5)
- 比特币源码解析(6)
- 比特币源码解析(7)
- 比特币源码解析(8)
- 比特币源码解析(9)
- 比特币源码解析(11)
- 比特币源码解析(12)
- 比特币源码解析(13)
- 比特币源码解析(14)
- 比特币源码解析(15)
- 比特币源码解析(16)
- 比特币源码解析(17)
- 比特币源码解析(18)
- ES6入门第1章:关于ES6
- 静态方法与非静态方法的区别
- Sum of powers UVA
- MFC常见问题解惑
- 关于如何在Word PPT中使用LaTex数学公式
- 比特币源码解析(22)
- 深入理解闭包
- NGUI-2
- bug笔记
- 加法电路原理
- 如何通过SecureCRT转发功能实现外网访问内网服务
- 栈展开(stack unwinding)&在destructors中的exceptions
- 0-1背包问题(回溯+暴力)
- 机器学习系列(二)k-近邻算法(2)