四十一.创建一笔交易(4)--第二个区块 1.BitcoinMiner第二个区块由于不是创世块所以我们这里就不手动写入而是通过挖矿产生。就是BitcoinMiner函数而且第二个区块我也不打算加入正常交易。只带有coinbase交易就是挖矿奖励我们由易到难度。由于BitcoinMiner函数之前被修改简化过在这里我们尽量贴近源码。按源码的行为来执行。2.执行条件这个函数当fGenerateBitcoins设置为true时里面就会执行挖矿这个是在窗口里可以勾选择设置的一旦勾选就会把这个状态写入数据库下次加载会读取数据库里的这个值保存上一次的继续挖矿状态。好我们这里没有窗口就在main函数开头自己设置为true就行了。while (fGenerateBitcoins)3.线程执行这个函数是线程来执行的思考一下就可以理解不可能用主线程来执行。具体在源码中net.cpp里有这个函数void ThreadBitcoinMiner(void* parg) { vfThreadRunning[3] true; // 标记线程运行中 CheckForShutdown(3); try { bool fRet BitcoinMiner(); // ← 调用真正的挖矿函数 printf(BitcoinMiner returned %s\n\n\n, fRet ? true : false); } CATCH_PRINT_EXCEPTION(BitcoinMiner()) vfThreadRunning[3] false; // 线程退出标记 }然后这个函数一个是在窗口界面勾选就是勾选generate那个一样的地方会调用。就是分别执行了赋值和启动挖矿线程如下void CMainFrame::OnMenuOptionsGenerate(wxCommandEvent event) { fGenerateBitcoins event.IsChecked(); nTransactionsUpdated; CWalletDB().WriteSetting(fGenerateBitcoins, fGenerateBitcoins); if (fGenerateBitcoins) if (_beginthread(ThreadBitcoinMiner, 0, NULL) -1) printf(Error: _beginthread(ThreadBitcoinMiner) failed\n); Refresh(); wxPaintEvent eventPaint; AddPendingEvent(eventPaint); }我们只要看关键的几个东西就行了其它都是窗口相关可以无视。4.控制台然后还有命令行版本输入带gen的命令也可以启动如下if (mapArgs.count(/gen)) { if (mapArgs[/gen].empty()) fGenerateBitcoins true; else fGenerateBitcoins atoi(mapArgs[/gen].c_str()); }虽然里面没直接调用ThreadBitcoinMiner但后面接了这个是一样的if (fGenerateBitcoins) if (_beginthread(ThreadBitcoinMiner, 0, NULL) -1) printf(Error: _beginthread(ThreadBitcoinMiner) failed\n);这里的话我们理解了就可以直接在main函数启动吧没有影响。5.处理代码接下我们来看一下BitcoinMiner里的代码看看哪些可以用哪些目前需要注释掉。先来看完整的代码bool BitcoinMiner() { printf(BitcoinMiner started\n); SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_LOWEST); CKey key; key.MakeNewKey(); CBigNum bnExtraNonce 0; while (fGenerateBitcoins) { Sleep(50); CheckForShutdown(3); while (vNodes.empty()) { Sleep(1000); CheckForShutdown(3); } unsigned int nTransactionsUpdatedLast nTransactionsUpdated; CBlockIndex* pindexPrev pindexBest; unsigned int nBits GetNextWorkRequired(pindexPrev); // // Create coinbase tx // CTransaction txNew; txNew.vin.resize(1); txNew.vin[0].prevout.SetNull(); txNew.vin[0].scriptSig nBits bnExtraNonce; txNew.vout.resize(1); txNew.vout[0].scriptPubKey key.GetPubKey() OP_CHECKSIG; // // Create new block // auto_ptrCBlock pblock(new CBlock()); if (!pblock.get()) return false; // Add our coinbase tx as first transaction pblock-vtx.push_back(txNew); // Collect the latest transactions into the block int64 nFees 0; CRITICAL_BLOCK(cs_main) CRITICAL_BLOCK(cs_mapTransactions) { CTxDB txdb(r); mapuint256, CTxIndex mapTestPool; vectorchar vfAlreadyAdded(mapTransactions.size()); bool fFoundSomething true; unsigned int nBlockSize 0; while (fFoundSomething nBlockSize MAX_SIZE/2) { fFoundSomething false; unsigned int n 0; for (mapuint256, CTransaction::iterator mi mapTransactions.begin(); mi ! mapTransactions.end(); mi, n) { if (vfAlreadyAdded[n]) continue; CTransaction tx (*mi).second; if (tx.IsCoinBase() || !tx.IsFinal()) continue; // Transaction fee requirements, mainly only needed for flood control // Under 10K (about 80 inputs) is free for first 100 transactions // Base rate is 0.01 per KB int64 nMinFee tx.GetMinFee(pblock-vtx.size() 100); mapuint256, CTxIndex mapTestPoolTmp(mapTestPool); if (!tx.ConnectInputs(txdb, mapTestPoolTmp, CDiskTxPos(1,1,1), 0, nFees, false, true, nMinFee)) continue; swap(mapTestPool, mapTestPoolTmp); pblock-vtx.push_back(tx); nBlockSize ::GetSerializeSize(tx, SER_NETWORK); vfAlreadyAdded[n] true; fFoundSomething true; } } } pblock-nBits nBits; pblock-vtx[0].vout[0].nValue pblock-GetBlockValue(nFees); printf(\n\nRunning BitcoinMiner with %d transactions in block\n, pblock-vtx.size()); // // Prebuild hash buffer // struct unnamed1 { struct unnamed2 { int nVersion; uint256 hashPrevBlock; uint256 hashMerkleRoot; unsigned int nTime; unsigned int nBits; unsigned int nNonce; } block; unsigned char pchPadding0[64]; uint256 hash1; unsigned char pchPadding1[64]; } tmp; tmp.block.nVersion pblock-nVersion; tmp.block.hashPrevBlock pblock-hashPrevBlock (pindexPrev ? pindexPrev-GetBlockHash() : 0); tmp.block.hashMerkleRoot pblock-hashMerkleRoot pblock-BuildMerkleTree(); tmp.block.nTime pblock-nTime max((pindexPrev ? pindexPrev-GetMedianTimePast()1 : 0), GetAdjustedTime()); tmp.block.nBits pblock-nBits nBits; tmp.block.nNonce pblock-nNonce 1; unsigned int nBlocks0 FormatHashBlocks(tmp.block, sizeof(tmp.block)); unsigned int nBlocks1 FormatHashBlocks(tmp.hash1, sizeof(tmp.hash1)); // // Search // unsigned int nStart GetTime(); uint256 hashTarget CBigNum().SetCompact(pblock-nBits).getuint256(); uint256 hash; loop { BlockSHA256(tmp.block, nBlocks0, tmp.hash1); BlockSHA256(tmp.hash1, nBlocks1, hash); if (hash hashTarget) { pblock-nNonce tmp.block.nNonce; assert(hash pblock-GetHash()); //// debug print printf(BitcoinMiner:\n); printf(proof-of-work found \n hash: %s \ntarget: %s\n, hash.GetHex().c_str(), hashTarget.GetHex().c_str()); pblock-print(); SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_NORMAL); CRITICAL_BLOCK(cs_main) { // Save key if (!AddKey(key)) return false; key.MakeNewKey(); // Process this block the same as if we had received it from another node if (!ProcessBlock(NULL, pblock.release())) printf(ERROR in BitcoinMiner, ProcessBlock, block not accepted\n); } SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_LOWEST); Sleep(500); break; } // Update nTime every few seconds if ((tmp.block.nNonce 0x3ffff) 0) { CheckForShutdown(3); if (tmp.block.nNonce 0) break; if (pindexPrev ! pindexBest) break; if (nTransactionsUpdated ! nTransactionsUpdatedLast GetTime() - nStart 60) break; if (!fGenerateBitcoins) break; tmp.block.nTime pblock-nTime max(pindexPrev-GetMedianTimePast()1, GetAdjustedTime()); } } } return true; }挖到矿后每次都创建一个新地址接收这个设定我们保留吧。6.CheckForShutdownvoid CheckForShutdown(int n) { if (fShutdown) { if (n ! -1) vfThreadRunning[n] false; _endthread(); } }然后就到了CheckForShutdown函数这是用来响应窗口关闭线程的比如窗口点X了会设置fShutdown为true然后线程检测到这个通知后就会关闭纯程释放资源。函数参数标记为几号线程比如这个 CheckForShutdown(3);BitcoinMiner对应的就是3号线程。进行相应的其它操作。这里就不管它了因为fShutdown初始化为false了。7.vNodeswhile (vNodes.empty()) { Sleep(1000); CheckForShutdown(3); }这句我们要注释掉因为他保证了至少一个接点连接上才能挖矿我们是单机不连接节点。所以不满足条件需要注释掉。8.bnExtraNonceCBigNum bnExtraNonce 0;这里来说一下这个数字是扩展随机数为什么需要这个呢是nNonce的范围有限如果溢出了。所以当nNonce的值枯竭了后bnExtraNonce计数会加1.// Update nTime every few seconds if ((tmp.block.nNonce 0x3ffff) 0) { CheckForShutdown(3); if (tmp.block.nNonce 0) break; if (pindexPrev ! pindexBest) break; if (nTransactionsUpdated ! nTransactionsUpdatedLast GetTime() - nStart 60) break; if (!fGenerateBitcoins) break; tmp.block.nTime pblock-nTime max(pindexPrev-GetMedianTimePast()1, GetAdjustedTime()); }看到最前面的吗nNonce会在这里加1.然后对应着下面nNonce0的判断如果成立表明溢出然后break跳出当前循环。返回到while (fGenerateBitcoins) {的循环里然后txNew.vin[0].scriptSig nBits bnExtraNonce;然后bnExtraNonce加1这样就保证了有了足够的随机数。这个是核心的东西我们肯定不能注释只能修复如果涉及其它缺失代码的话。8.nTransactionsUpdatedLastunsigned int nTransactionsUpdatedLast nTransactionsUpdated;这个nTransactionsUpdated在之前的提到过就是接收区块时会加1还有其它地方这个值也会计数就是内存交易池有变化。那么就得重新创建区块再挖矿所以后面就会有这个判断if (nTransactionsUpdated ! nTransactionsUpdatedLast GetTime() - nStart 60) break;但为什么还加个时间设置呢必须满足大于60秒。其实这个只针对不能老是挖同一个区块万一后面增加了许多交易呢要与时俱进你需要更新一下区块挖。但是频繁更新又不现实所以增加一个时间判断达到平衡。9.pindexPrev好但是还有一个问题如果你现在挖的交易已经被别人区块挖出来了包含了呢所以这里会有这个判断if (pindexPrev ! pindexBest) break;这个pindexPrev记录了挖矿前的pindexBest如果你接收了新区块别人挖出来的那么pindexBest就不一致了。所以会退出就是这个机制控制着。10.GetNextWorkRequiredCBlockIndex* pindexPrev pindexBest; unsigned int nBits GetNextWorkRequired(pindexPrev);来看这句代码获取当前块的难度通过GetNextWorkRequired函数这个pindexPrev当前就是pindexBest还记得之前的这个pindexBest参数吗就是最优链的索引也就是创世块的难度。我们来看一下具体的代码unsigned int GetNextWorkRequired(const CBlockIndex* pindexLast) { const unsigned int nTargetTimespan 14 * 24 * 60 * 60; // two weeks const unsigned int nTargetSpacing 10 * 60; const unsigned int nInterval nTargetTimespan / nTargetSpacing; // Genesis block if (pindexLast NULL) return bnProofOfWorkLimit.GetCompact(); // Only change once per interval if ((pindexLast-nHeight1) % nInterval ! 0) return pindexLast-nBits; // Go back by what we want to be 14 days worth of blocks const CBlockIndex* pindexFirst pindexLast; for (int i 0; pindexFirst i nInterval-1; i) pindexFirst pindexFirst-pprev; assert(pindexFirst); // Limit adjustment step unsigned int nActualTimespan pindexLast-nTime - pindexFirst-nTime; printf( nActualTimespan %d before bounds\n, nActualTimespan); if (nActualTimespan nTargetTimespan/4) nActualTimespan nTargetTimespan/4; if (nActualTimespan nTargetTimespan*4) nActualTimespan nTargetTimespan*4; // Retarget CBigNum bnNew; bnNew.SetCompact(pindexLast-nBits); bnNew * nActualTimespan; bnNew / nTargetTimespan; if (bnNew bnProofOfWorkLimit) bnNew bnProofOfWorkLimit; /// debug print printf(\n\n\nGetNextWorkRequired RETARGET *****\n); printf(nTargetTimespan %d nActualTimespan %d\n, nTargetTimespan, nActualTimespan); printf(Before: %08x %s\n, pindexLast-nBits, CBigNum().SetCompact(pindexLast-nBits).getuint256().ToString().c_str()); printf(After: %08x %s\n, bnNew.GetCompact(), bnNew.getuint256().ToString().c_str()); return bnNew.GetCompact(); }有这么大一串代码说明他不仅仅只是获取一下前一个区块难度那么简单然后我们再看他的命名带个Next。我们可以猜出来他还负责计算调整难度。什么意思呢11.难度机制我们知道比特币的区块大概10分钟出一个块但这不是硬性规定的。而且通过难度来控制的根据当前的算力然后通过调整相应的难度来达到平均10分钟出一个块。但是这个调整是每2016个区块进行判断调整一次而且上下不能超过4倍。比如当前1分钟出一个块2016个后不是一下子增加10倍难度而是先到4分钟一个。下一个2016再到10分钟。是属于缓慢温和的调整避免剧烈波动。12.GetNextWorkRequired好我们来分析一下具体实现代码。const unsigned int nTargetTimespan 14 * 24 * 60 * 60; // two weeks const unsigned int nTargetSpacing 10 * 60; const unsigned int nInterval nTargetTimespan / nTargetSpacing;先来看这三个参数分别为14天是多少秒意为按正常来说2016个区块正常产生要多少秒。然后是一个区正常产生是多少秒就是10钟一个然后10X60 即nTargetSpacing的值。最后通过这两个值相除就算出来了是2016个区块即nInterval。好现在我们有正常情况的数据总时间多少秒一个以及多少个区块。// Only change once per interval if ((pindexLast-nHeight1) % nInterval ! 0) return pindexLast-nBits;判断该区块是不是第2016个区块即是2016的倍数如果不是则不需要调整难度直接返回上一个区块难度结束。如果是的话那就需要调用难度接下来的代码13.调整难度// Go back by what we want to be 14 days worth of blocks const CBlockIndex* pindexFirst pindexLast; for (int i 0; pindexFirst i nInterval-1; i) pindexFirst pindexFirst-pprev; assert(pindexFirst);先往上遍历2016次即找到上一个2016开始处。有了这个区块索引就可以计算到下一个2016个区块实际花了多少时间。// Limit adjustment step unsigned int nActualTimespan pindexLast-nTime - pindexFirst-nTime;两数相减就可以算出来实际花了多少秒。14.倍数限制好实际花了多少秒比如正常是14天那么实际有可能花了7天也有可能是28天都有可能。由于上下限4倍限制那么在这里就要调整一下了如下printf( nActualTimespan %d before bounds\n, nActualTimespan); if (nActualTimespan nTargetTimespan/4) nActualTimespan nTargetTimespan/4; if (nActualTimespan nTargetTimespan*4) nActualTimespan nTargetTimespan*4;如果实际时间比14天还小4倍更多就是比14的4分之一还小那么就给他限制在4分之1了不能超过了所以你会看到赋值nActualTimespannTargetTimespan/4下面的比14天还大4倍*4也是同理就不解释了给他封顶不得超过4倍。然后后面才是最关键的部分根据这个实际时间nActualTimespan就是怎么计算出难度值呢它的核心算法我们来看一下14.计算公式其实计算公式拆开来看很简单就是计算正常时间是实际时间的多少比如说4倍那说明要增加难度为4倍如果是4分之1那说明减少到当前难度的4分之1。那么我如果我增加难度到4倍应该怎么做是不是要将目标哈希变得更小就是哈希值要除以4或者乘以4分之1也行。降低难度就是将哈希值增大乘以4即可。那么实际算法是什么样的呢// Retarget CBigNum bnNew; bnNew.SetCompact(pindexLast-nBits); bnNew * nActualTimespan; bnNew / nTargetTimespan; if (bnNew bnProofOfWorkLimit) bnNew bnProofOfWorkLimit; /// debug print printf(\n\n\nGetNextWorkRequired RETARGET *****\n); printf(nTargetTimespan %d nActualTimespan %d\n, nTargetTimespan, nActualTimespan); printf(Before: %08x %s\n, pindexLast-nBits, CBigNum().SetCompact(pindexLast-nBits).getuint256().ToString().c_str()); printf(After: %08x %s\n, bnNew.GetCompact(), bnNew.getuint256().ToString().c_str()); return bnNew.GetCompact();写法不一样但是逻辑是一样的。先获得上一个区块的难度值解码转换成实际哈希值BigNum类型。然后将这个数字乘以实际时间再除以目标时间。比如实际时间是7天目标时间是14天那么需要增加两倍难度。即将实际哈希值缩小两倍除以2或乘以2分之1都行。那么假设这个数字是8那么8剩7然后再除以14哈希值是不是就变成4了。哈希值缩小了一半增加了相应的难度。代码都对应上了没有问题。然后最后一行return bnNew.GetCompact();将这个实际哈希值再转码成相应的nBits这就是整个GetNextWorkRequired函数的过程。然后这里还有一些问题需要解决if (bnNew bnProofOfWorkLimit) bnNew bnProofOfWorkLimit;他对难度调用后的值也是有着最低的要求的。即不能大于bnProofOfWorkLimit什么意思就是难度最低要求我们来看一下实际这个数字是多少static const CBigNum bnProofOfWorkLimit(~uint256(0) 32);这个值是0x00000000FFFFF.....。什么意思呢就是前面最少都得8个0。那么我们的区块哈希值难度只是前面5个0。肯定不符合要求。解决方法是可以直接注释这个判断要么改变这个值。我们选择后一种把它改成这样static const CBigNum bnProofOfWorkLimit(~uint256(0) 16);//原32现16改成前面4个0好现在最低难度要求是0x0000FFF..那么我们的0x00000F就不会再大于它了符合要求。篇幅所限暂时到这里下一章解决BitcoinMiner函数据其它问题。