作者:chirpyli
起源:恒生LIGHT云社区
挖矿流程概述
比特币挖矿,其实就是比特币节点,对交易进行打包出块,获取记账权的同时失去比特币激励。挖矿次要流程如下:
- 收集网络上播送的交易,进行验证,退出到交易池中;
- 结构新块——将交易池中的交易打包,抉择一条最长的区块链,计算最新块头哈希值作为新块(候选块,还未取得记账权)前一区块哈希;
- 工作量证实,计算难题,竞争记账权,如果取得记账权,则将新块播送进来,如果收到其余节点播送的新块,则验证该新块,如果新块通过验证,则从新开始挖矿流程。
随着专用矿机的呈现,CPU挖矿根本曾经被废除了,这里的CPU挖矿代码只是用来学习以及测试用,理论矿工并不会应用上面的代码来挖矿。开启CPU挖矿的话,可通过一个 generatetoaddress
RPC调用开启挖矿到一个特定的地址。
一个比特币节点能够抉择挖矿也能够抉择不挖矿,如果想挖矿的话,输出开始挖矿的命令 bitcoin-cli generatetoaddress
开启挖矿流程。
上面咱们来剖析相干源代码:
// 开启挖矿流程后,进入这里,须要一个交易输入,及Coinbase交易要将挖矿处分给矿工,须要锁定脚本static UniValue generatetoaddress(const JSONRPCRequest& request){ if (request.fHelp || request.params.size() < 2 || request.params.size() > 3) throw std::runtime_error( RPCHelpMan{"generatetoaddress", "\nMine blocks immediately to a specified address (before the RPC call returns)\n", { {"nblocks", RPCArg::Type::NUM, RPCArg::Optional::NO, "How many blocks are generated immediately."}, //设置筹备挖多少个区块 {"address", RPCArg::Type::STR, RPCArg::Optional::NO, "The address to send the newly generated bitcoin to."}, //挖矿处分输入地址 {"maxtries", RPCArg::Type::NUM, /* default */ "1000000", "How many iterations to try."}, }, RPCResult{ "[ blockhashes ] (array) hashes of blocks generated\n" }, RPCExamples{ "\nGenerate 11 blocks to myaddress\n" + HelpExampleCli("generatetoaddress", "11 \"myaddress\"") + "If you are running the bitcoin core wallet, you can get a new address to send the newly generated bitcoin to with:\n" + HelpExampleCli("getnewaddress", "") }, }.ToString()); int nGenerate = request.params[0].get_int(); uint64_t nMaxTries = 1000000; if (!request.params[2].isNull()) { nMaxTries = request.params[2].get_int(); } CTxDestination destination = DecodeDestination(request.params[1].get_str()); if (!IsValidDestination(destination)) { throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Error: Invalid address"); } CScript coinbase_script = GetScriptForDestination(destination); //用于给矿工处分的锁定脚本 return generateBlocks(coinbase_script, nGenerate, nMaxTries); //产生新块,竞争记账权}static UniValue generateBlocks(const CScript& coinbase_script, int nGenerate, uint64_t nMaxTries){ static const int nInnerLoopCount = 0x10000; int nHeightEnd = 0; int nHeight = 0; { // Don't keep cs_main locked LOCK(cs_main); nHeight = ::ChainActive().Height(); nHeightEnd = nHeight+nGenerate; } unsigned int nExtraNonce = 0; UniValue blockHashes(UniValue::VARR); while (nHeight < nHeightEnd && !ShutdownRequested()) //如果不到指定区块高度或者没有进行挖矿申请就始终挖矿 { std::unique_ptr<CBlockTemplate> pblocktemplate(BlockAssembler(Params()).CreateNewBlock(coinbase_script)); //结构新块 if (!pblocktemplate.get()) throw JSONRPCError(RPC_INTERNAL_ERROR, "Couldn't create new block"); CBlock *pblock = &pblocktemplate->block; { LOCK(cs_main); IncrementExtraNonce(pblock, ::ChainActive().Tip(), nExtraNonce); //默克尔根哈希是在这外面计算并赋值的。 } //一直扭转nNonce值,计算难题 while (nMaxTries > 0 && pblock->nNonce < nInnerLoopCount && !CheckProofOfWork(pblock->GetHash(), pblock->nBits, Params().GetConsensus())) { ++pblock->nNonce; --nMaxTries; } if (nMaxTries == 0) { break; } if (pblock->nNonce == nInnerLoopCount) { continue; } std::shared_ptr<const CBlock> shared_pblock = std::make_shared<const CBlock>(*pblock); if (!ProcessNewBlock(Params(), shared_pblock, true, nullptr)) throw JSONRPCError(RPC_INTERNAL_ERROR, "ProcessNewBlock, block not accepted"); ++nHeight; blockHashes.push_back(pblock->GetHash().GetHex()); } return blockHashes;}
前面咱们开展这外面的代码,详细分析一下这外面的具体流程。先从收集交易开始。
验证交易退出到交易池
比特币钱包结构交易并公布到比特币区块链网络中,其余节点收到网络上接管到的交易,先进行验证,验证交易后,比特币节点会将这些交易增加到本人的内存池中同时将交易播送给其余节点。内存池也称作交易池,用来暂存尚未被确认(退出到区块)的交易记录。这里的验证规定如下(好长,且有可能会发生变化):
- 交易的语法和数据结构必须正确。
- 交易输出和输入均不能为空。
- 交易字节数的大小必须小于MAX_BLOCK_SIZE。
- 每个交易输入的汇总价值必须在容许范畴内(小于2100万比特币,大于0)。
- 交易输出的哈希值不能为零,不应该转播Coinbase交易。
- nLockTime小于或等于INT_MAX。
- 交易字节数必须大于或等于100。
- 交易中签名操作的数量必须小于签名操作的限度值。
- 解锁脚本(scriptSig)只能将数字压入堆栈,锁定脚本(scriptPubKey)必须匹配isStandard格局(这将回绝“非标准”交易)。
- 交易池或者主分支的区块中必须存在匹配的交易。
- 对于每个输出,如果援用的输入在交易池的其余交易中存在,交易必须被回绝。
- 对于每个输出,须要在主分支和交易池中查找被援用的输入交易。如果任何输出对应的输入交易不存在,那么这就是个孤儿交易。如果其对应的交易不在孤儿交易池中,将其退出孤儿交易池。
- 对于每个输出,如果援用的输入交易是一个铸币交易的输入,必须至多通过COINBASE_MATURITY(100)确认。
- 应用输入交易计算输出价值,查看每个输出价值以及汇总值,看其是否超过容许范畴(小于2100万比特币,大于0)。
- 如果输出价值汇总小于输入价值,回绝这笔交易。
- 如果交易费用太小以至无奈退出一个空的区块,回绝这笔交易。
- 每个输出的解锁脚本必须基于相应的输入锁定脚本进行验证。
结构新块
后面收集网络上的交易并验证,退出交易池,这一过程其实是挖矿流程的预备工作,结构新块并进行难题计算才是挖矿流程的外围步骤。
结构过程
结构新块,其实你看一下区块构造,把相干字段设置好就能够了。
class CBlock : public CBlockHeader{public: // network and disk std::vector<CTransactionRef> vtx; //...省略局部代码...}
从交易池中按交易优先级选取局部交易退出到区块交易列表中,并开始结构区块头,最次要的是前一区块哈希和默克尔树根哈希值。这里还须要结构一笔Coinbase交易,并作为整个区块的第一笔交易。
class CBlockHeader{public: // header int32_t nVersion; // 版本号,指定验证规定(indicates which set of block validation rules to follow) uint256 hashPrevBlock; // 前一区块哈希(理论计算时获得是前一区块头哈希)(a reference to the parent/previous block in the blockchain) uint256 hashMerkleRoot; // 默克尔根哈希(a hash (root hash) of the merkle tree data structure containing a block's transactions) uint32_t nTime; // 时戳(seconds from Unix Epoch) uint32_t nBits; // 区块难度(aka the difficulty target for this block) uint32_t nNonce; // 工作量证实nonce(value used in proof-of-work) // ...局部代码省略...}
前一区块哈希值是选取以后区块链最长的一条链条的最高区块头部做哈希,计算出前一区块哈希。默克尔根哈希值依据交易列表计算得出。上面咱们看一下结构新块的源代码:
// 这个类负责结构新块(精确的说应该是候选块,因为还没有通过工作量证实)/** Generate a new block, without valid proof-of-work */class BlockAssembler{private: // The constructed block template std::unique_ptr<CBlockTemplate> pblocktemplaclass CBlock : public CBlockHeader{public: // network and disk std::vector<CTransactionRef> vtx;te; // A convenience pointer that always refers to the CBlock in pblocktemplate CBlock* pblock; // Configuration parameters for the block size bool fIncludeWitness; unsigned int nBlockMaxWeight; CFeeRate blockMinFeeRate; // Information on the current status of the block uint64_t nBlockWeight; uint64_t nBlockTx; uint64_t nBlockSigOpsCost; CAmount nFees; CTxMemPool::setEntries inBlock; // Chain context for the block int nHeight; int64_t nLockTimeCutoff; const CChainParams& chainparams;public: struct Options { Options(); size_t nBlockMaxWeight; CFeeRate blockMinFeeRate; }; explicit BlockAssembler(const CChainParams& params); BlockAssembler(const CChainParams& params, const Options& options); /** Construct a new block template with coinbase to scriptPubKeyIn */ std::unique_ptr<CBlockTemplate> CreateNewBlock(const CScript& scriptPubKeyIn); //最重要的是这个成员函数,创立新块 static Optional<int64_t> m_last_block_num_txs; static Optional<int64_t> m_last_block_weight;private: // utility functions /** Clear the block's state and prepare for assembling a new block */ void resetBlock(); /** Add a tx to the block */ void AddToBlock(CTxMemPool::txiter iter); // Methods for how to add transactions to a block. /** Add transactions based on feerate including unconfirmed ancestors * Increments nPackagesSelected / nDescendantsUpdated with corresponding * statistics from the package selection (for logging statistics). */ void addPackageTxs(int &nPackagesSelected, int &nDescendantsUpdated) EXCLUSIVE_LOCKS_REQUIRED(mempool.cs); // helper functions for addPackageTxs() /** Remove confirmed (inBlock) entries from given set */ void onlyUnconfirmed(CTxMemPool::setEntries& testSet); /** Test if a new package would "fit" in the block */ bool TestPackage(uint64_t packageSize, int64_t packageSigOpsCost) const; /** Perform checks on each transaction in a package: * locktime, premature-witness, serialized size (if necessary) * These checks should always succeed, and they're here * only as an extra check in case of suboptimal node configuration */ bool TestPackageTransactions(const CTxMemPool::setEntries& package); /** Return true if given transaction from mapTx has already been evaluated, * or if the transaction's cached data in mapTx is incorrect. */ bool SkipMapTxEntry(CTxMemPool::txiter it, indexed_modified_transaction_set &mapModifiedTx, CTxMemPool::setEntries &failedTx) EXCLUSIVE_LOCKS_REQUIRED(mempool.cs); /** Sort the package in an order that is valid to appear in a block */ void SortForBlock(const CTxMemPool::setEntries& package, std::vector<CTxMemPool::txiter>& sortedEntries); /** Add descendants of given transactions to mapModifiedTx with ancestor * state updated assuming given transactions are inBlock. Returns number * of updated descendants. */ int UpdatePackagesForAdded(const CTxMemPool::setEntries& alreadyAdded, indexed_modified_transaction_set &mapModifiedTx) EXCLUSIVE_LOCKS_REQUIRED(mempool.cs);};
咱们接下来看一下 BlockAssembler
类最重要的成员函数 CreateNewBlock
的源码:
//参数scriptPubKeyIn示意Coinbase交易输入锁定脚本std::unique_ptr<CBlockTemplate> BlockAssembler::CreateNewBlock(const CScript& scriptPubKeyIn){ int64_t nTimeStart = GetTimeMicros(); resetBlock(); pblocktemplate.reset(new CBlockTemplate()); if(!pblocktemplate.get()) return nullptr; pblock = &pblocktemplate->block; // pointer for convenience // Add dummy coinbase tx as first transaction pblock->vtx.emplace_back(); pblocktemplate->vTxFees.push_back(-1); // updated at end pblocktemplate->vTxSigOpsCost.push_back(-1); // updated at end LOCK2(cs_main, mempool.cs); CBlockIndex* pindexPrev = ::ChainActive().Tip(); //抉择以后最长链,最高区块 assert(pindexPrev != nullptr); nHeight = pindexPrev->nHeight + 1; pblock->nVersion = ComputeBlockVersion(pindexPrev, chainparams.GetConsensus()); //设置版本号字段 // -regtest only: allow overriding block.nVersion with // -blockversion=N to test forking scenarios if (chainparams.MineBlocksOnDemand()) pblock->nVersion = gArgs.GetArg("-blockversion", pblock->nVersion); pblock->nTime = GetAdjustedTime(); //设置时戳字段 const int64_t nMedianTimePast = pindexPrev->GetMedianTimePast(); nLockTimeCutoff = (STANDARD_LOCKTIME_VERIFY_FLAGS & LOCKTIME_MEDIAN_TIME_PAST) ? nMedianTimePast : pblock->GetBlockTime(); // Decide whether to include witness transactions // This is only needed in case the witness softfork activation is reverted // (which would require a very deep reorganization). // Note that the mempool would accept transactions with witness data before // IsWitnessEnabled, but we would only ever mine blocks after IsWitnessEnabled // unless there is a massive block reorganization with the witness softfork // not activated. // TODO: replace this with a call to main to assess validity of a mempool // transaction (which in most cases can be a no-op). fIncludeWitness = IsWitnessEnabled(pindexPrev, chainparams.GetConsensus()); int nPackagesSelected = 0; int nDescendantsUpdated = 0; addPackageTxs(nPackagesSelected, nDescendantsUpdated); //增加交易池中交易到区块中 int64_t nTime1 = GetTimeMicros(); m_last_block_num_txs = nBlockTx; m_last_block_weight = nBlockWeight; // Create coinbase transaction. 创立Coinbase交易 CMutableTransaction coinbaseTx; coinbaseTx.vin.resize(1); coinbaseTx.vin[0].prevout.SetNull(); //Coinbase交易是没有输出,只有输入的 coinbaseTx.vout.resize(1); coinbaseTx.vout[0].scriptPubKey = scriptPubKeyIn; //锁定脚本 coinbaseTx.vout[0].nValue = nFees + GetBlockSubsidy(nHeight, chainparams.GetConsensus()); //输入给矿工,交易费+挖矿处分 coinbaseTx.vin[0].scriptSig = CScript() << nHeight << OP_0; pblock->vtx[0] = MakeTransactionRef(std::move(coinbaseTx)); //Coinbase交易放到交易列表的第一位,区块中的第一笔交易 pblocktemplate->vchCoinbaseCommitment = GenerateCoinbaseCommitment(*pblock, pindexPrev, chainparams.GetConsensus()); pblocktemplate->vTxFees[0] = -nFees; LogPrintf("CreateNewBlock(): block weight: %u txs: %u fees: %ld sigops %d\n", GetBlockWeight(*pblock), nBlockTx, nFees, nBlockSigOpsCost); // Fill in header pblock->hashPrevBlock = pindexPrev->GetBlockHash(); //设置区块头中前一区块哈希字段 UpdateTime(pblock, chainparams.GetConsensus(), pindexPrev); pblock->nBits = GetNextWorkRequired(pindexPrev, pblock, chainparams.GetConsensus()); //计算难度值,设置难度值字段,具体计算方法上面挖矿难度一节会讲到 pblock->nNonce = 0; //随机数,初始置为0,期待前面一直调整 pblocktemplate->vTxSigOpsCost[0] = WITNESS_SCALE_FACTOR * GetLegacySigOpCount(*pblock->vtx[0]); CValidationState state; if (!TestBlockValidity(state, chainparams, *pblock, pindexPrev, false, false)) { throw std::runtime_error(strprintf("%s: TestBlockValidity failed: %s", __func__, FormatStateMessage(state))); } int64_t nTime2 = GetTimeMicros(); LogPrint(BCLog::BENCH, "CreateNewBlock() packages: %.2fms (%d packages, %d updated descendants), validity: %.2fms (total %.2fms)\n", 0.001 * (nTime1 - nTimeStart), nPackagesSelected, nDescendantsUpdated, 0.001 * (nTime2 - nTime1), 0.001 * (nTime2 - nTimeStart)); return std::move(pblocktemplate);}
能够看到,除了默克尔根哈希字段,其余次要字段都曾经结构实现,而默克尔根哈希字段的结构在上面代码中会结构。
void IncrementExtraNonce(CBlock* pblock, const CBlockIndex* pindexPrev, unsigned int& nExtraNonce){ // Update nExtraNonce static uint256 hashPrevBlock; if (hashPrevBlock != pblock->hashPrevBlock) { nExtraNonce = 0; hashPrevBlock = pblock->hashPrevBlock; } ++nExtraNonce; unsigned int nHeight = pindexPrev->nHeight+1; // Height first in coinbase required for block.version=2 CMutableTransaction txCoinbase(*pblock->vtx[0]); txCoinbase.vin[0].scriptSig = (CScript() << nHeight << CScriptNum(nExtraNonce)) + COINBASE_FLAGS; assert(txCoinbase.vin[0].scriptSig.size() <= 100); pblock->vtx[0] = MakeTransactionRef(std::move(txCoinbase)); pblock->hashMerkleRoot = BlockMerkleRoot(*pblock); // 计算默克尔根哈希}uint256 BlockMerkleRoot(const CBlock& block, bool* mutated){ // 计算交易哈希值 std::vector<uint256> leaves; leaves.resize(block.vtx.size()); for (size_t s = 0; s < block.vtx.size(); s++) { leaves[s] = block.vtx[s]->GetHash(); } return ComputeMerkleRoot(std::move(leaves), mutated);}uint256 ComputeMerkleRoot(std::vector<uint256> hashes, bool* mutated) { bool mutation = false; while (hashes.size() > 1) { // 逐层向上计算,始终计算到根哈希 if (mutated) { for (size_t pos = 0; pos + 1 < hashes.size(); pos += 2) { if (hashes[pos] == hashes[pos + 1]) mutation = true; } } if (hashes.size() & 1) { // 奇数个数交易,最初一个交易与本人配对 hashes.push_back(hashes.back()); } SHA256D64(hashes[0].begin(), hashes[0].begin(), hashes.size() / 2); hashes.resize(hashes.size() / 2); } if (mutated) *mutated = mutation; if (hashes.size() == 0) return uint256(); return hashes[0];}
挖矿难度
为了防止随着算力的稳定造成比特币出块工夫的不稳固,比特币中挖矿难度值是动静调整的,具体难度值的计算公式如下:New Difficulty = Old Difficulty * (Actual Time of Last 2016 Blocks / 20160 minutes)
。每2016个区块调整一次,这样比特币的出块距离就被稳固在10分种左右。
难度值计算源代码如下(很啰嗦,但实际上晓得下面那个公式就好了):
unsigned int GetNextWorkRequired(const CBlockIndex* pindexLast, const CBlockHeader *pblock, const Consensus::Params& params){ assert(pindexLast != nullptr); unsigned int nProofOfWorkLimit = UintToArith256(params.powLimit).GetCompact(); // Only change once per difficulty adjustment interval // 块高是否是2016个区块的整数倍,如果不是就不必调整难度,就沿用用以后区块链顶点区块的难度值 if ((pindexLast->nHeight+1) % params.DifficultyAdjustmentInterval() != 0) { if (params.fPowAllowMinDifficultyBlocks) { // Special difficulty rule for testnet: // If the new block's timestamp is more than 2* 10 minutes // then allow mining of a min-difficulty block. if (pblock->GetBlockTime() > pindexLast->GetBlockTime() + params.nPowTargetSpacing*2) return nProofOfWorkLimit; else { // Return the last non-special-min-difficulty-rules-block const CBlockIndex* pindex = pindexLast; while (pindex->pprev && pindex->nHeight % params.DifficultyAdjustmentInterval() != 0 && pindex->nBits == nProofOfWorkLimit) pindex = pindex->pprev; return pindex->nBits; } } return pindexLast->nBits; } // Go back by what we want to be 14 days worth of blocks int nHeightFirst = pindexLast->nHeight - (params.DifficultyAdjustmentInterval()-1); assert(nHeightFirst >= 0); const CBlockIndex* pindexFirst = pindexLast->GetAncestor(nHeightFirst); assert(pindexFirst); return CalculateNextWorkRequired(pindexLast, pindexFirst->GetBlockTime(), params);}// 计算下一个难度值unsigned int CalculateNextWorkRequired(const CBlockIndex* pindexLast, int64_t nFirstBlockTime, const Consensus::Params& params){ if (params.fPowNoRetargeting) return pindexLast->nBits; // Limit adjustment step int64_t nActualTimespan = pindexLast->GetBlockTime() - nFirstBlockTime; if (nActualTimespan < params.nPowTargetTimespan/4) nActualTimespan = params.nPowTargetTimespan/4; if (nActualTimespan > params.nPowTargetTimespan*4) nActualTimespan = params.nPowTargetTimespan*4; // Retarget const arith_uint256 bnPowLimit = UintToArith256(params.powLimit); arith_uint256 bnNew; bnNew.SetCompact(pindexLast->nBits); bnNew *= nActualTimespan; bnNew /= params.nPowTargetTimespan; if (bnNew > bnPowLimit) bnNew = bnPowLimit; return bnNew.GetCompact();}
挖矿处分
矿工竞争记账权,可取得交易费及挖矿处分,挖矿所得会在Coinbase的交易输入给矿工。那么挖矿处分是怎么计算的呢?源代码如下:
//依据块高度计算处分值,最开始是处分50比特币,随后每210000块递加一半CAmount GetBlockSubsidy(int nHeight, const Consensus::Params& consensusParams){ int halvings = nHeight / consensusParams.nSubsidyHalvingInterval; // Force block reward to zero when right shift is undefined. if (halvings >= 64) return 0; CAmount nSubsidy = 50 * COIN; // Subsidy is cut in half every 210,000 blocks which will occur approximately every 4 years. nSubsidy >>= halvings; return nSubsidy;}
这个能够在比特币区块链浏览器查到的,第210000个区块之前,Coinbase挖矿处分值(不计算交易费)都是50比特币,而第210000个区块处分值为25比特币,之后顺次类推。
接下来就要进行难题计算,竞争记账权了。
工作量证实
工作量证实的过程如下,就是一直扭转 nNonce
值,直到胜利或者收到其余节点产生的新块从而开始下一个新区块工作量证实的过程。
static UniValue generateBlocks(const CScript& coinbase_script, int nGenerate, uint64_t nMaxTries){ static const int nInnerLoopCount = 0x10000; int nHeightEnd = 0; int nHeight = 0; { // Don't keep cs_main locked LOCK(cs_main); nHeight = ::ChainActive().Height(); nHeightEnd = nHeight+nGenerate; } unsigned int nExtraNonce = 0; UniValue blockHashes(UniValue::VARR); while (nHeight < nHeightEnd && !ShutdownRequested()) //如果不到指定区块高度或者没有进行挖矿申请就始终挖矿 { std::unique_ptr<CBlockTemplate> pblocktemplate(BlockAssembler(Params()).CreateNewBlock(coinbase_script)); //结构新块 if (!pblocktemplate.get()) throw JSONRPCError(RPC_INTERNAL_ERROR, "Couldn't create new block"); CBlock *pblock = &pblocktemplate->block; { LOCK(cs_main); IncrementExtraNonce(pblock, ::ChainActive().Tip(), nExtraNonce); //默克尔根哈希是在这外面计算并赋值的。 } //一直扭转nNonce值,计算难题 while (nMaxTries > 0 && pblock->nNonce < nInnerLoopCount && !CheckProofOfWork(pblock->GetHash(), pblock->nBits, Params().GetConsensus())) { ++pblock->nNonce; --nMaxTries; } if (nMaxTries == 0) { break; } if (pblock->nNonce == nInnerLoopCount) { continue; } std::shared_ptr<const CBlock> shared_pblock = std::make_shared<const CBlock>(*pblock); if (!ProcessNewBlock(Params(), shared_pblock, true, nullptr)) throw JSONRPCError(RPC_INTERNAL_ERROR, "ProcessNewBlock, block not accepted"); ++nHeight; blockHashes.push_back(pblock->GetHash().GetHex()); } return blockHashes;}
能够设想,这种CPU挖矿代码在GPU或者专用矿机背后曾经没有胜利挖矿可能性,而且一些比特币实现曾经将挖矿这部分代码移除。这里剖析这段代码只是为了对比特币工作量证实挖矿的过程有个清晰的意识,加深对比特币工作流程的了解。
另外这里要再扩大一下,最开始挖矿,难度值很低,只须要一直批改随机数nNonce
值就能够了,当初随着算力的进步,难度越来越大,其实矿工能够批改的不只是nNonce
,矿工还能够批改nTime
时戳的值,因为只有nTime
满足要求即可,容许肯定的误差(且工夫不可能齐全同步),还能够批改Coinbase交易中交易输出中的脚本来间接批改默克尔根哈希值hashMerkleRoot
,从而达到扩大随机源的目标。所以理论的挖矿程序与下面的CPU挖矿程序不同。且其中最耗时的操作就是哈希运算的过程,挖矿矿机个别会专门针对哈希算法做优化的。
接下来咱们持续剖析:
// 查看是否满足难度条件bool CheckProofOfWork(uint256 hash, unsigned int nBits, const Consensus::Params& params){ bool fNegative; bool fOverflow; arith_uint256 bnTarget; bnTarget.SetCompact(nBits, &fNegative, &fOverflow); // Check range if (fNegative || bnTarget == 0 || fOverflow || bnTarget > UintToArith256(params.powLimit)) return false; // Check proof of work matches claimed amount if (UintToArith256(hash) > bnTarget) return false; return true;}
如果满足下面的难度条件,胜利的竞争到了新块的记账权,则进入上面的流程:新块解决流程,次要内容是验证新块,验证通过就存入磁盘并通过网络播送进来,抉择退出最长链。
// 新块解决流程bool ProcessNewBlock(const CChainParams& chainparams, const std::shared_ptr<const CBlock> pblock, bool fForceProcessing, bool *fNewBlock){ AssertLockNotHeld(cs_main); { CBlockIndex *pindex = nullptr; if (fNewBlock) *fNewBlock = false; CValidationState state; // CheckBlock() does not support multi-threaded block validation because CBlock::fChecked can cause data race. // Therefore, the following critical section must include the CheckBlock() call as well. LOCK(cs_main); // Ensure that CheckBlock() passes before calling AcceptBlock, as // belt-and-suspenders. bool ret = CheckBlock(*pblock, state, chainparams.GetConsensus()); //校验区块,如果校验通过就存入磁盘 if (ret) { // Store to disk ret = ::ChainstateActive().AcceptBlock(pblock, state, chainparams, &pindex, fForceProcessing, nullptr, fNewBlock); } if (!ret) { GetMainSignals().BlockChecked(*pblock, state); return error("%s: AcceptBlock FAILED (%s)", __func__, FormatStateMessage(state)); } } NotifyHeaderTip(); CValidationState state; // Only used to report errors, not invalidity - ignore it if (!::ChainstateActive().ActivateBestChain(state, chainparams, pblock)) //抉择退出最长链 return error("%s: ActivateBestChain failed (%s)", __func__, FormatStateMessage(state)); return true;}
首先要对新区块进行验证
// 验证新区块bool CheckBlock(const CBlock& block, CValidationState& state, const Consensus::Params& consensusParams, bool fCheckPOW, bool fCheckMerkleRoot){ // These are checks that are independent of context. if (block.fChecked) return true; // Check that the header is valid (particularly PoW). This is mostly // redundant with the call in AcceptBlockHeader. if (!CheckBlockHeader(block, state, consensusParams, fCheckPOW)) return false; // Check the merkle root. if (fCheckMerkleRoot) { bool mutated; uint256 hashMerkleRoot2 = BlockMerkleRoot(block, &mutated); //查看默克尔根哈希是否统一 if (block.hashMerkleRoot != hashMerkleRoot2) return state.Invalid(ValidationInvalidReason::BLOCK_MUTATED, false, REJECT_INVALID, "bad-txnmrklroot", "hashMerkleRoot mismatch"); // Check for merkle tree malleability (CVE-2012-2459): repeating sequences // of transactions in a block without affecting the merkle root of a block, // while still invalidating it. if (mutated) return state.Invalid(ValidationInvalidReason::BLOCK_MUTATED, false, REJECT_INVALID, "bad-txns-duplicate", "duplicate transaction"); } // All potential-corruption validation must be done before we do any // transaction validation, as otherwise we may mark the header as invalid // because we receive the wrong transactions for it. // Note that witness malleability is checked in ContextualCheckBlock, so no // checks that use witness data may be performed here. // Size limits 区块大小在长度限度之内 if (block.vtx.empty() || block.vtx.size() * WITNESS_SCALE_FACTOR > MAX_BLOCK_WEIGHT || ::GetSerializeSize(block, PROTOCOL_VERSION | SERIALIZE_TRANSACTION_NO_WITNESS) * WITNESS_SCALE_FACTOR > MAX_BLOCK_WEIGHT) return state.Invalid(ValidationInvalidReason::CONSENSUS, false, REJECT_INVALID, "bad-blk-length", "size limits failed"); // First transaction must be coinbase, the rest must not be 第一个交易(且只有第一个)是coinbase交易 if (block.vtx.empty() || !block.vtx[0]->IsCoinBase()) return state.Invalid(ValidationInvalidReason::CONSENSUS, false, REJECT_INVALID, "bad-cb-missing", "first tx is not coinbase"); for (unsigned int i = 1; i < block.vtx.size(); i++) if (block.vtx[i]->IsCoinBase()) return state.Invalid(ValidationInvalidReason::CONSENSUS, false, REJECT_INVALID, "bad-cb-multiple", "more than one coinbase"); // Check transactions应用查看清单验证区块内的交易并确保它们的有效性 for (const auto& tx : block.vtx) if (!CheckTransaction(*tx, state, true)) return state.Invalid(state.GetReason(), false, state.GetRejectCode(), state.GetRejectReason(), strprintf("Transaction check failed (tx hash %s) %s", tx->GetHash().ToString(), state.GetDebugMessage())); unsigned int nSigOps = 0; for (const auto& tx : block.vtx) { nSigOps += GetLegacySigOpCount(*tx); } if (nSigOps * WITNESS_SCALE_FACTOR > MAX_BLOCK_SIGOPS_COST) return state.Invalid(ValidationInvalidReason::CONSENSUS, false, REJECT_INVALID, "bad-blk-sigops", "out-of-bounds SigOpCount"); if (fCheckPOW && fCheckMerkleRoot) block.fChecked = true; return true;}// 验证是否满足工作量证实static bool CheckBlockHeader(const CBlockHeader& block, CValidationState& state, const Consensus::Params& consensusParams, bool fCheckPOW = true){ // Check proof of work matches claimed amount if (fCheckPOW && !CheckProofOfWork(block.GetHash(), block.nBits, consensusParams)) return state.Invalid(ValidationInvalidReason::BLOCK_INVALID_HEADER, false, REJECT_INVALID, "high-hash", "proof of work failed"); return true;}// 验证校验交易bool CheckTransaction(const CTransaction& tx, CValidationState &state, bool fCheckDuplicateInputs){ // Basic checks that don't depend on any context if (tx.vin.empty()) return state.Invalid(ValidationInvalidReason::CONSENSUS, false, REJECT_INVALID, "bad-txns-vin-empty"); if (tx.vout.empty()) return state.Invalid(ValidationInvalidReason::CONSENSUS, false, REJECT_INVALID, "bad-txns-vout-empty"); // Size limits (this doesn't take the witness into account, as that hasn't been checked for malleability) if (::GetSerializeSize(tx, PROTOCOL_VERSION | SERIALIZE_TRANSACTION_NO_WITNESS) * WITNESS_SCALE_FACTOR > MAX_BLOCK_WEIGHT) return state.Invalid(ValidationInvalidReason::CONSENSUS, false, REJECT_INVALID, "bad-txns-oversize"); // Check for negative or overflow output values CAmount nValueOut = 0; for (const auto& txout : tx.vout) { if (txout.nValue < 0) return state.Invalid(ValidationInvalidReason::CONSENSUS, false, REJECT_INVALID, "bad-txns-vout-negative"); if (txout.nValue > MAX_MONEY) return state.Invalid(ValidationInvalidReason::CONSENSUS, false, REJECT_INVALID, "bad-txns-vout-toolarge"); nValueOut += txout.nValue; if (!MoneyRange(nValueOut)) return state.Invalid(ValidationInvalidReason::CONSENSUS, false, REJECT_INVALID, "bad-txns-txouttotal-toolarge"); } // Check for duplicate inputs - note that this check is slow so we skip it in CheckBlock if (fCheckDuplicateInputs) { std::set<COutPoint> vInOutPoints; for (const auto& txin : tx.vin) { if (!vInOutPoints.insert(txin.prevout).second) return state.Invalid(ValidationInvalidReason::CONSENSUS, false, REJECT_INVALID, "bad-txns-inputs-duplicate"); } } if (tx.IsCoinBase()) { if (tx.vin[0].scriptSig.size() < 2 || tx.vin[0].scriptSig.size() > 100) return state.Invalid(ValidationInvalidReason::CONSENSUS, false, REJECT_INVALID, "bad-cb-length"); } else { for (const auto& txin : tx.vin) if (txin.prevout.IsNull()) return state.Invalid(ValidationInvalidReason::CONSENSUS, false, REJECT_INVALID, "bad-txns-prevout-null"); } return true;}
下面新块验证通过后,将新块存入磁盘中:
/** Store block on disk. If dbp is non-nullptr, the file is known to already reside on disk */bool CChainState::AcceptBlock(const std::shared_ptr<const CBlock>& pblock, CValidationState& state, const CChainParams& chainparams, CBlockIndex** ppindex, bool fRequested, const FlatFilePos* dbp, bool* fNewBlock){ const CBlock& block = *pblock; if (fNewBlock) *fNewBlock = false; AssertLockHeld(cs_main); CBlockIndex *pindexDummy = nullptr; CBlockIndex *&pindex = ppindex ? *ppindex : pindexDummy; if (!AcceptBlockHeader(block, state, chainparams, &pindex)) return false; // Try to process all requested blocks that we don't have, but only // process an unrequested block if it's new and has enough work to // advance our tip, and isn't too many blocks ahead. bool fAlreadyHave = pindex->nStatus & BLOCK_HAVE_DATA; bool fHasMoreOrSameWork = (m_chain.Tip() ? pindex->nChainWork >= m_chain.Tip()->nChainWork : true); // Blocks that are too out-of-order needlessly limit the effectiveness of // pruning, because pruning will not delete block files that contain any // blocks which are too close in height to the tip. Apply this test // regardless of whether pruning is enabled; it should generally be safe to // not process unrequested blocks. bool fTooFarAhead = (pindex->nHeight > int(m_chain.Height() + MIN_BLOCKS_TO_KEEP)); // TODO: Decouple this function from the block download logic by removing fRequested // This requires some new chain data structure to efficiently look up if a // block is in a chain leading to a candidate for best tip, despite not // being such a candidate itself. // TODO: deal better with return value and error conditions for duplicate // and unrequested blocks. if (fAlreadyHave) return true; if (!fRequested) { // If we didn't ask for it: if (pindex->nTx != 0) return true; // This is a previously-processed block that was pruned if (!fHasMoreOrSameWork) return true; // Don't process less-work chains if (fTooFarAhead) return true; // Block height is too high // Protect against DoS attacks from low-work chains. // If our tip is behind, a peer could try to send us // low-work blocks on a fake chain that we would never // request; don't process these. if (pindex->nChainWork < nMinimumChainWork) return true; } if (!CheckBlock(block, state, chainparams.GetConsensus()) || !ContextualCheckBlock(block, state, chainparams.GetConsensus(), pindex->pprev)) { assert(IsBlockReason(state.GetReason())); if (state.IsInvalid() && state.GetReason() != ValidationInvalidReason::BLOCK_MUTATED) { pindex->nStatus |= BLOCK_FAILED_VALID; setDirtyBlockIndex.insert(pindex); } return error("%s: %s", __func__, FormatStateMessage(state)); } // Header is valid/has work, merkle tree and segwit merkle tree are good...RELAY NOW // (but if it does not build on our best tip, let the SendMessages loop relay it) if (!IsInitialBlockDownload() && m_chain.Tip() == pindex->pprev) GetMainSignals().NewPoWValidBlock(pindex, pblock); // 如果是最新块,播送进来 // Write block to history file if (fNewBlock) *fNewBlock = true; try { FlatFilePos blockPos = SaveBlockToDisk(block, pindex->nHeight, chainparams, dbp); if (blockPos.IsNull()) { state.Error(strprintf("%s: Failed to find position to write new block to disk", __func__)); return false; } ReceivedBlockTransactions(block, pindex, blockPos, chainparams.GetConsensus()); } catch (const std::runtime_error& e) { return AbortNode(state, std::string("System error: ") + e.what()); } FlushStateToDisk(chainparams, state, FlushStateMode::NONE); CheckBlockIndex(chainparams.GetConsensus()); return true;}
实际上,播送的是区块头,源码如下:
/** * Maintain state about the best-seen block and fast-announce a compact block * to compatible peers. */void PeerLogicValidation::NewPoWValidBlock(const CBlockIndex *pindex, const std::shared_ptr<const CBlock>& pblock) { std::shared_ptr<const CBlockHeaderAndShortTxIDs> pcmpctblock = std::make_shared<const CBlockHeaderAndShortTxIDs> (*pblock, true); const CNetMsgMaker msgMaker(PROTOCOL_VERSION); LOCK(cs_main); static int nHighestFastAnnounce = 0; if (pindex->nHeight <= nHighestFastAnnounce) return; nHighestFastAnnounce = pindex->nHeight; bool fWitnessEnabled = IsWitnessEnabled(pindex->pprev, Params().GetConsensus()); uint256 hashBlock(pblock->GetHash()); { LOCK(cs_most_recent_block); most_recent_block_hash = hashBlock; most_recent_block = pblock; most_recent_compact_block = pcmpctblock; fWitnessesPresentInMostRecentCompactBlock = fWitnessEnabled; } // 遍历连贯节点,发送进来 connman->ForEachNode([this, &pcmpctblock, pindex, &msgMaker, fWitnessEnabled, &hashBlock](CNode* pnode) { AssertLockHeld(cs_main); // TODO: Avoid the repeated-serialization here if (pnode->nVersion < INVALID_CB_NO_BAN_VERSION || pnode->fDisconnect) return; ProcessBlockAvailability(pnode->GetId()); CNodeState &state = *State(pnode->GetId()); // If the peer has, or we announced to them the previous block already, // but we don't think they have this one, go ahead and announce it if (state.fPreferHeaderAndIDs && (!fWitnessEnabled || state.fWantsCmpctWitness) && !PeerHasHeader(&state, pindex) && PeerHasHeader(&state, pindex->pprev)) { LogPrint(BCLog::NET, "%s sending header-and-ids %s to peer=%d\n", "PeerLogicValidation::NewPoWValidBlock", hashBlock.ToString(), pnode->GetId()); connman->PushMessage(pnode, msgMaker.Make(NetMsgType::CMPCTBLOCK, *pcmpctblock)); state.pindexBestHeaderSent = pindex; } });}
再上面就是抉择退出最长链了,这就波及到比特币区块链分叉的问题及相干的解决逻辑,这里不再细述,感兴趣的能够本人浏览比特币源码。并且,不是所有的区块链都有分叉这个概念的,在许可链等采纳PBFT共识算法的区块链中,每个块一旦造成共识,就是确定性的,而比特币因Pow共识算法的起因,新块产生后并不是确定性的,从而有了分叉这个概念。
至此,比特币挖矿的流程根本就这些了,能够看到Pow共识算法在私有链中是非常简单高效的,它相比传统共识算法PBFT、Raft等最大的特点是共识过程无需与其余节点交互,无论是网络中只有1个节点还是成千上万个节点(目前比特币的共识(挖矿)节点规模大略在1万左右),因而它对网络动态变化的适应性十分高。但代价就是须要微小算力的反对,而传统共识算法没有这种问题。