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
#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();
}