1. 程式人生 > >比特幣原始碼分析(二十二)

比特幣原始碼分析(二十二)

用最簡單的術語來說,挖礦就是不斷重複計算區塊頭的雜湊值,修改一個引數(即nonce欄位),直到生成的雜湊值與特定的target相匹配的一個過程。

1、挖礦的流程

閱讀原始碼前先參考《精通比特幣》梳理一遍節點挖礦的流程:
(1). 構建一個空區塊,稱為候選區塊
(2). 從記憶體池中打包交易至候選區塊
(3). 構造區塊頭,填寫區塊頭的下述欄位
  1)填寫版本號version欄位
  2)填寫父區塊雜湊prevhash欄位
  3)用merkle樹彙總全部的交易,將merkle root的雜湊值填寫至merkle root欄位
  4)填寫時間戳timestamp欄位
  5)填寫難度目標target欄位
(4). 開始挖礦。挖礦就是不斷重複計算區塊頭的雜湊值,修改nonce引數,直到找到一個滿足條件的nonce值。當挖礦節點成功求出一個解後把解填入區塊頭的nonce欄位。
(5). 這時一個新區塊就成功挖出了,然後挖礦節點會做下面這些事:
  1) 按照標準清單檢驗新區塊,檢驗通過後進行下面的 2)和 3)步驟
  2)立刻將這個新區塊發給它的所有相鄰節點,相鄰節點收到這個新區塊後進行驗證,驗證有效後會繼續傳播給所有相鄰節點。
  3)將這個新區塊連線到現有的區塊鏈中,按照如下規則:
    根據新區塊的prevhash欄位在現有區塊鏈中尋找這個父區塊,
    (Ⅰ) 如果父區塊是主區塊鏈的“末梢”,則將新區塊新增上去即可;
    (Ⅱ) 如果父區塊所在的鏈是備用鏈,則節點將新區塊新增到備用鏈,同時比較備用鏈與主鏈的工作量。如果備用鏈比主鏈積累了更多的工作量,節點將選擇備用鏈作為其新的主鏈,而之前的主鏈則成為了備用鏈;
    (Ⅲ) 如果在現有的區塊鏈中找不到它的父區塊,那麼這個區塊被認為是“孤塊”。孤塊會被儲存在孤塊池中,直到它們的父區塊被節點接收到。一旦收到了父區塊並且將其連線到現有的區塊鏈上,節點就會將孤塊從孤塊池中取出,並且連線到它的父區塊,讓它作為區塊鏈的一部分。
  (後續閱讀原始碼後會發現,上述流程(5),即新區塊挖出後所做的操作,在原始碼中體現在ProcessNewBlock()方法裡,另外,當節點接收到一個新區塊時也會呼叫這個方法,也就是說當挖礦節點挖出一個新區塊和節點接收到一個新區塊時都會呼叫ProcessNewBlock()方法)

2、原始碼閱讀

來看看挖礦的入口,可以通過RPC命令generate和generatetoaddress進行挖礦,分別定義在src/wallet/rpcwallet.cpp裡:

static const CRPCCommand commands[] =
{ //  category              name                        actor (function)           argNames
    //  --------------------- ------------------------    -----------------------  ----------
{ "rawtransactions", "fundrawtransaction", &fundrawtransaction, {"hexstring","options","iswitness"} }, ......... { "wallet", "rescanblockchain", &rescanblockchain, {"start_height", "stop_height"} }, { "generating", "generate"
, &generate, {"nblocks","maxtries"} }, };

和src/rpc/mining.cpp中:

static const CRPCCommand commands[] =
{ //  category              name                      actor (function)         argNames
  //  --------------------- ------------------------  -----------------------  ----------
    { "mining",             "getnetworkhashps",       &getnetworkhashps,       {"nblocks","height"} },
    { "mining",             "getmininginfo",          &getmininginfo,          {} },

    .........


    { "generating",         "generatetoaddress",      &generatetoaddress,      {"nblocks","address","maxtries"} },

    .........
};

上述兩個RPC命令分別對應generate(const JSONRPCRequest& request) 和 generatetoaddress(const JSONRPCRequest& request) 方法,這兩個方法最終都會呼叫generateBlocks()方法:

