比特币源码解析(16)
来源:互联网 发布:毒品网络 豆瓣 编辑:程序博客网 时间:2024/06/05 14:31
0x01 AppInitSanityChecks - Step 4 sanity checks
bool AppInitSanityChecks(){ // ********************************************************* Step 4: sanity checks // Initialize elliptic curve code std::string sha256_algo = SHA256AutoDetect(); LogPrintf("Using the '%s' SHA256 implementation\n", sha256_algo); RandomInit(); ECC_Start(); globalVerifyHandle.reset(new ECCVerifyHandle()); // Sanity check if (!InitSanityCheck()) return InitError(strprintf(_("Initialization sanity check failed. %s is shutting down."), _(PACKAGE_NAME))); // Probe the data directory lock to give an early error message, if possible // We cannot hold the data directory lock here, as the forking for daemon() hasn't yet happened, // and a fork will cause weird behavior to it. return LockDataDirectory(true);}
椭圆曲线参数初始化
这一步的整个函数都相对较短,代码首先根据cpuid
决定使用何种版本的sha256算法,SHA256AutoDetect
函数具体实现如下,
std::string SHA256AutoDetect(){#if defined(EXPERIMENTAL_ASM) && (defined(__x86_64__) || defined(__amd64__)) uint32_t eax, ebx, ecx, edx; if (__get_cpuid(1, &eax, &ebx, &ecx, &edx) && (ecx >> 19) & 1) { Transform = sha256_sse4::Transform; assert(SelfTest(Transform)); return "sse4"; }#endif assert(SelfTest(Transform)); return "standard";}
对于64位的系统通过__get_cpuid
获取cpuid,然后通过ecx来获取具体芯片的feature flags,具体可以参考https://en.wikipedia.org/wiki/CPUID,ecx的第19位正好对应sse4.1的指令,其他情况就返回standard
。接下来的RandomInit()
同样也是根据cpuid
来决定是否增加RDRAND
作为额外的随机源,对于RDRAND
这里https://software.intel.com/en-us/blogs/2012/11/17/the-difference-between-rdrand-and-rdseed有很好的解释,上面链接主要解释RDRAND
和RDSEED
的却别,简单来说就是根据输出的值的作用来决定使用哪个,如果输出是作为其他PRNG(Pseudorandom number generator, 伪随机数生成器)的种子,那么就使用RDSEED
;其他情况都使用RDRAND
。ECC_Start()
开始进行椭圆曲线参数初始化。比特币中所有的公钥、私钥、签名等密码学技术基本上都是使用的椭圆曲线体系。
Sanity Check
接下来的一句代码进行bitcoin运行时需要的一些基本库的完整性检测,就是调用测试程序来测试它们是否都正常工作,以确保bitcoin运行环境正常。检测的模块包括以下几个部分,
/** Sanity checks * Ensure that Bitcoin is running in a usable environment with all * necessary library support. */bool InitSanityCheck(void){ if(!ECC_InitSanityCheck()) { InitError("Elliptic curve cryptography sanity check failure. Aborting."); return false; } if (!glibc_sanity_test() || !glibcxx_sanity_test()) return false; if (!Random_SanityCheck()) { InitError("OS cryptographic RNG sanity check failure. Aborting."); return false; } return true;}
- ECC_InitSanityCheck():椭圆曲线测试。
- glibc_sanity_test() & glibcxx_sanity_check():glibc和glibcxx库测试。
- Random_SanityCheck():随机数生成环境测试。
测试锁定数据目录
LockDataDirectory()
的实现如下,
static bool LockDataDirectory(bool probeOnly){ std::string strDataDir = GetDataDir().string(); // Make sure only a single Bitcoin process is using the data directory. fs::path pathLockFile = GetDataDir() / ".lock"; FILE* file = fsbridge::fopen(pathLockFile, "a"); // empty lock file; created if it doesn't exist. if (file) fclose(file); try { static boost::interprocess::file_lock lock(pathLockFile.string().c_str()); if (!lock.try_lock()) { return InitError(strprintf(_("Cannot obtain a lock on data directory %s. %s is probably already running."), strDataDir, _(PACKAGE_NAME))); } if (probeOnly) { lock.unlock(); } } catch(const boost::interprocess::interprocess_exception& e) { return InitError(strprintf(_("Cannot obtain a lock on data directory %s. %s is probably already running.") + " %s.", strDataDir, _(PACKAGE_NAME), e.what())); } return true;}
函数传入一个bool型参数probeOnly
,为true
表示只是测试是否能进行锁定但并不实际锁定,false
表示将数据目录(也就是~/.bitcoin/
)进行锁定,这里的锁定是指进程间的锁定,也就是只允许当前进程访问锁定的文件,其他进程不允许访问,如果probeOnly
为true
,那么在锁定之后再进行解锁。
0x02 AppInitMain Step 4a: application initialization
终于到主函数了!
创建PID文件
bool AppInitMain(boost::thread_group& threadGroup, CScheduler& scheduler){ const CChainParams& chainparams = Params(); // ********************************************************* Step 4a: application initialization#ifndef WIN32 CreatePidFile(GetPidFile(), getpid());#endif
AppInitMain
传入两个参数,threadGroup
和scheduler
,这两个参数到传入前都是空值。然后获取chainparams
,这个变量之前出现过多次,主要是包含一些共识的参数,自身是根据选择不同的网络main
、testnet
或者regtest
来生成不同的参数。接下来对于非Windows系统创建进程的PID文件,
转载:http://www.linuxidc.com/Linux/2012-12/76950.htm
(1) pid文件的内容:pid文件为文本文件,内容只有一行, 记录了该进程的ID。 用cat命令可以看到。
(2) pid文件的作用:防止进程启动多个副本。只有获得pid文件(固定路径固定文件名)写入权限(F_WRLCK)的进程才能正常启动并把自身的PID写入该文件中。其它同一个程序的多余进程则自动退出。
通过上面文章我们知道pid文件的作用就是防止进程启动多个副本,从而打乱原有的消息传输。
限定日志大小
if (gArgs.GetBoolArg("-shrinkdebugfile", logCategories == BCLog::NONE)) { // Do this first since it both loads a bunch of debug.log into memory, // and because this needs to happen before any other debug.log printing ShrinkDebugFile(); }
-shrinkdebugfile
:限制日志文件的大小,如果没有设置-debug
参数,那么该变量的默认值为1.
如果设置了这个参数那么就会执行ShrinkDebugFile
函数,该函数的实现位于src/util.cpp
,
void ShrinkDebugFile(){ // Amount of debug.log to save at end when shrinking (must fit in memory) constexpr size_t RECENT_DEBUG_HISTORY_SIZE = 10 * 1000000; // Scroll debug.log if it's getting too big fs::path pathLog = GetDataDir() / "debug.log"; FILE* file = fsbridge::fopen(pathLog, "r"); // If debug.log file is more than 10% bigger the RECENT_DEBUG_HISTORY_SIZE // trim it down by saving only the last RECENT_DEBUG_HISTORY_SIZE bytes if (file && fs::file_size(pathLog) > 11 * (RECENT_DEBUG_HISTORY_SIZE / 10)) { // Restart the file with some of the end std::vector<char> vch(RECENT_DEBUG_HISTORY_SIZE, 0); fseek(file, -((long)vch.size()), SEEK_END); int nBytes = fread(vch.data(), 1, vch.size(), file); fclose(file); file = fsbridge::fopen(pathLog, "w"); if (file) { fwrite(vch.data(), 1, nBytes, file); fclose(file); } } else if (file != nullptr) fclose(file);}
我们可以看到该函数首先定义了一个变量RECENT_DEBUG_HISTORY_SIZE
,大小为10MB,然后判断debug.log
文件的大小,如果小于11MB,那么就不做处理;否则读取文件的最后RECENT_DEBUG_HISTORY_SIZE
这么多字节,重新保存到debug.log
文件中。
DebugLog显示处理
if (fPrintToDebugLog) OpenDebugLog(); if (!fLogTimestamps) LogPrintf("Startup time: %s\n", DateTimeStrFormat("%Y-%m-%d %H:%M:%S", GetTime())); LogPrintf("Default data directory %s\n", GetDefaultDataDir().string()); LogPrintf("Using data directory %s\n", GetDataDir().string()); LogPrintf("Using config file %s\n", GetConfigFile(gArgs.GetArg("-conf", BITCOIN_CONF_FILENAME)).string()); LogPrintf("Using at most %i automatic connections (%i file descriptors available)\n", nMaxConnections, nFD);
fPrintToDebugLog
表示是否打开debug.log
文件,默认值为true
,只有在bench_bitcoin.cpp
中才设为false
;OpenDebugLog()
函数的实现如下:
static void DebugPrintInit(){ assert(mutexDebugLog == nullptr); mutexDebugLog = new boost::mutex(); vMsgsBeforeOpenLog = new std::list<std::string>;}void OpenDebugLog(){ boost::call_once(&DebugPrintInit, debugPrintInitFlag); boost::mutex::scoped_lock scoped_lock(*mutexDebugLog); assert(fileout == nullptr); assert(vMsgsBeforeOpenLog); fs::path pathDebug = GetDataDir() / "debug.log"; fileout = fsbridge::fopen(pathDebug, "a"); if (fileout) { setbuf(fileout, nullptr); // unbuffered // dump buffered messages from before we opened the log while (!vMsgsBeforeOpenLog->empty()) { FileWriteStr(vMsgsBeforeOpenLog->front(), fileout); vMsgsBeforeOpenLog->pop_front(); } } delete vMsgsBeforeOpenLog; vMsgsBeforeOpenLog = nullptr;}
boost::call_once
表示在多线程访问该语句时始终只执行一次调用的函数,其中第一个参数是被调用的函数地址,第二个参数类型为boost::once_flag
,代码中使用的是debugPrintInitFlag
,该变量的定义为,
static boost::once_flag debugPrintInitFlag = BOOST_ONCE_INIT;
在函数DebugPrintInit()
中定义了变量mutexDebugLog
,类型为boost::mutex::scoped_lock
,用来保证函数中后续代码是线程安全的,scoped_lock
类似于我们之前在http://blog.csdn.net/pure_lady/article/details/77675915#t3中讲过的lock_guard
, 能够保证在作用域范围内是互斥访问的,离开作用域时由析构函数自动解锁。但是接下来的一段代码有些奇怪,如果按照简单的代码执行顺序来看,在DebugPrintInit()
中创建了对象vMsgsBeforeOpenLog
,类型为链表,这时链表肯定是空的;而到了OpenDebugLog()
函数中直接有个while将vMsgsBeforeOpenLog
中的字符串写入到fileout
中,空链表有什么好写的?但是用gbd
调试程序时,运行到这里发现vMsgsBeforeOpenLog
竟然不是空的!
解释
全局搜索
vMsgsBeforeOpenLog
之后发现,还有个地方使用了这个变量,在src/util.cpp
中的LogPrintStr()
里,
// src/util.cpp line 343 boost::call_once(&DebugPrintInit, debugPrintInitFlag); boost::mutex::scoped_lock scoped_lock(*mutexDebugLog); // buffer if we haven't opened the log yet if (fileout == nullptr) { assert(vMsgsBeforeOpenLog); ret = strTimestamped.length(); vMsgsBeforeOpenLog->push_back(strTimestamped); }
从上面代码可以发现这里也调用了
DebugPrintInit()
这个函数,所以远在OpenDebugLog()
之前,vMsgsBeforeOpenLog
变量就已经创建了,然后LogPrintStr
在之后又被调用了很多次,每次都会往vMsgsBeforeOpenLog
中写入一些东西,所以到OpenDebugLog()
时该变量已经有内容了。
回到源代码,接下来几行开始打印一些提示信息,包括数据目录、最大连接数以及配置文件。
设置Cache大小
InitSignatureCache(); InitScriptExecutionCache();
-maxsigcachesize=<n>
:限制signature cache的大小为n MiB,默认值为32.
这俩函数首先都是从命令行中获取-maxsigcachesize
参数的值,然后分别通过signatureCache
和scriptExecutionCache
中的set_bytes()
函数设置最大的缓存大小。这个两个变量的类型都是Cache
,至于具体在什么时候用到还得看后面的代码。
创建Script Check Thead
LogPrintf("Using %u threads for script verification\n", nScriptCheckThreads); if (nScriptCheckThreads) { for (int i=0; i<nScriptCheckThreads-1; i++) threadGroup.create_thread(&ThreadScriptCheck); }
nScriptCheckThreads
是由参数中-par
设定的,位于http://blog.csdn.net/pure_lady/article/details/77982837#t4,这段代码是根据参数来创建线程具体的线程。其中ThreadScriptCheck
函数的实现如下,
void ThreadScriptCheck() { RenameThread("bitcoin-scriptch"); scriptcheckqueue.Thread();}
首先给当前线程重命名为bitcoin-scriptch
,然后启动线程,线程启动的流程如下:
1 static CCheckQueue<CScriptCheck> scriptcheckqueue(128); // src/validation.cpp line 15352 //! Worker thread src/checkqueue.h line 136 void Thread() { Loop(); }3 bool Loop(bool fMaster = false); // src/checkqueue.h line 694 // src/validation.cpp line 1202 bool CScriptCheck::operator()() { const CScript &scriptSig = ptxTo->vin[nIn].scriptSig; const CScriptWitness *witness = &ptxTo->vin[nIn].scriptWitness; return VerifyScript(scriptSig, scriptPubKey, witness, nFlags, CachingTransactionSignatureChecker(ptxTo, nIn, amount, cacheStore, *txdata), &error);}5 // src/script/interpreter.cpp line 1407 bool VerifyScript(const CScript& scriptSig, const CScript& scriptPubKey, const CScriptWitness* witness, unsigned int flags, const BaseSignatureChecker& checker, ScriptError* serror);
脚本验证线程的启动步骤如上流程所示,最开始是在AppInitMain
中使用threadGroup
中的create_thread
来创建新的线程,而每一个线程的回调函数都是ThreadScriptCheck()
,在这个回调函数当中主要是通过一个CCheckQueue
类型的静态变量scriptcheckqueue
执行类中的Thread()
成员函数,而Thread()
函数又调用一个类中的Loop()
私有函数,这个Loop
函数就是整个script check
的线程控制中心,Loop
的实现如下:
/** Internal function that does bulk of the verification work. */ bool Loop(bool fMaster = false) { boost::condition_variable& cond = fMaster ? condMaster : condWorker; std::vector<T> vChecks; vChecks.reserve(nBatchSize); unsigned int nNow = 0; bool fOk = true; do { { boost::unique_lock<boost::mutex> lock(mutex); // first do the clean-up of the previous loop run (allowing us to do it in the same critsect) if (nNow) { fAllOk &= fOk; nTodo -= nNow; if (nTodo == 0 && !fMaster) // We processed the last element; inform the master it can exit and return the result condMaster.notify_one(); } else { // first iteration nTotal++; } // logically, the do loop starts here while (queue.empty()) { if ((fMaster || fQuit) && nTodo == 0) { nTotal--; bool fRet = fAllOk; // reset the status for new work later if (fMaster) fAllOk = true; // return the current status return fRet; } nIdle++; cond.wait(lock); // wait nIdle--; } // Decide how many work units to process now. // * Do not try to do everything at once, but aim for increasingly smaller batches so // all workers finish approximately simultaneously. // * Try to account for idle jobs which will instantly start helping. // * Don't do batches smaller than 1 (duh), or larger than nBatchSize. nNow = std::max(1U, std::min(nBatchSize, (unsigned int)queue.size() / (nTotal + nIdle + 1))); vChecks.resize(nNow); for (unsigned int i = 0; i < nNow; i++) { // We want the lock on the mutex to be as short as possible, so swap jobs from the global // queue to the local batch vector instead of copying. vChecks[i].swap(queue.back()); queue.pop_back(); } // Check whether we need to do work at all fOk = fAllOk; } // execute work for (T& check : vChecks) if (fOk) fOk = check(); vChecks.clear(); } while (true); }
分析这段代码首先要注意两个变量,第一个是std::vector<T> vChecks;
中的T
代表什么,从scriptcheckqueue
的定义static CCheckQueue<CScriptCheck> scriptcheckqueue(128);
来看,T
代表的是CScriptCheck
,这个CScriptCheck
的定义在src/validation.h line 355
处,其中的一个最主要的函数就是重载的()
操作符,这可以解释上面代码的最后fOk = check();
这句,我们稍后再来看这个函数的实现;第二个需要注意的变量是boost::condition_variable& cond
这个变量也是整个函数的核心 ,关于boost condition_variable
可以参考http://blog.csdn.net/pure_lady/article/details/78057467#t5这篇文章,可能会更方便理解。
明白了上面两个变量再看起代码来会感觉清晰不少,这里面总共有两种角色,Master
和Worker
,其中Master
负责统计结果,Worker
负责执行具体的脚本验证。从源码的执行顺序来看,首先在AppInitMain
中执行Thread()
函数,也就是创建Worker
,此时任务队列queue
为空,所有的Worker
创建后都在cond.wait(lock)
处阻塞,等到有新的任务被加到queue
中时,才会被唤醒批量执行任务;最后调用Wait()
函数创建一个Master
,等待所有的Worker
执行完,统计执行结果并返回。
分析完Worker
和Master
的工作流程,下面再来分析一下CScriptCheck
重载的()
操作符,也就是上面代码中的check()
函数,它的实现如下(位于src/validation.cpp line 1202
):
// src/validation.cpp line 1202bool CScriptCheck::operator()() { const CScript &scriptSig = ptxTo->vin[nIn].scriptSig; const CScriptWitness *witness = &ptxTo->vin[nIn].scriptWitness; return VerifyScript(scriptSig, scriptPubKey, witness, nFlags, CachingTransactionSignatureChecker(ptxTo, nIn, amount, cacheStore, *txdata), &error);}
其中scriptSig
就是用户的签名也是解锁脚本,witness
是隔离见证中从scriptSig
中分离出来的,scriptPubKey
也就是锁定脚本,然后调用VerifyScript
来验证锁定脚本和解锁脚本是否能正确结合运行。关于脚本的验证和执行以及脚本的结构,包含的具体内容等等,我打算在后面专门再写一章来介绍比特币中的脚本系统,目前就就可当成一个封装的模块,知道它的功能就可以。
- 比特币源码解析(16)
- 比特币源码解析(1)
- 比特币源码解析(2)
- 比特币源码解析(3)
- 比特币源码解析(4)
- 比特币源码解析(5)
- 比特币源码解析(6)
- 比特币源码解析(7)
- 比特币源码解析(8)
- 比特币源码解析(9)
- 比特币源码解析(11)
- 比特币源码解析(12)
- 比特币源码解析(13)
- 比特币源码解析(14)
- 比特币源码解析(15)
- 比特币源码解析(17)
- 比特币源码解析(18)
- 比特币源码解析(19)
- Mybatis查询oracle之clob类型
- CodeForces
- maven 与 nexus 中央仓库(私服) 发布与引用
- netty源码探索
- Unsupervised Learning Model-Reducing Dimension
- 比特币源码解析(16)
- react native Navigator使用踩的坑
- 集合扩容问题(ArrList为例,常用集合扩容机制) -- JAVA 基础
- 【LeetCode】2. Add Two Numbers
- jsp的常用的标签
- 37条强大的常用linux shell命令组合
- 【C#】身份证识别(一):身份证号定位
- 遍历
- 编程范式20 函数式编程