BCH工作量證明源代碼分析

概述

Bitcoin Cash 源碼中,POW功能模塊,主要提供兩個(gè)函數(shù),供上層進(jìn)行調(diào)用:

  1. GetNextWorkRequired: 獲取下個(gè)塊的工作量(即難度)
  2. CheckProofOfWork: 檢查塊的工作量是否合法。 true:合法; false:不合法。
    下面是詳細(xì)分析

獲取下個(gè)塊的難度

uint32_t GetNextWorkRequired(const CBlockIndex *pindexPrev,
    const CBlockHeader *pblock, const Consensus::Params &params) {
    
     // Genesis block
    if (pindexPrev == nullptr) {
        return UintToArith256(params.powLimit).GetCompact();
    }

    // Special rule for regtest: we never retarget.
    if (params.fPowNoRetargeting) {
        return pindexPrev->nBits;
    }

    if (pindexPrev->GetMedianTimePast() >=
        GetArg("-newdaaactivationtime", params.cashHardForkActivationTime)) {
        return GetNextCashWorkRequired(pindexPrev, pblock, params);
    }

    return GetNextEDAWorkRequired(pindexPrev, pblock, params);
}
  • 參數(shù),pindexprev : 當(dāng)前區(qū)塊的父區(qū)塊(In); pblock : 當(dāng)前區(qū)塊(In),主要使用了其中的時(shí)間戳字段; param : 當(dāng)前的鏈參數(shù)
  • 如果為上個(gè)區(qū)塊為創(chuàng)世塊,直接返回當(dāng)前鏈參數(shù)配置的最低難度。
  • 如果當(dāng)前的鏈為回歸測(cè)試鏈(regtest 測(cè)試鏈),返回與上個(gè)區(qū)塊一樣的難度
  • 如果上個(gè)區(qū)塊的MTP時(shí)間 >= CashHardWokd(硬分叉難度調(diào)整DAA)的激活時(shí)間,那采用新的難度算法
  • 采用以前的難度算法

BCH的難度調(diào)整

uint32_t GetNextCashWorkRequired(const CBlockIndex *pindexPrev,
        const CBlockHeader *pblock, const Consensus::Params &params) {
    // This cannot handle the genesis block and early blocks in general.
    assert(pindexPrev);

    // 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 (params.fPowAllowMinDifficultyBlocks &&
        (pblock->GetBlockTime() >
         pindexPrev->GetBlockTime() + 2 * params.nPowTargetSpacing)) {
        return UintToArith256(params.powLimit).GetCompact();
    }

    // Compute the difficulty based on the full adjustement interval.
    const uint32_t nHeight = pindexPrev->nHeight;
    assert(nHeight >= params.DifficultyAdjustmentInterval());

    // Get the last suitable block of the difficulty interval.
    const CBlockIndex *pindexLast = GetSuitableBlock(pindexPrev);
    assert(pindexLast);

    // Get the first suitable block of the difficulty interval.
    uint32_t nHeightFirst = nHeight - 144;
    const CBlockIndex *pindexFirst =
        GetSuitableBlock(pindexPrev->GetAncestor(nHeightFirst));
    assert(pindexFirst);

    // Compute the target based on time and work done during the interval.
    const arith_uint256 nextTarget =
        ComputeTarget(pindexFirst, pindexLast, params);

    const arith_uint256 powLimit = UintToArith256(params.powLimit);
    if (nextTarget > powLimit) {
        return powLimit.GetCompact();
    }

    return nextTarget.GetCompact()
}
  • 如果當(dāng)前鏈為測(cè)試鏈(testnet 測(cè)試鏈),并且當(dāng)前塊的時(shí)間與上個(gè)區(qū)塊的時(shí)間間隔大于nPowTargetSpacing *2,允許下個(gè)塊采用當(dāng)前鏈的最低難度
  • 獲取上個(gè)區(qū)塊的往上3個(gè)塊的中值區(qū)塊,作為結(jié)束位置
  • 獲取當(dāng)前上個(gè)區(qū)塊的第144個(gè)祖先區(qū)塊的中值區(qū)塊,作為起始位置
  • 依據(jù)起始位置,結(jié)束位置,和鏈參數(shù)計(jì)算下個(gè)塊的難度(工作量)work
  • 當(dāng)下個(gè)塊的難度低于當(dāng)前鏈最低難度時(shí),返回當(dāng)前鏈的最低難度;否則返回計(jì)算后的難度
  • 總結(jié):現(xiàn)階段采用的算法是:進(jìn)行逐塊調(diào)整難度,調(diào)整機(jī)制如下