UniValue generateBlocks(std::shared_ptr<CReserveScript> coinbaseScript, int nGenerate, uint64_t nMaxTries, bool keepScript)
{
    static const int nInnerLoopCount = 0x10000;//這個變數是用來控制一個新的候選區快hash計算時修改nNonce值的次數的
    int nHeightEnd = 0;
    int nHeight = 0;

    {   // Don't keep cs_main locked
        LOCK(cs_main);
        nHeight = chainActive.Height();//當前主鏈的高度
        nHeightEnd = nHeight+nGenerate;//nGenerate是要生成的新區塊數,nHeightEnd則是最終挖出新區塊後主鏈的高度
    }
    unsigned int nExtraNonce = 0;
    UniValue blockHashes(UniValue::VARR);
    while (nHeight < nHeightEnd)//開始挖礦,直到挖出目標數量的新區塊
    {
        //建立一個新的候選區快
        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;
        //修改區塊的extranonce值
        {
            LOCK(cs_main);
            IncrementExtraNonce(pblock, chainActive.Tip(), nExtraNonce);
        }

        //這裡就開始真正挖礦的hash計算了,不斷修改pblock->nNonce的值,計算hash,檢查是否滿足難度目標target(代表著工作量證明)
        while (nMaxTries > 0 && pblock->nNonce < nInnerLoopCount && !CheckProofOfWork(pblock->GetHash(), pblock->nBits, Params().GetConsensus())) {
            ++pblock->nNonce;
            --nMaxTries;
        }
        if (nMaxTries == 0) {
            break;
        }
        //如果nNonce值修改的次數已經超過了nInnerLoopCount規定的次數還沒有找到工作量證明的一個解,則廢棄這個候選區塊,重新回到前面建立一個新的候選區塊
        if (pblock->nNonce == nInnerLoopCount) {
            continue;
        }

        //到了這裡說明候選區塊在前面的過程中已經完成了工作量證明,候選區塊成為了一個有效的新區塊
        std::shared_ptr<const CBlock> shared_pblock = std::make_shared<const CBlock>(*pblock);
        //呼叫ProcessNewBlock()處理這個有效的新區塊
        if (!ProcessNewBlock(Params(), shared_pblock, true, nullptr))
            throw JSONRPCError(RPC_INTERNAL_ERROR, "ProcessNewBlock, block not accepted");
        ++nHeight;
        //將生成的所有新區塊的hash儲存起來
        blockHashes.push_back(pblock->GetHash().GetHex());

        //mark script as important because it was used at least for one coinbase output if the script came from the wallet
        if (keepScript)
        {
            coinbaseScript->KeepScript();
        }
    }
    return blockHashes;//返回所有新區塊的hash
}

上面原始碼中已經對關鍵的邏輯做了註釋,主要分為三部分主要邏輯:建立候選區塊、進行hash計算完成工作量證明、處理新區塊。接下去將對這幾部分原始碼做更進一步的詳細分析。

2.1建立候選區塊
建立候選區塊是通過這部分程式碼完成的:

        //建立一個新的候選區快
        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;
        //修改區塊的extranonce值和計算默克爾樹根節點的hash
        {
            LOCK(cs_main);
            IncrementExtraNonce(pblock, chainActive.Tip(), nExtraNonce);
        }

主要包含兩部分:
CreateNewBlock()方法完成計算並填寫填寫nVersion ,nTime,hashPrevBlock,nBits,將nNonce初始化為0的操作;IncrementExtraNonce()方法完成修改nExtraNonce值和計算並填寫hashMerkleRoot欄位。

