这是一篇写得非常好的关于难度的文章。
来源并参考: https://zhuanlan.zhihu.com/p/28830859
什么是难度
难度(Difficulty)一词来源于区块链技术的先驱比特币,用来度量挖出一个区块平均需要的运算次数。挖矿本质上就是在求解一个谜题,不同的电子币设置了不同的谜题。比如比特币使用SHA-256、莱特币使用Scrypt、以太坊使用Ethash。一个谜题的解的所有可能取值被称为解的空间,挖矿就是在这些可能的取值中寻找一个解。
这些谜题都有如下共同的特点:
没有比穷举法更有效的求解方法
解在空间中均匀分布,从而使每一次穷举尝试找到一个解的概率基本一致
解的空间足够大,保证一定能够找到解
假设现在有一种电子币,解所在的空间为0-99共100个数字,谜题为x<100。这个谜题非常简单,空间中的任何一个数字都能满足。如果想让谜题更难以求解该怎么做呢?把谜题改成x<50,现在空间中只有一半的数字能满足了,也就是说,现在的难度比原来大了。并且我们还能知道难度大了多少,原来求解平均要尝试1次,现在求解平均要尝试2次了,也就是说,x<50的难度是x<100的2/1=2倍。同理,如果谜题变成x<10,难度就是x<100的100/10=10倍。
现在谜题多了个参数Difficulty,谜题变成了x
func CalcDifficulty(config *params.ChainConfig, time uint64, parent *types.Header) *big.Int {
next := new(big.Int).Add(parent.Number, big1)
switch {
case config.IsMetropolis(next):
return calcDifficultyMetropolis(time, parent)
case config.IsHomestead(next):
return calcDifficultyHomestead(time, parent)
default:
return calcDifficultyFrontier(time, parent)
}
}
有三种计算难度的规则,分别对应以太坊的三个主要版本:已经成为历史的Frontier、正在使用的Homestead和将要发布的Metropolis。
现在正在使用的Homestead的难度计算规则在calcDifficultyHomestead中实现。
func calcDifficultyHomestead(time uint64, parent *types.Header) *big.Int {
// https://github.com/ethereum/EIPs/blob/master/EIPS/eip-2.mediawiki
// algorithm:
// diff = (parent_diff +
// (parent_diff / 2048 * max(1 - (block_timestamp - parent_timestamp) // 10, -99))
// ) + 2^(periodCount - 2)
bigTime := new(big.Int).SetUint64(time)
bigParentTime := new(big.Int).Set(parent.Time)
// holds intermediate values to make the algo easier to read & audit
x := new(big.Int)
y := new(big.Int)
// 1 - (block_timestamp - parent_timestamp) // 10
x.Sub(bigTime, bigParentTime)
x.Div(x, big10)
x.Sub(big1, x)
// max(1 - (block_timestamp - parent_timestamp) // 10, -99)
if x.Cmp(bigMinus99) < 0 {
x.Set(bigMinus99)
}
// (parent_diff + parent_diff // 2048 * max(1 - (block_timestamp - parent_timestamp) // 10, -99))
y.Div(parent.Difficulty, params.DifficultyBoundDivisor)
x.Mul(y, x)
x.Add(parent.Difficulty, x)
// minimum difficulty can ever be (before exponential factor)
if x.Cmp(params.MinimumDifficulty) < 0 {
x.Set(params.MinimumDifficulty)
}
// for the exponential factor
periodCount := new(big.Int).Add(parent.Number, big1)
periodCount.Div(periodCount, expDiffPeriod)
// the exponential factor, commonly referred to as "the bomb"
// diff = diff + 2^(periodCount - 2)
if periodCount.Cmp(big1) > 0 {
y.Sub(periodCount, big2)
y.Exp(big2, y, nil)
x.Add(x, y)
}
return x
}
不过我不是来搬运代码的,我们说人话。
在解释具体计算过程之前,先来介绍一下,用到的几种运算符。
整数除法,符号//
计算a//b时,先计算a/b,然后取不大于a/b的最大整数。
例如:
-11.3 // 5 = -3
11.3 // 5 = 2
取整,符号INT
计算INT(a)时,仅仅取整数部分,丢弃小数。
例如:
INT(3.7) = 3
INT(-3.7) = -3
最大值,符号MAX
计算MAX(a,b)时,结果为a和b中较大的那一个。
例如:
MAX(-1,0) = 0
MAX(7,10) = 10
计算一个区块的难度时,需要以下输入:
parent_timestamp:上一个区块产生的时间
parent_diff:上一个区块的难度
block_timestamp:当前区块产生的时间
block_number:当前区块的序号
block_diff = parent_diff + 难度调整 + 难度炸弹
难度调整 = parent_diff // 2048 * MAX(1 - (block_timestamp - parent_timestamp) // 10, -99)
难度炸弹 = INT(2**((block_number // 100000) - 2))
另外,区块难度不能低于以太坊的创世区块,创世区块的难度为131072,这是以太坊难度的下限。