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í)~