看下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; // pointer for convenience

    // Add dummy coinbase tx as first transaction
    //先新增一個虛設的coinbase交易作為區塊的第一個交易,相當於佔位,因為每個區塊的第一個交易必須是coinbase交易,後面會初始化這個偽coinbase交易
    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;//新區塊的高度是=當前主鏈的高度+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) or when
    // -promiscuousmempoolflags is used.
    // 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()) && fMineWitnessTx;

    int nPackagesSelected = 0;
    int nDescendantsUpdated = 0;
    addPackageTxs(nPackagesSelected, nDescendantsUpdated);//根據交易選擇演算法從記憶體池中選擇交易打包進區塊,這個過程並不會把交易從記憶體池中移除,移除交易的過程是在後續的處理新區塊的方法ProcessNewBlock()裡

    int64_t nTime1 = GetTimeMicros();

    nLastBlockTx = nBlockTx;
    nLastBlockWeight = nBlockWeight;

    // Create coinbase transaction.
    CMutableTransaction coinbaseTx;
    coinbaseTx.vin.resize(1);
    coinbaseTx.vin[0].prevout.SetNull();
    coinbaseTx.vout.resize(1);
    coinbaseTx.vout[0].scriptPubKey = scriptPubKeyIn;
    coinbaseTx.vout[0].nValue = nFees + GetBlockSubsidy(nHeight, chainparams.GetConsensus());//coinbase交易的輸出就是礦工的獎勵,交易費+區塊獎勵
    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();//填寫父區塊hash
    UpdateTime(pblock, chainparams.GetConsensus(), pindexPrev);
    pblock->nBits          = GetNextWorkRequired(pindexPrev, pblock, chainparams.GetConsensus());//計算並填寫難度目標target,這裡即變數nBits
    pblock->nNonce         = 0;//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);
}

上述原始碼已經對建立候選區塊過程中的關鍵部分做了註釋,接下來繼續對這些關鍵部分做更進一步的分析,包括打包交易、計算挖礦獎勵、計算難度目標、修改extranonce值,計算默克爾樹根節點hash。

2.1.1打包交易
從記憶體池中選擇交易打包進區塊,看下 addPackageTxs() 這個方法:

