比特幣采用工作量證明(POW)的方式來(lái)確保取得共識(shí)。在解析之前,先看一下比特幣定義的arith_uint256類,它可以按照預(yù)定規(guī)則,將32位無(wú)符號(hào)整數(shù),以類似浮點(diǎn)數(shù)的方式轉(zhuǎn)換為一個(gè)256位無(wú)符號(hào)整數(shù):
那么它代表的數(shù)字實(shí)際上為:最前面8位是以256為底的指數(shù),用e表示;
從前面數(shù)第9位是符號(hào)位,代表正負(fù),用s表示;
尾23位是尾數(shù),用m表示。

來(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)告知)。
歡迎轉(zhuǎn)載,請(qǐng)注明出處。