概述
Bitcoin Cash 源碼中,POW功能模塊,主要提供兩個(gè)函數(shù),供上層進(jìn)行調(diào)用:
-
GetNextWorkRequired: 獲取下個(gè)塊的工作量(即難度) -
CheckProofOfWork: 檢查塊的工作量是否合法。 true:合法; false:不合法。
下面是詳細(xì)分析
獲取下個(gè)塊的難度
uint32_t GetNextWorkRequired(const CBlockIndex *pindexPrev,
const CBlockHeader *pblock, const Consensus::Params ¶ms) {
// 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 ¶ms) {
// 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 ¶ms) {
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 ¶ms) {
// 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)入下面邏輯
- 當(dāng)前塊與上個(gè)區(qū)塊的時(shí)間間隔大于 nPowTargetSpacing * 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 ¶ms) {
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)。