// This transaction selection algorithm orders the mempool based
// on feerate of a transaction including all unconfirmed ancestors.
// Since we don't remove transactions from the mempool as we select them
// for block inclusion, we need an alternate method of updating the feerate
// of a transaction with its not-yet-selected ancestors as we go.
// This is accomplished by walking the in-mempool descendants of selected
// transactions and storing a temporary modified state in mapModifiedTxs.
// Each time through the loop, we compare the best transaction in
// mapModifiedTxs with the next transaction in the mempool to decide what
// transaction package to work on next.
void BlockAssembler::addPackageTxs(int &nPackagesSelected, int &nDescendantsUpdated)
{
    // mapModifiedTx will store sorted packages after they are modified
    // because some of their txs are already in the block
    indexed_modified_transaction_set mapModifiedTx;
    // Keep track of entries that failed inclusion, to avoid duplicate work
    CTxMemPool::setEntries failedTx;

    // Start by adding all descendants of previously added txs to mapModifiedTx
    // and modifying them for their already included ancestors
    UpdatePackagesForAdded(inBlock, mapModifiedTx);

    CTxMemPool::indexed_transaction_set::index<ancestor_score>::type::iterator mi = mempool.mapTx.get<ancestor_score>().begin();
    CTxMemPool::txiter iter;

    // Limit the number of attempts to add transactions to the block when it is
    // close to full; this is just a simple heuristic to finish quickly if the
    // mempool has a lot of entries.
    const int64_t MAX_CONSECUTIVE_FAILURES = 1000;
    int64_t nConsecutiveFailed = 0;

    while (mi != mempool.mapTx.get<ancestor_score>().end() || !mapModifiedTx.empty())
    {
        // First try to find a new transaction in mapTx to evaluate.
        if (mi != mempool.mapTx.get<ancestor_score>().end() &&
                SkipMapTxEntry(mempool.mapTx.project<0>(mi), mapModifiedTx, failedTx)) {
            ++mi;
            continue;
        }

        // Now that mi is not stale, determine which transaction to evaluate:
        // the next entry from mapTx, or the best from mapModifiedTx?
        bool fUsingModified = false;

        modtxscoreiter modit = mapModifiedTx.get<ancestor_score>().begin();
        if (mi == mempool.mapTx.get<ancestor_score>().end()) {
            // We're out of entries in mapTx; use the entry from mapModifiedTx
            iter = modit->iter;
            fUsingModified = true;
        } else {
            // Try to compare the mapTx entry to the mapModifiedTx entry
            iter = mempool.mapTx.project<0>(mi);
            if (modit != mapModifiedTx.get<ancestor_score>().end() &&
                    CompareTxMemPoolEntryByAncestorFee()(*modit, CTxMemPoolModifiedEntry(iter))) {
                // The best entry in mapModifiedTx has higher score
                // than the one from mapTx.
                // Switch which transaction (package) to consider
                iter = modit->iter;
                fUsingModified = true;
            } else {
                // Either no entry in mapModifiedTx, or it's worse than mapTx.
                // Increment mi for the next loop iteration.
                ++mi;
            }
        }

        // We skip mapTx entries that are inBlock, and mapModifiedTx shouldn't
        // contain anything that is inBlock.
        assert(!inBlock.count(iter));

        uint64_t packageSize = iter->GetSizeWithAncestors();
        CAmount packageFees = iter->GetModFeesWithAncestors();
        int64_t packageSigOpsCost = iter->GetSigOpCostWithAncestors();
        if (fUsingModified) {
            packageSize = modit->nSizeWithAncestors;
            packageFees = modit->nModFeesWithAncestors;
            packageSigOpsCost = modit->nSigOpCostWithAncestors;
        }

        if (packageFees < blockMinFeeRate.GetFee(packageSize)) {
            // Everything else we might consider has a lower fee rate
            return;
        }

        if (!TestPackage(packageSize, packageSigOpsCost)) {
            if (fUsingModified) {
                // Since we always look at the best entry in mapModifiedTx,
                // we must erase failed entries so that we can consider the
                // next best entry on the next loop iteration
                mapModifiedTx.get<ancestor_score>().erase(modit);
                failedTx.insert(iter);
            }

            ++nConsecutiveFailed;

            if (nConsecutiveFailed > MAX_CONSECUTIVE_FAILURES && nBlockWeight >
                    nBlockMaxWeight - 4000) {
                // Give up if we're close to full and haven't succeeded in a while
                break;
            }
            continue;
        }

        CTxMemPool::setEntries ancestors;
        uint64_t nNoLimit = std::numeric_limits<uint64_t>::max();
        std::string dummy;
        mempool.CalculateMemPoolAncestors(*iter, ancestors, nNoLimit, nNoLimit, nNoLimit, nNoLimit, dummy, false);

        onlyUnconfirmed(ancestors);
        ancestors.insert(iter);

        // Test if all tx's are Final
        if (!TestPackageTransactions(ancestors)) {
            if (fUsingModified) {
                mapModifiedTx.get<ancestor_score>().erase(modit);
                failedTx.insert(iter);
            }
            continue;
        }

        // This transaction will make it in; reset the failed counter.
        nConsecutiveFailed = 0;

        // Package can be added. Sort the entries in a valid order.
        std::vector<CTxMemPool::txiter> sortedEntries;
        SortForBlock(ancestors, iter, sortedEntries);

        for (size_t i=0; i<sortedEntries.size(); ++i) {
            AddToBlock(sortedEntries[i]);
            // Erase from the modified set, if present
            mapModifiedTx.erase(sortedEntries[i]);
        }

        ++nPackagesSelected;

        // Update transactions that depend on each of these
        nDescendantsUpdated += UpdatePackagesForAdded(ancestors, mapModifiedTx);
    }
}

2.1.2計算挖礦獎勵
coinbase交易的輸出就是礦工的獎勵:

    coinbaseTx.vout[0].nValue = nFees + GetBlockSubsidy(nHeight, chainparams.GetConsensus());//coinbase交易的輸出就是礦工的獎勵,交易費+區塊獎勵

礦工的獎勵 = 交易費 + 區塊獎勵。
2.1.2.1 交易費
交易費nFees是上述打包交易的addPackageTxs()方法根據打包進區塊的所有交易的費用累加起來得到的,看下addPackageTxs()方法的這段程式碼:

        for (size_t i=0; i<sortedEntries.size(); ++i) {
            AddToBlock(sortedEntries[i]);
            // Erase from the modified set, if present
            mapModifiedTx.erase(sortedEntries[i]);
        }

會呼叫 AddToBlock():

