比特币探究之工作量证明

比特币采用工作量证明(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();
}

写完了发现居然没有图。好吧,最后贴一张矿场图镇个场子、撑撑门面(图片来自百度,如有侵权烦请告知)。
比特币探究之工作量证明_第1张图片
轰鸣的矿场

欢迎转载,请注明出处。

你可能感兴趣的:(比特币探究之工作量证明)