比特幣探究之工作量證明

比特幣采用工作量證明(POW)的方式來(lái)確保取得共識(shí)。在解析之前,先看一下比特幣定義的arith_uint256類,它可以按照預(yù)定規(guī)則,將32位無(wú)符號(hào)整數(shù),以類似浮點(diǎn)數(shù)的方式轉(zhuǎn)換為一個(gè)256位無(wú)符號(hào)整數(shù):

最前面8位是以256為底的指數(shù),用e表示;
從前面數(shù)第9位是符號(hào)位,代表正負(fù),用s表示;
尾23位是尾數(shù),用m表示。

那么它代表的數(shù)字實(shí)際上為:

來(lái)看源碼src/arith_uint256.cpp中的SetCompact函數(shù),就容易理解了:

//把uint32_t轉(zhuǎn)換為uint256
arith_uint256& arith_uint256::SetCompact(uint32_t nCompact, bool* pfNegative, bool* pfOverflow)
{
    int nSize = nCompact >> 24;  //指數(shù)e
    uint32_t nWord = nCompact & 0x007fffff;  //尾數(shù)m
    if (nSize <= 3) {
        nWord >>= 8 * (3 - nSize);
        *this = nWord;
    } else {
        *this = nWord;
        *this <<= 8 * (nSize - 3);
    }
    if (pfNegative)
        *pfNegative = nWord != 0 && (nCompact & 0x00800000) != 0;  //是負(fù)值嗎
    if (pfOverflow)
        *pfOverflow = nWord != 0 && ((nSize > 34) ||
                                     (nWord > 0xff && nSize > 33) ||
                                     (nWord > 0xffff && nSize > 32));  //有沒(méi)有溢出
    return *this;
}

如何證明完成工作量證明?比特幣規(guī)定,只有當(dāng)區(qū)塊的hash值小于一個(gè)預(yù)定的難度值時(shí),才被視為完成POW。這個(gè)預(yù)定難度值用一個(gè)uint256表示,最低難度powLimit被設(shè)定為0x00000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffff,即至少前32位是0。本文寫作時(shí),剛剛生成的第532871塊的hash值為000000000000000000032d35bd3f1f7ec4417d7e35698cf76daba0f2a5d61db6,前78位都是0,可見(jiàn)當(dāng)前挖礦難度之高。

現(xiàn)在可以看看CheckProofOfWork函數(shù)了,它在src/pow.cpp文件中:

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

    //nBits就是當(dāng)前的難度值,調(diào)用SetCompact把它轉(zhuǎn)換成uint256,一個(gè)表示難度的hash值
    bnTarget.SetCompact(nBits, &fNegative, &fOverflow);

    //如果是負(fù)值,或者溢出,或者小于最低難度,那么檢查失敗
    if (fNegative || bnTarget == 0 || fOverflow || bnTarget > UintToArith256(params.powLimit))
        return false;

    //大于當(dāng)前的難度hash,檢查失敗
    if (UintToArith256(hash) > bnTarget)
        return false;

    //執(zhí)行到這里,說(shuō)明hash值比預(yù)定的難度hash還要小,說(shuō)明已完成POW
    return true;
}

上面的函數(shù)中,nBits代表挖礦難度,它是從哪里來(lái)的呢?回頭再看看之前比特幣探究之挖礦一文中的CreateNewBlock函數(shù),其中有如下一行:

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

GetNextWorkRequired函數(shù)同樣定義在src/pow.cpp中:

unsigned int GetNextWorkRequired(const CBlockIndex* pindexLast, const CBlockHeader *pblock, const Consensus::Params& params)
{
    assert(pindexLast != nullptr);
    unsigned int nProofOfWorkLimit = UintToArith256(params.powLimit).GetCompact();

    //檢查是否到了難度調(diào)整周期。比特幣規(guī)定,每14天(2周)調(diào)整一次難度
    //按照平均10分鐘的區(qū)塊生成速度,14天可生成2016個(gè),即每過(guò)2016個(gè)塊調(diào)整一次難度
    if ((pindexLast->nHeight+1) % params.DifficultyAdjustmentInterval() != 0)
    {
        if (params.fPowAllowMinDifficultyBlocks)
        {
            //如果新塊比上一個(gè)塊晚了20分鐘,那么允許最低難度挖塊
            //這個(gè)主要用在測(cè)試網(wǎng)絡(luò)里,主網(wǎng)里是不允許的
            if (pblock->GetBlockTime() > pindexLast->GetBlockTime() + params.nPowTargetSpacing*2)
                return nProofOfWorkLimit;
            else
            {
                //返回上一個(gè)正常難度
                const CBlockIndex* pindex = pindexLast;
                while (pindex->pprev && pindex->nHeight % params.DifficultyAdjustmentInterval() != 0 && pindex->nBits == nProofOfWorkLimit)
                    pindex = pindex->pprev;
                return pindex->nBits;
            }
        }
        //沒(méi)到難度調(diào)整時(shí)間,直接返回上一個(gè)塊的難度
        return pindexLast->nBits;
    }

    //如果已經(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);
}

當(dāng)區(qū)塊生成難度需要調(diào)整時(shí),CalculateNextWorkRequired將被調(diào)用,以計(jì)算下一組區(qū)塊的生成難度:

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

    //限定調(diào)整節(jié)奏,正常為14天,最小3.5天,最大56天
    int64_t nActualTimespan = pindexLast->GetBlockTime() - nFirstBlockTime;
    if (nActualTimespan < params.nPowTargetTimespan/4)
        nActualTimespan = params.nPowTargetTimespan/4;
    if (nActualTimespan > params.nPowTargetTimespan*4)
        nActualTimespan = params.nPowTargetTimespan*4;

    const arith_uint256 bnPowLimit = UintToArith256(params.powLimit);
    arith_uint256 bnNew;
    bnNew.SetCompact(pindexLast->nBits);
    //新難度 = 舊難度 × 實(shí)際生成時(shí)間 / 預(yù)定生成時(shí)間
    //如果舊難度低了,生成快了,實(shí)際生成時(shí)間短了,那么新難度自然就會(huì)提升
    //所以,隨著硬件越來(lái)越快,挖礦難度自然越來(lái)越大
    bnNew *= nActualTimespan;
    bnNew /= params.nPowTargetTimespan;
    //難度也是有范圍的,起碼不能比最低難度還容易
    if (bnNew > bnPowLimit)
        bnNew = bnPowLimit;

    return bnNew.GetCompact();
}

寫完了發(fā)現(xiàn)居然沒(méi)有圖。好吧,最后貼一張礦場(chǎng)圖鎮(zhèn)個(gè)場(chǎng)子、撐撐門面(圖片來(lái)自百度,如有侵權(quán)煩請(qǐng)告知)。
轟鳴的礦場(chǎng)

歡迎轉(zhuǎn)載,請(qǐng)注明出處。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

友情鏈接更多精彩內(nèi)容