比特币探究之挖矿
2018-07-19 本文已影响42人
魏兆华
比特币源码中的挖矿部分,始于src/rpc/mining.cpp中的generateBlocks函数。其大体运作参见以下流程图:
generateBlocks函数总体流程
源码部分解析如下,其中的中文注释为笔者添加。
UniValue generateBlocks(std::shared_ptr<CReserveScript> coinbaseScript, int nGenerate, uint64_t nMaxTries, bool keepScript)
{
//内部循环最多尝试65536次
static const int nInnerLoopCount = 0x10000;
int nHeightEnd = 0;
int nHeight = 0;
{
LOCK(cs_main); //放在大括号里,尽早释放锁
nHeight = chainActive.Height(); //区块当前高度
nHeightEnd = nHeight+nGenerate; //预定生成到的高度
}
//额外随机数。最初比特币主要依赖nNonce来证明工作量(POW),但随着矿机越来越牛B,
//挖矿难度越来越大,只靠nNonce已经满足不了要求,于是新增nExtraNonce数据,放在Coinbase中。
//如今的挖矿,实际上需要不断改变nNonce、nTime、hashMerkleTreeRoot三个数据,以求完成POW。
unsigned int nExtraNonce = 0;
UniValue blockHashes(UniValue::VARR); //VARR表明这是一个数组
//第一层循环,只要区块高度没有达到预定高度,也即区块未生成,并且没有收到关闭请求
while (nHeight < nHeightEnd && !ShutdownRequested())
{
//调用BlockAssembler::CreateNewBlock函数生成新区块的主体部分,但没做POW
std::unique_ptr<CBlockTemplate> pblocktemplate(BlockAssembler(Params()).CreateNewBlock(coinbaseScript->reserveScript));
if (!pblocktemplate.get())
throw JSONRPCError(RPC_INTERNAL_ERROR, "Couldn't create new block");
CBlock *pblock = &pblocktemplate->block;
{
LOCK(cs_main);
//递增额外随机数,每递增一次后,下面接着做POW,尝试的次数和范围都大大增加了
//话说只靠nNonce那还是CPU挖矿时代,到了ASIC矿机时代,hashMerkleTreeRoot才是主力
IncrementExtraNonce(pblock, chainActive.Tip(), nExtraNonce);
}
//第二层循环,递增随机数,调用CheckProofOfWork函数做POW
while (nMaxTries > 0 && pblock->nNonce < nInnerLoopCount && !CheckProofOfWork(pblock->GetHash(), pblock->nBits, Params().GetConsensus())) {
++pblock->nNonce;
--nMaxTries;
}
//如果达到了预定最大尝试次数,那就退出
if (nMaxTries == 0) {
break;
}
//如果nNonce达到了最大内部循环次数,说明这一波计算没能搞定,
//那么重新打包交易递增额外随机数,继续尝试
if (pblock->nNonce == nInnerLoopCount) {
continue;
}
//执行到这里,说明通过了POW,调用ProcessNewBlock进行必要的区块验证
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()); //把区块哈希值存起来,最后返回给调用者
//保留脚本,这个脚本可能从钱包来,要用于一个或多个创币交易
if (keepScript)
{
coinbaseScript->KeepScript();
}
}
return blockHashes;
}
下面接着看BlockAssembler.CreateNewBlock函数,它在src/miner.cpp里。其总体流程图如下:
BlockAssembler.CreateNewBlock主要流程
相关源码如下:
std::unique_ptr<CBlockTemplate> BlockAssembler::CreateNewBlock(const CScript& scriptPubKeyIn, bool fMineWitnessTx)
{
int64_t nTimeStart = GetTimeMicros();
//重置成空块
resetBlock();
pblocktemplate.reset(new CBlockTemplate());
if(!pblocktemplate.get())
return nullptr;
pblock = &pblocktemplate->block;
//先加一个空交易,作为创币交易(CoinBase)
pblock->vtx.emplace_back(); //C++11引入的函数,插入的对象在原地构建,比push_back效率明显提升
pblocktemplate->vTxFees.push_back(-1);
pblocktemplate->vTxSigOpsCost.push_back(-1);
LOCK2(cs_main, mempool.cs);
CBlockIndex* pindexPrev = chainActive.Tip();
assert(pindexPrev != nullptr);
nHeight = pindexPrev->nHeight + 1;
//根据共识参数,计算区块版本号
pblock->nVersion = ComputeBlockVersion(pindexPrev, chainparams.GetConsensus());
//如果启动参数里带有blockversion参数,那么就使用指定参数作为版本号
if (chainparams.MineBlocksOnDemand())
pblock->nVersion = gArgs.GetArg("-blockversion", pblock->nVersion);
pblock->nTime = GetAdjustedTime();
const int64_t nMedianTimePast = pindexPrev->GetMedianTimePast();
//截止锁定时间,按照现有策略取的是nMedianTimePast,为最近11个区块时间的中位数
nLockTimeCutoff = (STANDARD_LOCKTIME_VERIFY_FLAGS & LOCKTIME_MEDIAN_TIME_PAST)
? nMedianTimePast : pblock->GetBlockTime();
//是否包含见证交易
fIncludeWitness = IsWitnessEnabled(pindexPrev, chainparams.GetConsensus()) && fMineWitnessTx;
int nPackagesSelected = 0;
int nDescendantsUpdated = 0;
//交易打包,这个函数后面再详细解析
addPackageTxs(nPackagesSelected, nDescendantsUpdated);
int64_t nTime1 = GetTimeMicros();
nLastBlockTx = nBlockTx;
nLastBlockWeight = nBlockWeight;
//创币交易Coinbase
CMutableTransaction coinbaseTx;
coinbaseTx.vin.resize(1);
coinbaseTx.vin[0].prevout.SetNull(); //创币交易没有输入,凭空产生
coinbaseTx.vout.resize(1);
coinbaseTx.vout[0].scriptPubKey = scriptPubKeyIn;
//矿工的收益 = 交易费累计 + 挖矿奖励(50BTC按4年半衰期递减)
coinbaseTx.vout[0].nValue = nFees + GetBlockSubsidy(nHeight, chainparams.GetConsensus());
//Coinbase的scriptSig原来是空的,现在多了一个区块高度
coinbaseTx.vin[0].scriptSig = CScript() << nHeight << OP_0;
//将创币交易作为交易列表中第一个交易
pblock->vtx[0] = MakeTransactionRef(std::move(coinbaseTx));
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);
//填充区块头
pblock->hashPrevBlock = pindexPrev->GetBlockHash();
UpdateTime(pblock, chainparams.GetConsensus(), pindexPrev);
//设定难度值
pblock->nBits = GetNextWorkRequired(pindexPrev, pblock, chainparams.GetConsensus());
//设定随机数为零,准备POW
pblock->nNonce = 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);
}
下一讲继续解析交易打包和POW工作量证明。
本文欢迎转载,请注明出处。