Bitcoin中的PoW共識(shí)機(jī)制代碼解讀

PoW共識(shí)機(jī)制又雙來(lái)了

之前已經(jīng)講了兩篇關(guān)于PoW的內(nèi)容,基本上把PoW介紹的很詳細(xì)了(個(gè)人水平有限),那么我們是否可以通過(guò)bitcoin中的源碼再對(duì)PoW有一個(gè)更深入的了解呢?當(dāng)時(shí)是可以的,接下來(lái),咱們就從Bitcoin的源碼入手,再來(lái)詳細(xì)的分析一波:

CheckProofOfWork函數(shù)

首先我們來(lái)說(shuō)對(duì)于PoW最為核心的代碼部分,很短,但是很重要:

bool CheckProofOfWork(uint256 hash, unsigned int nBits, const Consensus::Params& params)
{
    bool fNegative;
    bool fOverflow;
    arith_uint256 bnTarget;
    // nBits就是當(dāng)前的一個(gè)難度值,通過(guò)使用SetCompact函數(shù)將nBits轉(zhuǎn)換為長(zhǎng)度為256的類似于hash值的一個(gè)值
    bnTarget.SetCompact(nBits, &fNegative, &fOverflow);

    // 對(duì)這些內(nèi)容進(jìn)行檢查,1)hash值為負(fù)數(shù),2)bnTarget通過(guò)SetCompact獲得的值為0,3)溢出,4)目標(biāo)難度值比最低難度值還要低
    if (fNegative || bnTarget == 0 || fOverflow || bnTarget > UintToArith256(params.powLimit))
        return false;

    // Check proof of work matches claimed amount
    // 如果所得hash值經(jīng)過(guò)轉(zhuǎn)換后比當(dāng)前難度值的hash值要大,則返回失敗
    if (UintToArith256(hash) > bnTarget)
        return false;

    return true;
}

整個(gè)代碼當(dāng)中有一部分內(nèi)容需要著重注意:

bnTarget > UintToArith256(params.powLimit)

這一句的代碼顯示的內(nèi)容應(yīng)該是bnTarget大于powLimit(最低難度)的256轉(zhuǎn)換值,但是我在注釋里面寫的命名是要小于最低難度啊,這是為什么呢?

在這里是一個(gè)我們需要注意的內(nèi)容:
我們雖然總是說(shuō)判斷的是難度值的大小,但是根據(jù)bitcoin中的源碼的分析來(lái)說(shuō),他的bitcoin的大小是和數(shù)值正好相反的,也就是說(shuō),數(shù)值越大,難度越?。?/code>

這樣就可以很好的解決前面的問(wèn)題了。

但是我們也不要和后面的那一句混了:

UintToArith256(hash) > bnTarget

這一句判斷的是最終類hash值與目標(biāo)難度hash值的比較,這個(gè)比較還是按照我們正常的規(guī)定來(lái)理解就好,一旦找到比target小的值,就可以直接返回true,也就是找到了我們想要的那個(gè)nonce值。

GetNextWorkRequired函數(shù)

這個(gè)函數(shù)的主要功能是用來(lái)進(jìn)行判斷的,判斷的是什么呢?我們?cè)?a href="http://www.lxweimin.com/p/6f8334276c7c" target="_blank">上一篇文章中說(shuō)到過(guò)我們要在一定時(shí)間后對(duì)難度進(jìn)行調(diào)整,這里判斷的就是判斷是否需要對(duì)難度進(jìn)行調(diào)整。

unsigned int GetNextWorkRequired(const CBlockIndex* pindexLast, const CBlockHeader *pblock, const Consensus::Params& params)
{
    assert(pindexLast != nullptr);
    unsigned int nProofOfWorkLimit = UintToArith256(params.powLimit).GetCompact();
    // 每個(gè)難度周期僅進(jìn)行一次周期的調(diào)整
    // 檢查是否到達(dá)難度調(diào)整時(shí)間,一個(gè)周期是指每?jī)芍苓M(jìn)行一次調(diào)整,10分鐘一塊,2016塊,總共20160分鐘

    // Only change once per difficulty adjustment interval
    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
    // 如果已經(jīng)到了調(diào)整的時(shí)間,向上回溯找到這一組2016個(gè)區(qū)塊的首地址
    int nHeightFirst = pindexLast->nHeight - (params.DifficultyAdjustmentInterval()-1);
    assert(nHeightFirst >= 0);
    const CBlockIndex* pindexFirst = pindexLast->GetAncestor(nHeightFirst);
    assert(pindexFirst);
    // 重新計(jì)算難度值
    return CalculateNextWorkRequired(pindexLast, pindexFirst->GetBlockTime(), params);
}

這是Bitcoin中對(duì)于是否要調(diào)整難度進(jìn)行的判斷,那么我們還看到三行很明顯的注釋:

// 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.

我們發(fā)現(xiàn),里面還有一段是對(duì)test,也就是測(cè)試網(wǎng)絡(luò)進(jìn)行難度調(diào)節(jié)的內(nèi)容,我們可以根據(jù)他的注釋看出他的難度調(diào)整和我們主網(wǎng)上是不一樣的,但是我們也不太需要在意。

我們看函數(shù)整體,只要我們的判斷他已經(jīng)到了需要調(diào)整的時(shí)間,我們要做的就是兩步:
1) 向上追溯2016個(gè)塊,并計(jì)算相應(yīng)的生成時(shí)間;
2) 重新計(jì)算難度值
第一步的代碼很簡(jiǎn)單,所以不用贅述。

