区块链学习(4) 难度目标调整

1、最大难度目标
中本聪规定:
0x00000000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF
为最大目标值,区块要被比特币网络接受其哈希值必须要小于最大目标值。

2、难度目标的存储 
以压缩格式存储在区块头部的nbits字段中,公式如下:
target = coefficient*2^(8*(exponent-3))
例如创世块的难度目标为:0x1D00FFFF,根据公式展开为:
0x00FFFF * 2^(8*(0x1D-3)) = 0x00000000FFFF0000000000000000000000000000000000000000000000000000
可见压缩后精度是有损失的。不过这个也无关紧要。
(8*(exponent-3))/4 = 2*(exponent-3) 可以得出十六进制难度系数后面有多少个0。
0x1D00FFFF 展开后 系数00FFFF后面可以算出有52个0。

3、难度目标的调整
比特币设定为每10分钟生成一个区块,每2016个区块,自动调整一次难度。
难度调整的公式为:
新难度值 = 旧难度值 * (之前2016个区块花费时长(单位秒) / 2016*10*60)
不难看出这个公式的意思是:如果当前算力比较大、出块的速度比较块,那么就加大挖矿难度,反之就降低挖矿难度。

4、难度目标的计算
先来一个小程序扫描一下区块网络中的难度信息。
通过命令 curl -s https://chain.api.btc.com/v3/block/0 来获取区块信息,然后解析出相关的难度信息。
btc.com不太稳定,有时会返回失败。可以多试几次。

// xmain.c
// by maxzero
#include "xutils.h"

int grab_block_info(int block_num, char *block_buf, int block_len)
{
    FILE* f = NULL;
    int   n = 0;
    int   r = 0;

    char  url[1024];
    char* buf = NULL;
    int   len = 0;

    memset(url, 0x00, sizeof(url));
    sprintf(url, "curl -s https://chain.api.btc.com/v3/block/%d", block_num);
    f = popen(url, "r");

    buf = block_buf;
    len = block_len;

    memset(buf, 0x00, len);
    n = fread(buf, 1, len, f);
    if (n <= 0) {
        xprint_err("fread() failed. n=%d\n", n);
        r = -1;
    }
    
    pclose(f);
    return r;
}

int scan_block_bits(int begin, int end, int interval)
{
    int i = 0;

    int   len = 1*1024*1024*sizeof(char);
    char *buf = (char*)malloc(len);
    char  temp[32];

    for (i=begin; i<=end; i+=interval) {
        printf("block=%06d ", i);
        sleep(1);
        if (0 != grab_block_info(i, buf, len)) {
            continue;
        }

        memset(temp, 0x00, sizeof(temp));
        if (-1 != xkey_value_parser(buf, "\"timestamp\":", ",", temp, sizeof(temp))) {
            printf("timestamp=%s ", temp);
        }

        memset(temp, 0x00, sizeof(temp));
        if (-1 != xkey_value_parser(buf, "\"bits\":", ",", temp, sizeof(temp))) {
            uint32_t nbits = atoi(temp);
            printf("nbits=%s 0x%x ", temp, nbits);
        }

        memset(temp, 0x00, sizeof(temp));
        if (-1 != xkey_value_parser(buf, "\"difficulty\":", ",", temp, sizeof(temp))) {
            printf("difficulty=%s", temp);
        }
        
        printf("\n");
    }
    
    free(buf);
    return 0;
}

int main(int argc, char* argv[])
{
    /*扫描block 0-552383,间隔2016*/
    scan_block_bits(0, 552383, 2016);
    return 0;
}

编译运行,xutils.c 在区块链学习(2)中。打印有点多,贴的这部分也就够用了。