BCH采用的難度計(jì)算

/**
 * Compute the a target based on the work done between 2 blocks and the time
 * required to produce that work.
 */
static arith_uint256 ComputeTarget(const CBlockIndex *pindexFirst,
                                   const CBlockIndex *pindexLast,
                                   const Consensus::Params &params) {
    assert(pindexLast->nHeight > pindexFirst->nHeight);

    /**
     * From the total work done and the time it took to produce that much work,
     * we can deduce how much work we expect to be produced in the targeted time
     * between blocks.
     */
std::cout << "pindexLast->height : " << pindexLast->nHeight << ", pindexLast->nChainWork : " << pindexLast->nChainWork.GetCompact() <<
          ", pindexFirst->nHeight : " << pindexFirst->nHeight << ", pindexFirst->nChainWork : " << pindexFirst->nChainWork.GetCompact() << std::endl;
    arith_uint256 work = pindexLast->nChainWork - pindexFirst->nChainWork;
    work *= params.nPowTargetSpacing;

    // In order to avoid difficulty cliffs, we bound the amplitude of the
    // adjustement we are going to do.
    assert(pindexLast->nTime > pindexFirst->nTime);
    int64_t nActualTimespan = pindexLast->nTime - pindexFirst->nTime;
    if (nActualTimespan > 288 * params.nPowTargetSpacing) {
        nActualTimespan = 288 * params.nPowTargetSpacing;
    } else if (nActualTimespan < 72 * params.nPowTargetSpacing) {
        nActualTimespan = 72 * params.nPowTargetSpacing;
    }

    work /= nActualTimespan;

    /**
     * We need to compute T = (2^256 / W) - 1 but 2^256 doesn't fit in 256 bits.
     * By expressing 1 as W / W, we get (2^256 - W) / W, and we can compute
     * 2^256 - W as the complement of W.
     */
    return (-work) / work;
}
  • 計(jì)算起始位置至結(jié)束位置累計(jì)的工作量
  • 根據(jù)實(shí)際出塊時(shí)間與目標(biāo)出塊時(shí)間進(jìn)行調(diào)整
  • 盡量保證在1天之內(nèi)出144個(gè)塊,保證10分鐘一個(gè)塊
  • 如果一天之內(nèi)超過了144個(gè)塊,則需要增加難度,反之就要降低難度
  • 為了保證難度調(diào)整算法的不出現(xiàn)劇烈波動(dòng),一天的出塊時(shí)間最多不超過288個(gè),最少不低于72個(gè)
  • 最后返回將計(jì)算后的難度

BCH以前采用的EDA難度調(diào)整算法

采用EDA的算法計(jì)算下個(gè)塊的難度:

uint32_t GetNextEDAWorkRequired(const CBlockIndex *pindexPrev,
    const CBlockHeader *pblock, const Consensus::Params &params) {
    // Only change once per difficulty adjustment interval
    uint32_t nHeight = pindexPrev->nHeight + 1;
    if (nHeight % params.DifficultyAdjustmentInterval() == 0) {
        // Go back by what we want to be 14 days worth of blocks
        assert(nHeight >= params.DifficultyAdjustmentInterval());
        uint32_t nHeightFirst = nHeight - params.DifficultyAdjustmentInterval();
        const CBlockIndex *pindexFirst = pindexPrev->GetAncestor(nHeightFirst);
        assert(pindexFirst);

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

    const uint32_t nProofOfWorkLimit =
        UintToArith256(params.powLimit).GetCompact();

    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() >
            pindexPrev->GetBlockTime() + 2 * params.nPowTargetSpacing) {
            return nProofOfWorkLimit;
        }

        // Return the last non-special-min-difficulty-rules-block
        const CBlockIndex *pindex = pindexPrev;
        while (pindex->pprev &&
               pindex->nHeight % params.DifficultyAdjustmentInterval() != 0 &&
               pindex->nBits == nProofOfWorkLimit) {
            pindex = pindex->pprev;
        }

        return pindex->nBits;
    }
    // We can't go bellow the minimum, so early bail.
    uint32_t nBits = pindexPrev->nBits;
    if (nBits == nProofOfWorkLimit) {
        return nProofOfWorkLimit;
    }

    // If producing the last 6 block took less than 12h, we keep the same
    // difficulty.
    const CBlockIndex *pindex6 = pindexPrev->GetAncestor(nHeight - 7);
    assert(pindex6);
    int64_t mtp6blocks =
        pindexPrev->GetMedianTimePast() - pindex6->GetMedianTimePast();
    if (mtp6blocks < 12 * 3600) {
        return nBits;
    }

    // If producing the last 6 block took more than 12h, increase the difficulty
    // target by 1/4 (which reduces the difficulty by 20%). This ensure the
    // chain do not get stuck in case we lose hashrate abruptly.
    arith_uint256 nPow;
    nPow.SetCompact(nBits);

    nPow += (nPow >> 2);

    // Make sure we do not go bellow allowed values.
    const arith_uint256 bnPowLimit = UintToArith256(params.powLimit);
    if (nPow > bnPowLimit) nPow = bnPowLimit;

    return nPow.GetCompact();
    ......
}
  • 每2016個(gè)塊調(diào)整一次難度nHeight % params.DifficultyAdjustmentInterval() == 0, 符合難度條件,則進(jìn)入難度判斷:
    • 獲取計(jì)算起始位置的塊索引,依據(jù):起始位置,結(jié)束位置,鏈參數(shù) 計(jì)算下個(gè)塊的難度
  • 如果當(dāng)前鏈為測(cè)試鏈(testnet),進(jìn)入下面邏輯
    1. 當(dāng)前塊與上個(gè)區(qū)塊的時(shí)間間隔大于 nPowTargetSpacing * 2,返回最低難度。
    2. 返回最后一個(gè)不等于最低難度的塊的難度
  • 如果難度不在調(diào)整周期,并且上個(gè)區(qū)塊的難度為當(dāng)前鏈參數(shù)的最低難度,直接返回最低難度
  • 如果6個(gè)祖先塊的MTP時(shí)間間隔小于12小時(shí),直接返回上個(gè)區(qū)塊的難度
  • 不然就降低到當(dāng)前難度1/4的難度:nPow += (nPow >> 2);
  • 當(dāng)下個(gè)塊的難度低于當(dāng)前鏈最低難度時(shí),返回當(dāng)前鏈的最低難度;否則返回計(jì)算后的難度
    總結(jié):以前的難度調(diào)節(jié)機(jī)制是,主要分為兩種:每隔2016個(gè)塊params.DifficultyAdjustmentInterval()進(jìn)行一次大的難度調(diào)整。在難度穩(wěn)定期間(相對(duì)來說),每6個(gè)塊進(jìn)行一次判斷,看是否需要進(jìn)行難度調(diào)整,如果6個(gè)塊的出塊時(shí)間大于12小時(shí),將上個(gè)區(qū)塊的難度降低1/4,作為下個(gè)塊的難度。

EDA所采用的難度計(jì)算方法