void BlockAssembler::AddToBlock(CTxMemPool::txiter iter)
{
    pblock->vtx.emplace_back(iter->GetSharedTx());
    pblocktemplate->vTxFees.push_back(iter->GetFee());
    pblocktemplate->vTxSigOpsCost.push_back(iter->GetSigOpCost());
    nBlockWeight += iter->GetTxWeight();
    ++nBlockTx;
    nBlockSigOpsCost += iter->GetSigOpCost();
    nFees += iter->GetFee();
    inBlock.insert(iter);

    bool fPrintPriority = gArgs.GetBoolArg("-printpriority", DEFAULT_PRINTPRIORITY);
    if (fPrintPriority) {
        LogPrintf("fee %s txid %s\n",
                  CFeeRate(iter->GetModifiedFee(), iter->GetTxSize()).ToString(),
                  iter->GetTx().GetHash().ToString());
    }
}

nFees += iter->GetFee();將所有打包進區塊的交易的費用累加起來。

2.1.2.2區塊獎勵

看下GetBlockSubsidy()方法是如何計算獎勵的:

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;
}

根據當前區塊的高度/獎勵減半的間隔區塊數nSubsidyHalvingInterval,從而得到減半的次數,其中nSubsidyHalvingInterval在主網中是210000,該變數定義在src/chainparams.cpp的CMainParams類中。每生成210000個區塊,區塊獎勵就減半,根據比特幣平均每10分鐘產生一個區塊計算,產生210000個區塊需要大約4年,即每隔4年,區塊獎勵就減半一次。
計算出減半次數halvings後根據初始是50個比特幣的獎勵就可以計算出當前的獎勵。

2.1.3 計算難度目標
nBits代表著工作量證明的難度目標值,這個值是由系統計算生成的

    pblock->nBits          = GetNextWorkRequired(pindexPrev, pblock, chainparams.GetConsensus());

看下GetNextWorkRequired()方法是如何獲取難度目標值的

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
    //到了這裡說明新區塊恰好需要進行難度調整,即新區塊的高度能被2016整除,則進行一次難度調整,計算新的難度目標
    int nHeightFirst = pindexLast->nHeight - (params.DifficultyAdjustmentInterval()-1);
    assert(nHeightFirst >= 0);
    const CBlockIndex* pindexFirst = pindexLast->GetAncestor(nHeightFirst);
    assert(pindexFirst);

    return CalculateNextWorkRequired(pindexLast, pindexFirst->GetBlockTime(), params);
}

下面對程式碼中的關鍵部分做更進一步的分析。

2.1.3.1難度調整週期
比特幣系統是以2016個區塊為一個週期自動進行難度調整的,2016是這樣算出來的:比特幣規定2個星期,即14天,進行一次難度調整,平均每10分鐘生成一個區塊,按此計算,即2016個區塊調整一次。看下params.DifficultyAdjustmentInterval()方法:

    int64_t DifficultyAdjustmentInterval() const { return nPowTargetTimespan / nPowTargetSpacing; }

nPowTargetTimespan和nPowTargetSpacing變數的初始化是在src/chainparams.cpp的CMainParams類中:

        consensus.nPowTargetTimespan = 14 * 24 * 60 * 60; // two weeks
        consensus.nPowTargetSpacing = 10 * 60;

nPowTargetTimespan表示調整週期是2周,nPowTargetSpacing表示每10分鐘生成一個區塊,nPowTargetTimespan / nPowTargetSpacing 的結果是2016,也就是說每隔2016個區塊進行一次難度調整,計算新的難度目標,計算方法見下面。

2.1.3.2計算新的難度目標
看下CalculateNextWorkRequired()方法:

unsigned int CalculateNextWorkRequired(const CBlockIndex* pindexLast, int64_t nFirstBlockTime, const Consensus::Params& params)
{
    if (params.fPowNoRetargeting)
        return pindexLast->nBits;

    // Limit adjustment step
    // 計算生成最近的2016個區塊實際花費了多少時間
    int64_t nActualTimespan = pindexLast->GetBlockTime() - nFirstBlockTime;
    //這裡需要限制調整的步長,即把實際花費的時間限制在0.5周和8周之間
    if (nActualTimespan < params.nPowTargetTimespan/4)//params.nPowTargetTimespan是2周,即20160分鐘
        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();
}