gcc xmain.c xutils.c
./a.out
block=000000 timestamp=1231006505 nbits=486604799 0x1d00ffff difficulty=1
block=002016 timestamp=1233063531 nbits=486604799 0x1d00ffff difficulty=1
block=004032 timestamp=1234466190 nbits=486604799 0x1d00ffff difficulty=1
block=006048 timestamp=1235966513 nbits=486604799 0x1d00ffff difficulty=1
block=008064 timestamp=1237508786 nbits=486604799 0x1d00ffff difficulty=1
block=010080 timestamp=1239055463 nbits=486604799 0x1d00ffff difficulty=1
block=012096 timestamp=1240599098 nbits=486604799 0x1d00ffff difficulty=1
block=014112 timestamp=1242098425 nbits=486604799 0x1d00ffff difficulty=1
block=016128 timestamp=1243737085 nbits=486604799 0x1d00ffff difficulty=1
block=018144 timestamp=1246051973 nbits=486604799 0x1d00ffff difficulty=1
block=020160 timestamp=1248481816 nbits=486604799 0x1d00ffff difficulty=1
block=022176 timestamp=1252069298 nbits=486604799 0x1d00ffff difficulty=1
block=024192 timestamp=1254454028 nbits=486604799 0x1d00ffff difficulty=1
block=026208 timestamp=1257002900 nbits=486604799 0x1d00ffff difficulty=1
block=028224 timestamp=1259358667 nbits=486604799 0x1d00ffff difficulty=1
block=030240 timestamp=1261130161 nbits=486604799 0x1d00ffff difficulty=1
block=032256 timestamp=1262153464 nbits=486594666 0x1d00d86a difficulty=1.1828995343128408
block=034272 timestamp=1263250117 nbits=486589480 0x1d00c428 difficulty=1.3050621315915245
block=036288 timestamp=1264424879 nbits=486588017 0x1d00be71 difficulty=1.3442249707710294
...

由打印可见,在block=032256时发生了第一次难度调整,nbits由0x1d00ffff 调整为 0x1d00d86a。
下面我们就来看一下block=032256的nbits是怎么算出来的。
将相关参数代入公式:
新难度值 = 旧难度值 * (之前2016个区块花费时长(单位秒) / 2016*10*60)
block-032256->nbits=block-032255->nbits*((block-032255->timestamp-block-030240->timestamp)/2016*10*60)
block-032255->nbits要先转换为256位的整数,计算完后再压缩位32位的整数,最终得到block-032256->nbits。
这里涉及到256位大数运算,自己搞还是挺费事的。先偷个懒直接用bitcoin中提供的arith_uint256类来计算了,以后有空了再补上吧。计算代码如下:

// xmain.cpp
// by maxzero
#include <stdio.h>
#include "arith_uint256.h"

uint32_t difficulty_compute(int64_t time_span, uint32_t nbits_prev)
{
    uint32_t nbits = 0;
    arith_uint256 diff;

    diff.SetCompact(nbits_prev);
    diff *= time_span;
    diff /= (2016*10*60);
    nbits = diff.GetCompact();

    printf("0x%x\n", nbits);
    printf("%s\n", diff.GetHex().c_str());
    return nbits;
}

//block=030240 timestamp=1261130161 nbits=486604799 0x1d00ffff difficulty=1
//block=032255 timestamp=1262152739 nbits=486604799 0x1d00ffff difficulty=1
//block=032256 timestamp=1262153464 nbits=486594666 0x1d00d86a difficulty=1.1828995343128408
int main(int argc, char* argv[])
{
    difficulty_compute((1262152739-1261130161), 0x1d00ffff);
    return 0;
}

编译运行

g++ xmain.cpp arith_uint256.cpp uint256.cpp utilstrencodings.cpp -std=gnu++11 -I./

#依赖的文件信息
#这些都可以在bitcoin的源码找到
#https://github.com/bitcoin/bitcoin/tree/master/src

├── a.out
├── arith_uint256.cpp
├── arith_uint256.h
├── compat
│   ├── byteswap.h
│   └── endian.h
├── crypto
│   ├── common.h
│   └── sha256.h
├── xmain.cpp
├── sha256.cpp
├── tinyformat.h
├── uint256.cpp
├── uint256.h
├── utilstrencodings.cpp
└── utilstrencodings.h

./a.out
0x1d00d86a #压缩后的nbits
00000000d86a528bc8bc8bc8bc8bc8bc8bc8bc8bc8bc8bc8bc8bc8bc8bc8bc8b #未压缩的nbits

bitcoin中关于难度调整的代码在pow.cpp中

unsigned int CalculateNextWorkRequired(const CBlockIndex* pindexLast, int64_t nFirstBlockTime, const Consensus::Params& params)
{
    if (params.fPowNoRetargeting)
        return pindexLast->nBits;

    // Limit adjustment step
    // nActualTimespan 之前2016个区块花费时长(单位秒)
    int64_t nActualTimespan = pindexLast->GetBlockTime() - nFirstBlockTime;
    // params.nPowTargetTimespan 2016*10*60 14天
    // nActualTimespan 的取值范围限定为 3.5天-56天
    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);
    bnNew *= nActualTimespan;
    bnNew /= params.nPowTargetTimespan;
    
    // bnNew 不能大于最大难度目标
    if (bnNew > bnPowLimit)
        bnNew = bnPowLimit;

    return bnNew.GetCompact();
}

 

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值