比特币采用工作量证明(POW)的方式来确保取得共识。在解析之前,先看一下比特币定义的arith_uint256类,它可以按照预定规则,将32位无符号整数,以类似浮点数的方式转换为一个256位无符号整数:
那么它代表的数字实际上为:最前面8位是以256为底的指数,用e表示;
从前面数第9位是符号位,代表正负,用s表示;
尾23位是尾数,用m表示。
来看源码src/arith_uint256.cpp中的SetCompact函数,就容易理解了:
//把uint32_t转换为uint256
arith_uint256& arith_uint256::SetCompact(uint32_t nCompact, bool* pfNegative, bool* pfOverflow)
{
int nSize = nCompact >> 24; //指数e
uint32_t nWord = nCompact & 0x007fffff; //尾数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; //是负值吗
if (pfOverflow)
*pfOverflow = nWord != 0 && ((nSize > 34) ||
(nWord > 0xff && nSize > 33) ||
(nWord > 0xffff && nSize > 32)); //有没有溢出
return *this;
}
如何证明完成工作量证明?比特币规定,只有当区块的hash值小于一个预定的难度值时,才被视为完成POW。这个预定难度值用一个uint256表示,最低难度powLimit被设定为0x00000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffff,即至少前32位是0。本文写作时,刚刚生成的第532871块的hash值为000000000000000000032d35bd3f1f7ec4417d7e35698cf76daba0f2a5d61db6,前78位都是0,可见当前挖矿难度之高。
现在可以看看CheckProofOfWork函数了,它在src/pow.cpp文件中:
bool CheckProofOfWork(uint256 hash, unsigned int nBits, const Consensus::Params& params)
{
bool fNegative;
bool fOverflow;
arith_uint256 bnTarget;
//nBits就是当前的难度值,调用SetCompact把它转换成uint256,一个表示难度的hash值
bnTarget.SetCompact(nBits, &fNegative, &fOverflow);
//如果是负值,或者溢出,或者小于最低难度,那么检查失败
if (fNegative || bnTarget == 0 || fOverflow || bnTarget > UintToArith256(params.powLimit))
return false;
//大于当前的难度hash,检查失败
if (UintToArith256(hash) > bnTarget)
return false;
//执行到这里,说明hash值比预定的难度hash还要小,说明已完成POW
return true;
}
上面的函数中,nBits代表挖矿难度,它是从哪里来的呢?回头再看看之前比特币探究之挖矿一文中的CreateNewBlock函数,其中有如下一行:
pblock->nBits = GetNextWorkRequired(pindexPrev, pblock, chainparams.GetConsensus());
GetNextWorkRequired函数同样定义在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();
//检查是否到了难度调整周期。比特币规定,每14天(2周)调整一次难度
//按照平均10分钟的区块生成速度,14天可生成2016个,即每过2016个块调整一次难度
if ((pindexLast->nHeight+1) % params.DifficultyAdjustmentInterval() != 0)
{
if (params.fPowAllowMinDifficultyBlocks)
{
//如果新块比上一个块晚了20分钟,那么允许最低难度挖块
//这个主要用在测试网络里,主网里是不允许的
if (pblock->GetBlockTime() > pindexLast->GetBlockTime() + params.nPowTargetSpacing*2)
return nProofOfWorkLimit;
else
{
//返回上一个正常难度
const CBlockIndex* pindex = pindexLast;
while (pindex->pprev && pindex->nHeight % params.DifficultyAdjustmentInterval() != 0 && pindex->nBits == nProofOfWorkLimit)
pindex = pindex->pprev;
return pindex->nBits;
}
}
//没到难度调整时间,直接返回上一个块的难度
return pindexLast->nBits;
}
//如果已经到了调整时间,向上回溯找到这一组2016块的首个区块
int nHeightFirst = pindexLast->nHeight - (params.DifficultyAdjustmentInterval()-1);
assert(nHeightFirst >= 0);
const CBlockIndex* pindexFirst = pindexLast->GetAncestor(nHeightFirst);
assert(pindexFirst);
//计算新的难度值
return CalculateNextWorkRequired(pindexLast, pindexFirst->GetBlockTime(), params);
}
当区块生成难度需要调整时,CalculateNextWorkRequired将被调用,以计算下一组区块的生成难度:
unsigned int CalculateNextWorkRequired(const CBlockIndex* pindexLast, int64_t nFirstBlockTime, const Consensus::Params& params)
{
if (params.fPowNoRetargeting)
return pindexLast->nBits;
//限定调整节奏,正常为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);
//新难度 = 旧难度 × 实际生成时间 / 预定生成时间
//如果旧难度低了,生成快了,实际生成时间短了,那么新难度自然就会提升
//所以,随着硬件越来越快,挖矿难度自然越来越大
bnNew *= nActualTimespan;
bnNew /= params.nPowTargetTimespan;
//难度也是有范围的,起码不能比最低难度还容易
if (bnNew > bnPowLimit)
bnNew = bnPowLimit;
return bnNew.GetCompact();
}
写完了发现居然没有图。好吧,最后贴一张矿场图镇个场子、撑撑门面(图片来自百度,如有侵权烦请告知)。欢迎转载,请注明出处。