計算公式:新的難度目標值 = 舊的難度目標值 * 生成最近2016個區塊所花費的實際時間 / 系統期望生成2016個區塊的時間
其中程式碼中:nBits 即 舊的難度目標值,nActualTimespa 即 生成最近2016個區塊所花費的實際時間 ,
params.nPowTargetTimespan 即 系統期望生成2016個區塊的時間 。

2.1.3.3難度目標的表示
上面講了難度目標的計算方法,這裡再進一步講一下難度目標的表示方法,難度目標值用nBits表示,nBits是一個無符號的32位整數,定義在src/chain.h的CBlockIndex類中:

    uint32_t nBits;

這個無符號整數的最高位的1個位元組代表指數(exponent),低位的3個位元組代表係數(coefficient),這個記法將工作量證明的target表示為係數/指數(coefficient/exponent)的格式。
計算難度目標target的公式為:target = coefficient * 2^(8 * (exponent – 3))
例如在區塊277,316中,nBits的值為 0x1903a30c,在這個區塊裡,0x19為指數,而 0x03a30c為係數,計算難度值:

target = 0x03a30c * 2^(0x08 * (0x19 - 0x03))
=> target = 0x03a30c * 2^(0x08 * 0x16)
=> target = 0x03a30c * 2^0xB0

按十進位制計算為:

=> target = 238,348 * 2^176
=> target = 22,829,202,948,393,929,850,749,706,076,701,368,331,072,452,018,388,575,715,328

轉化回十六進位制後為:

=> target = 0x0000000000000003A30C00000000000000000000000000000000000000000000

上述過程就是由無符號的32位整數nBits轉為難度值的詳細步驟。

由無符號的32位整數nBits轉為難度值的函式
(如:0x1903a30c 轉為 0x0000000000000003A30C00000000000000000000000000000000000000000000 ):

// This implementation directly uses shifts instead of going
// through an intermediate MPI representation.
arith_uint256& arith_uint256::SetCompact(uint32_t nCompact, bool* pfNegative, bool* pfOverflow)
{
    int nSize = nCompact >> 24;
    uint32_t nWord = nCompact & 0x007fffff;
    if (nSize <= 3) {
        nWord >>= 8 * (3 - nSize);
        *this = nWord;
    } else {
        *this = nWord;
        *this <<= 8 * (nSize - 3);
    }
    if (pfNegative)
        *pfNegative = nWord != 0 && (nCompact & 0x00800000) != 0;
    if (pfOverflow)
        *pfOverflow = nWord != 0 && ((nSize > 34) ||
                                     (nWord > 0xff && nSize > 33) ||
                                     (nWord > 0xffff && nSize > 32));
    return *this;
}

由難度值轉為無符號的32位整數nBits的函式
(如:0x0000000000000003A30C00000000000000000000000000000000000000000000 轉為 0x1903a30c ):

uint32_t arith_uint256::GetCompact(bool fNegative) const
{
    int nSize = (bits() + 7) / 8;
    uint32_t nCompact = 0;
    if (nSize <= 3) {
        nCompact = GetLow64() << 8 * (3 - nSize);
    } else {
        arith_uint256 bn = *this >> 8 * (nSize - 3);
        nCompact = bn.GetLow64();
    }
    // The 0x00800000 bit denotes the sign.
    // Thus, if it is already set, divide the mantissa by 256 and increase the exponent.
    if (nCompact & 0x00800000) {
        nCompact >>= 8;
        nSize++;
    }
    assert((nCompact & ~0x007fffff) == 0);
    assert(nSize < 256);
    nCompact |= nSize << 24;
    nCompact |= (fNegative && (nCompact & 0x007fffff) ? 0x00800000 : 0);
    return nCompact;
}

這兩個方法定義在src/arith_uint256.h的 arith_uint256類中。

2.1.4 修改extranonce值,計算默克爾樹根節點hash

上述CreateNewBlock()方法包含了填寫nVersion ,nTime,hashPrevBlock,nBits,將nNonce初始化為0的操作,但是填寫區塊頭的hashMerkleRoot欄位的操作並不在CreateNewBlock()方法裡,而是在IncrementExtraNonce()方法裡:

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);
}

下面做進一步分析

