大家好,我是虞双齐,这篇文章是关于区块链共识算法系列课文章。上一篇文章《共识算法PoW之由来》中,我们讲解了工作量证明的基本原理,核心是采取穷举法暴力寻找出一个符合难度值的随机数。这篇文章讲解比特币算力,通过本节学习,你可以掌握算力概念并能理解算力引发的悲剧。
大家好,我是虞双齐,这篇文章是关于区块链共识算法系列课文章。上一篇文章《共识算法PoW之由来》中,我们讲解了工作量证明的基本原理,核心是采取穷举法暴力寻找出一个符合难度值的随机数。这篇文章讲解比特币算力,通过本节学习,你可以掌握算力概念并能理解算力引发的悲剧。
比特币挖矿形同猜数字谜,矿工要找出一个随机数(Nonce)参与哈希运算[1]
Hash(Block+Nonce)
使得区块哈希值符合难度要求。 算力则指计算机每秒可执行哈希运算的次数,也称为哈希率(hashrate)。
上图是当前比特币算力图表[2]。到2017年时,比特币挖矿所需算力疯涨。这与不断研发出的新型矿机投入市场有关——这些矿机利用新的技术,拥有更强的运算能力,即单位成本下的算力在快速增长,由此带来了整体算力的提升。
算力每隔千位划为一个单位,最小单位 H=1次
,其他分部是:
如果不清楚单位简称,可以查看下面国际单位的前缀表[3]:
Factor | Name | Symbol |
---|---|---|
10^24 | yotta | Y |
10^21 | zetta | Z |
10^18 | exa | E |
10^15 | peta | P |
10^12 | tera | T |
10^9 | giga | G |
10^6 | mega | M |
10^3 | kilo | k |
10^2 | hecto | h |
10^1 | deka | da |
上图红色线是比特币从上线到现在的难度值变化图表。可以看到难度一直在增大,难度越大意味着矿工找出那个随机数,所需要的时间也就越长。同时可以观察到难度值是周期性调整,这是因为比特币协议期望每十分钟出一个区块,2016个区块需要两周时间完成。每出2016个区块,全网节点自动根据前面2016个出块的出款时间调整难度值。如果用时超过两周,则降低难度,反之则增加难度。 以便维持一个区块是10分钟。更多图表数据可以到bitcoinwisdom网站查看。
难度值代表着寻找随机数耗时程度,耗时越长意味着计算机所需要执行哈希计算的次数越多。一台普通笔记本电脑,每秒可以执行800次哈希运算,配置中端显卡可以算2000多次。
中本聪在自己电脑上挖出创世区块(比特币区块链的第一个区块)。原本中本聪设计的是一个公平的完全去中心化的一个数字货币系统,每个人都可以使用个人电脑进行挖矿(他也预料网络会出现挖矿的服务商(矿池))。然而,有利可图时大量新算力不断加入,矿工竞争激烈,使得单个矿工的挖矿成功率几乎为零。2011年起矿池出现,大量矿工纷纷加入矿池,以稳定收入,摊薄成本。大量算力融入,使得比特币挖矿难度越来越大。数字货币挖矿业形同军事竞备,挖矿设备不断更新迭代,不再遵循摩尔定律。 专业矿机专门针对哈希算法、散热、耗能进行优化,这脱离了比特币网络节点运行在成千上万的普通计算中并公平参与挖矿的初衷。矿池的算力占据,也使得比特币风险一直存在:51%算力攻击。
需要多少算力才能找出一个随机数,由当前区块的挖矿难度决定,难度越大所需算力越多。但挖矿难度并不在区块信息中,只在网络节点中依据规则动态计算,公式如下:
$D = T_1/T $
T 字母是 Target 的缩写,D 字母是 DiFFiculty 缩写。$T_1$ 和 T 均是一个256位的大数字(big number),其中 $T_1$ 为一个非常大的常数 $2^{256-32}-1=2^224-1$ 。依据公式, T 越小,挖矿难度D 越大。
依据公式,当 T = 0 时, D 无穷大,标志着无法计算出结果。幸运的是, T 不会为 0,最小值为 1,此时难度值最大,为 $2^{256-32}-1=2^224-1$。当 $T = T_1$ 时,难度值为最小值 1。
为了方便人类直观估算难度,比特币协议将大数字 T 压缩为一个浮点数记录在区块头中,字段为bits
。
如果一个区块目标值是 0x1b0404cb,则转化成 Target 值为 $$0x0404cb * 256^{0x1b-3}$$
T 使用类浮点数的一种压缩表示法[4]进行压缩,压缩计算过程如下:
例如,将数字1000压缩,先转换为256进制数:$$1000=0x03 256^{2-1} + 0xe8 256^{1-1}$$ 结果为[0x03,0xe8]。第一个数未超过 0x7f,则不需填 0。但长度两位低于三位,在后面补零,最终表示为:0x0203e800。
又比如数字 $$2^{256-32}-1$$,转换为256进制为:
FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF
第一位数字 0xFF 大于 0x7f,故前面添加零后,变成:
00 FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF
其长度等于 28+1=29 (0x1d),且长度超过三位,无需补零,则压缩结果为:0x1d00FFFF。因为压缩存储容量只有才4个字节,前两字节已经被长度和添加的 00 所占用,只剩下2个字节来存储数字,这样后面的26个 FF 值被丢弃。
如果我们将压缩结果 0x1d00FFFF 解压还会是原值吗? 实际上结果是: $$T=0x00FFFF × 256 ^{0x1b−3}$$ =
0x00000000FFFF0000000000000000000000000000000000000000000000000000
解压时这个数字被截断了,不再是原来的 $$2^{256-32}-1$$,比特币的 $$T_1$$ 值就是这个 0x1d00FFFF ,如果区块中 bits 为 0x1d00FFFF 则说明该区块挖矿难度为最小挖矿难度 1。
实际上,专业的矿池程序会保留被截断的FF:
0x00000000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF
称上面数字为矿池难度 1(pool diFFiculty 1)。因此根据公式,区块目标值为 0x1b0404cb 的挖矿难度在挖机上看到的是:
D = 0x00000000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF /
0x00000000000404CB000000000000000000000000000000000000000000000000
= 16307.669773817162 (pdiFF)
称为 pdiFF。但是一些比特币客户端可能无法精确到这么大,所以不保留尾部的FF:
0x00000000FFFFFF0000000000000000000000000000000000000000000000000000
此时,该挖矿难度为:
D = 0x00000000FFFF0000000000000000000000000000000000000000000000000000 /
0x00000000000404CB000000000000000000000000000000000000000000000000
= 16307.420938523983 (bdiFF)
称为 bidFF。
你可以在一些提供服务的网站上查看图表数据,如:
下图是写此文章时,比特币区块 546336 的摘要。
现在我们知道挖矿难度是如何计算的,那么为了挖出一个区块,需要执行多次哈希运算才能找到随机数,使得区块的哈希值小于目标值呢?
前面已确定 $T_1=0x1d00FFFF$,解压为 $:0xFFFF × 2 ^ 208$ ,对于 难度 D 的目标值:
因此,挖出难度为 D 的区块预计需要计算的哈希次数为:
目前难度计算速度要求是在10分钟内找到,即在600秒内完全计算,意味着网络算力最低必须是:
依上计算,当 D=1 时,需要每秒计算7158278次哈希,即: 7.15 Mhahs/s。
在调整难度时,调整的是目标值。目标值计算公式如下,但在实际计算时有些特别处理,将目标值控制在一定范围内。
新目标值= 当前目标值 * 实际2016个区块出块时间 / 理论2016个区块出块时间(2周)。
计算过程,Go代码如下。点击查看bticoin C++源码
var (
bigOne = big.NewInt(1)
// 最大难度:00000000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF,2^224,0x1d00FFFF
mainPowLimit = new(big.Int).Sub(new(big.Int).Lsh(bigOne, 224), bigOne)
powTargetTimespan = time.Hour * 24 * 14 // 两周
)
func CalculateNextWorkTarget(prev2016block, lastBlock Block) *big.Int {
// 如果新区块(+1)不是2016的整数倍,则不需要更新,仍然是最后一个区块的 bits
if (lastBlock.Head.Height+1)%2016 != 0 {
return CompactToBig(lastBlock.Head.Bits)
}
// 计算 2016个区块出块时间
actualTimespan := lastBlock.Head.Timestamp.Sub(prev2016block.Head.Timestamp)
if actualTimespan < powTargetTimespan/4 {
actualTimespan = powTargetTimespan / 4
} else if actualTimespan > powTargetTimespan*4 {
// 如果超过8周,则按8周计算
actualTimespan = powTargetTimespan * 4
}
lastTarget := CompactToBig(lastBlock.Head.Bits)
// 计算公式: target = lastTarget * actualTime / expectTime
newTarget := new(big.Int).Mul(lastTarget, big.NewInt(int64(actualTimespan.Seconds())))
newTarget.Div(newTarget, big.NewInt(int64(powTargetTimespan.Seconds())))
//超过最多难度,则重置
if newTarget.Cmp(mainPowLimit) > 0 {
newTarget.Set(mainPowLimit)
}
return newTarget
}
测试代码如下,计算的是对高度为497951+1出块时计算的新目标值。
func TestGetTarget(t *testing.T) {
firstTime, _ := time.Parse("2006-01-02 15:04:05", "2017-11-25 03:53:16")
lastTime, _ := time.Parse("2006-01-02 15:04:05", "2017-12-07 00:22:42")
prevB := Block{Head: BlockHeader{Height: 497951, Bits: 0x1800d0f6, Timestamp: lastTime}}
prev2016B := Block{Head: BlockHeader{Height: 495936, Bits: 0x1800d0f6, Timestamp: firstTime}}
result := CalculateNextWorkTarget(prev2016B, prevB)
bits := BigToCompact(result)
if bits != 0x1800b0ed {
t.Fatalf("expect 0x1800b0ed,unexpected %x", bits)
}
}
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!