区块链研习社

比特币探究之挖矿

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工作量证明。


本文欢迎转载,请注明出处。

上一篇下一篇

猜你喜欢

热点阅读