我們主要再來(lái)看一下關(guān)于重新計(jì)算難度的函數(shù)。

CalculateNextWorkRequired函數(shù)

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

    // Limit adjustment step
    // 難度調(diào)整步驟
    // 如果生成時(shí)間小于1/4,就按照1/4來(lái)計(jì)算,大于4倍,就按照4倍來(lái)計(jì)算,以防止出現(xiàn)難度偏差過(guò)大的現(xiàn)象。
    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);   // 首先將上一個(gè)區(qū)塊的難度置到bnNew當(dāng)中
    // 新難度 = 舊難度 * 實(shí)際生成時(shí)間 / 預(yù)定生成時(shí)間
    bnNew *= nActualTimespan;
    bnNew /= params.nPowTargetTimespan;

    // 難度也是有最低值的,起碼不能比最低難度還容易
    if (bnNew > bnPowLimit)
        bnNew = bnPowLimit;

    return bnNew.GetCompact();
}

這就是關(guān)于計(jì)算難度的函數(shù),內(nèi)容也是很好理解的,我們選擇幾點(diǎn)比較重要的來(lái)說(shuō):

1.nActualTimespan的計(jì)算
int64_t nActualTimespan = pindexLast->GetBlockTime() - nFirstBlockTime;
    if (nActualTimespan < params.nPowTargetTimespan/4)
        nActualTimespan = params.nPowTargetTimespan/4;
    if (nActualTimespan > params.nPowTargetTimespan*4)
        nActualTimespan = params.nPowTargetTimespan*4;

這一部分的代碼就是用來(lái)計(jì)算實(shí)際使用時(shí)間的代碼,首先肯定是左后一塊的時(shí)間減去第一塊的時(shí)間,但接下來(lái)還有兩個(gè)if判斷語(yǔ)句,意思也很好理解,如果實(shí)際花費(fèi)時(shí)間小于理論時(shí)間的1/4,就按照1/4來(lái)處理;如果實(shí)際花費(fèi)時(shí)間大于理論時(shí)間的4倍,就按照4倍來(lái)處理。這個(gè)理由也很簡(jiǎn)單,如果相差的時(shí)間太大的話,那么理論上調(diào)整的難度的大小的范圍就會(huì)很大,因此我們需要將其控制在一定的范圍,那么在bitcoin中規(guī)定,最小不低于1/4,最大不超過(guò)4倍。

2.bnNew的計(jì)算

我們發(fā)現(xiàn)bnNew也就是我們想要求的那個(gè)新的難度值,求解公式也很簡(jiǎn)單:

 arith_uint256 bnNew;
 bnNew.SetCompact(pindexLast->nBits);  
 bnNew *= nActualTimespan;
 bnNew /= params.nPowTargetTimespan;

我們定義bnNew之后,就使用我們的公式:

新難度 = 舊難度 * 實(shí)際生成時(shí)間 / 預(yù)定生成時(shí)間

就可以計(jì)算出來(lái)新的難度值,那么我們計(jì)算后,還是剛才那個(gè)問(wèn)題,一定不能忽視,就是難度越大,數(shù)值越?。?/code>因?yàn)槲覀內(nèi)绻?code>nActualTimespan計(jì)算出來(lái)比20160(2016 * 10)分鐘小的時(shí)候,最后得到的 bnNew是比之前要小的,但是我們想要的是比他大的值,因此我們一定要好好理解這個(gè)內(nèi)容。

總結(jié)

以上就是對(duì)于bitcoin源碼中關(guān)于pow的幾個(gè)函數(shù)的解讀,其實(shí)總體看來(lái),還是很簡(jiǎn)單的,因?yàn)槲覀冎恍枰斫?個(gè)函數(shù)的功能即可,里面的一些參數(shù),我們可以按照他的定義一一理解即可,因此,我們對(duì)于bitcoin的理解又加深了一步,加油!

區(qū)塊鏈真的是一個(gè)很有意思的東西,歡迎大家一起來(lái)交流學(xué)習(xí)~

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

推薦閱讀更多精彩內(nèi)容

  • POW:Proof of Work,工作量證明 共識(shí)是一個(gè)多維博弈過(guò)程 1,共識(shí)越分散(節(jié)點(diǎn)多,參與度高),效率越...
    rectinajh閱讀 18,910評(píng)論 1 5
  • 課堂是教育的主陣地,那么課堂上最重要的資源是什么呢?這個(gè)問(wèn)題直到上個(gè)星期我才意識(shí)到是老師的注意力。我從老師和學(xué)生兩...
    浪上頭了閱讀 406評(píng)論 0 0
  • 2016.6.3周五。晴但卻不怎么明朗的天,一大早背著雙肩包去銀行辦理業(yè)務(wù)然后回公司。就這樣開始了一天的工作,然后...
    元?dú)鉂M滿的慧慧醬閱讀 294評(píng)論 0 0
  • 第一天跑步是種什么感覺? 決定跑步的那一刻起,就覺得自己走上了逆襲的路。定好鬧鐘,興奮了半個(gè)晚上,早上如期提前10...
    alisawjl閱讀 757評(píng)論 0 0
  • To u: 不經(jīng)意間,認(rèn)識(shí)這么久了,真的白駒過(guò)隙呀,好希望時(shí)間走慢一點(diǎn)呢。 回憶在一起的時(shí)光,沒(méi)有什么記憶深刻的...
    小考拉俱樂(lè)部閱讀 256評(píng)論 2 2