依據(jù)起始位置,結(jié)束位置,鏈參數(shù),計(jì)算下個(gè)塊的難度

uint32_t CalculateNextWorkRequired(const CBlockIndex *pindexPrev,
        if (params.fPowNoRetargeting) {
        return pindexPrev->nBits;
    }
    // Limit adjustment step
    int64_t nActualTimespan = pindexPrev->GetBlockTime() - nFirstBlockTime;
    if (nActualTimespan < params.nPowTargetTimespan / 4) {
        nActualTimespan = params.nPowTargetTimespan / 4;
    }
    if (nActualTimespan > params.nPowTargetTimespan * 4) {
        nActualTimespan = params.nPowTargetTimespan * 4;
    }
    std::cout << "nActualTimespan : " << nActualTimespan << std::endl;

    // Retarget
    const arith_uint256 bnPowLimit = UintToArith256(params.powLimit);
    arith_uint256 bnNew;
    bnNew.SetCompact(pindexPrev->nBits);
    bnNew *= nActualTimespan;
    bnNew /= params.nPowTargetTimespan;

    if (bnNew > bnPowLimit) bnNew = bnPowLimit;

    return bnNew.GetCompact();
}
  • 如果為回歸測(cè)試鏈,直接返回上個(gè)區(qū)塊的難度
  • 計(jì)算實(shí)際的時(shí)間間隔
  • 當(dāng)實(shí)際時(shí)間間隔 < 預(yù)定目標(biāo)的1/4時(shí),將下階段的時(shí)間間隔設(shè)為預(yù)定目標(biāo)的1/4;或當(dāng)實(shí)際時(shí)間間隔 > 預(yù)定目標(biāo)的4倍時(shí),將下階段的時(shí)間間隔設(shè)為預(yù)定目標(biāo)的4倍。
  • 計(jì)算新的難度bnNew *= nActualTimespan;;
  • 當(dāng)新難度低于當(dāng)前鏈最低難度時(shí),直接返回最低難度;否則返回計(jì)算后的新難度。
    可以看出以前的難度調(diào)整算法:以4基礎(chǔ)進(jìn)行調(diào)整。當(dāng)難度太小時(shí),即出塊的時(shí)間變短,將下階段的時(shí)間增加至目標(biāo)時(shí)間的1/4,進(jìn)行緩慢增加難度;當(dāng)難度太大時(shí),即出塊的時(shí)間變長(zhǎng),將下階段時(shí)間降低至目標(biāo)時(shí)間的4倍,緩慢降低難度;上述調(diào)節(jié)措施可以避免難度的劇烈波動(dòng)。

塊頭工作量的檢查

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

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

    // Check range
    if (fNegative || bnTarget == 0 || fOverflow ||
        bnTarget > UintToArith256(params.powLimit)) {
        return false;
    }

    // Check proof of work matches claimed amount
    if (UintToArith256(hash) > bnTarget) {
        return false;
    }

    return true;
}
  • 參數(shù),hash 將要檢查的區(qū)塊哈希;nBits 該區(qū)塊中的難度字段;param:當(dāng)前鏈參數(shù)
  • 將難度編碼為BCH中指定大數(shù)類型,判斷編碼過程中是否有溢出,負(fù)數(shù),或難度小于當(dāng)前鏈的最低難度情況,如果存在,返回false。
  • 將hash轉(zhuǎn)換為BCH中指定的大數(shù)類型,與塊頭難度編碼后的值進(jìn)行比較。如果大于塊頭難度,返回false。否則返回true。
    該函數(shù)用來判斷:塊頭哈希與塊中聲明的難度是否吻合(即該區(qū)塊的工作量是否正確,不依賴于上下文)。

本文由 Copernicus團(tuán)隊(duì) 姚永芯寫作,轉(zhuǎn)載無(wú)需授權(quán)。

最后編輯于
?著作權(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)容