2.1.4.1修改extranonce值
分析原始碼前先根據《精通比特幣》第八章 挖礦和共識 瞭解一下extranonce的用途

自2012年以來,比特幣挖礦發展出一個解決區塊頭結構的根本性限制的方案。在比特幣的早期,礦工可以通過遍歷隨機數
(Nonce)直到生成的雜湊值小於target從而挖出一個區塊。隨著難度的增加,礦工經常在嘗試了40億個nonce值後仍然沒有出塊。然而,這很容易通過更新區塊的時間戳並計算經過的時間來解決。因為時間戳是區塊頭的一部分,它的變化可以讓礦工再次遍歷nonce的值。然而,當挖礦硬體的速度超過了4GH/秒,這種方法變得越來越困難,因為隨機數的取值在一秒內就被用盡了。隨著ASIC礦機的出現並且超過了TH/秒的hash速率後,挖礦軟體為了找到有效的塊,需要更多的空間來儲存nonce值。時間戳可以延伸一點,但如果把它移動得太過遙遠(too far into the future),會導致區塊變為無效。區塊頭裡面需要一個新的“改變”的源頭。解決方案是使用coinbase交易作為額外隨機值的來源,因為coinbase指令碼可以儲存2-100位元組的資料,礦工們開始使用這個空間作為額外隨機值的空間,允許他們去探索一個大得多的區塊頭值的範圍來找到有效的區塊。這個coinbase交易包含在merkle樹中,這意味著任何coinbase指令碼的變化將導致Merkle根的變化。8個位元組的額外隨機數,加上4個位元組的“標準”隨機數,允許礦工每秒嘗試2^96(8後面跟28個零)種可能性而無需修改時間戳。如果未來礦工嘗試完了以上所有的可能性,他們還可以通過修改時間戳來解決。同樣,coinbase指令碼中也有更多的空間可以為將來隨機數的額外空間擴充套件做準備。

總結來說,就是原本區塊頭裡的32位無符號整數的nonce欄位空間太小了,最多隻能大約有40億(準確值是:2^32 - 1,即4294967295)種可能性,解決方案是在coinbase交易的輸入腳本里分配8個位元組作為額外隨機數的空間。這樣,8個位元組的額外隨機數,加上4個位元組的“標準”隨機數,就可以允許礦工每秒嘗試2^96(8後面跟28個零)種可能性而無需修改時間戳。
可以看到採用了這種方案後,礦工可以遍歷的可能性由 (2^32 - 1) 升到了 2^96 種。

好了,原理介紹完了,看下原始碼:

  txCoinbase.vin[0].scriptSig = (CScript() << nHeight << CScriptNum(nExtraNonce)) + COINBASE_FLAGS;

將nExtraNonce寫入到coinbase交易的scriptSig解鎖指令碼中。

2.1.4.2計算並填寫hashMerkleRoot欄位
由於coinbase交易包含在merkle樹中,這意味著任何coinbase指令碼的變化將導致Merkle根的變化,這也是為什麼計算hashMerkleRoot要在extranonce值更新之後再進行。看下BlockMerkleRoot()方法原始碼:

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(leaves, mutated);
}

最終會呼叫MerkleComputation()方法計算MerkleRoot的hash,該方法定義在src/consensus/merkle.cpp中:

/* This implements a constant-space merkle root/path calculator, limited to 2^32 leaves. */
static void MerkleComputation(const std::vector<uint256>& leaves, uint256* proot, bool* pmutated, uint32_t branchpos, std::vector<uint256>* pbranch) {
    if (pbranch) pbranch->clear();
    if (leaves.size() == 0) {
        if (pmutated) *pmutated = false;
        if (proot) *proot = uint256();
        return;
    }
    bool mutated = false;
    // count is the number of leaves processed so far.
    uint32_t count = 0;
    // inner is an array of eagerly computed subtree hashes, indexed by tree
    // level (0 being the leaves).
    // For example, when count is 25 (11001 in binary), inner[4] is the hash of
    // the first 16 leaves, inner[3] of the next 8 leaves, and inner[0] equal to
    // the last leaf. The other inner entries are undefined.
    uint256 inner[32];
    // Which position in inner is a hash that depends on the matching leaf.
    int matchlevel = -1;
    // First process all leaves into 'inner' values.
    while (count < leaves.size()) {
        uint256 h = leaves[count];
        bool matchh = count == branchpos;
        count++;
        int level;
        // For each of the lower bits in count that are 0, do 1 step. Each
        // corresponds to an inner value that existed before processing the
        // current leaf, and each needs a hash to combine it.
        for (level = 0; !(count & (((uint32_t)1) << level)); level++) {
            if (pbranch) {
                if (matchh) {
                    pbranch->push_back(inner[level]);
                } else if (matchlevel == level) {
                    pbranch->push_back(h);
                    matchh = true;
                }
            }
            mutated |= (inner[level] == h);
            CHash256().Write(inner[level].begin(), 32).Write(h.begin(), 32).Finalize(h.begin());
        }
        // Store the resulting hash at inner position level.
        inner[level] = h;
        if (matchh) {
            matchlevel = level;
        }
    }
    // Do a final 'sweep' over the rightmost branch of the tree to process
    // odd levels, and reduce everything to a single top value.
    // Level is the level (counted from the bottom) up to which we've sweeped.
    int level = 0;
    // As long as bit number level in count is zero, skip it. It means there
    // is nothing left at this level.
    while (!(count & (((uint32_t)1) << level))) {
        level++;
    }
    uint256 h = inner[level];
    bool matchh = matchlevel == level;
    while (count != (((uint32_t)1) << level)) {
        // If we reach this point, h is an inner value that is not the top.
        // We combine it with itself (Bitcoin's special rule for odd levels in
        // the tree) to produce a higher level one.
        if (pbranch && matchh) {
            pbranch->push_back(h);
        }
        CHash256().Write(h.begin(), 32).Write(h.begin(), 32).Finalize(h.begin());
        // Increment count to the value it would have if two entries at this
        // level had existed.
        count += (((uint32_t)1) << level);
        level++;
        // And propagate the result upwards accordingly.
        while (!(count & (((uint32_t)1) << level))) {
            if (pbranch) {
                if (matchh) {
                    pbranch->push_back(inner[level]);
                } else if (matchlevel == level) {
                    pbranch->push_back(h);
                    matchh = true;
                }
            }
            CHash256().Write(inner[level].begin(), 32).Write(h.begin(), 32).Finalize(h.begin());
            level++;
        }
    }
    // Return result.
    if (pmutated) *pmutated = mutated;
    if (proot) *proot = h;
}

2.2進行hash計算完成工作量證明
上面講了一個新的候選區塊的構建過程,包括計算並填寫了nVersion ,nTime,hashPrevBlock,nBits欄位,將nNonce欄位初始化為0,計算並填寫hashMerkleRoot欄位。一個新的候選區塊此時已經構建完成了,接下來就是不斷進行hash計算,使候選區塊成為一個有效的新區塊。

        //這裡就開始真正挖礦的hash計算了,不斷修改pblock->nNonce的值,計算hash,檢查是否滿足難度目標target(代表著工作量證明)
        while (nMaxTries > 0 && pblock->nNonce < nInnerLoopCount && !CheckProofOfWork(pblock->GetHash(), pblock->nBits, Params().GetConsensus())) {
            ++pblock->nNonce;
            --nMaxTries;
        }
        if (nMaxTries == 0) {
            break;
        }
        //如果nNonce值修改的次數已經超過了nInnerLoopCount規定的次數還沒有找到工作量證明的一個解,則廢棄這個候選區塊,重新回到前面建立一個新的候選區塊進行新一輪工作
        if (pblock->nNonce == nInnerLoopCount) {
            continue;
        }

以nInnerLoopCount(值為0x10000,即65536)次計算為一輪進行工作量證明的計算,若沒有找到一個解,則重新建立一個新的候選區塊進行新一輪工作。工作量證明的計算方法很清晰,就是將nNonce值不斷加1,並計算區塊的hash,根據CheckProofOfWork()方法判斷是否滿足難度目標:

bool CheckProofOfWork(uint256 hash, unsigned int nBits, const Consensus::Params& params)
{
    bool fNegative;
    bool fOverflow;
    arith_uint256 bnTarget;

    bnTarget.SetCompact(nBits, &fNegative, &fOverflow);