https://github.com/ethereum/EIPs/blob/master/EIPS/eip-100.mdgit
创世纪区块的难度是131,072,有一个特殊的公式用来计算以后的每一个块的难度。若是某个区块比前一个区块验证的更快,以太坊协议就会增长区块的难度。github
区块的难度影响nonce,它是在挖矿时必需要使用proof-of-work算法来计算的一个hash值。算法
区块难度和nonce之间的关系用数学形式表达就是:less
Hd表明的是难度。函数
找到符合难度阈值的nonce惟一方法就是使用proof-of-work算法来列举全部的可能性。找到解决方案预期时间与难度成正比—难度越高,找到nonce就越困难,所以验证一个区块也就越难,这又相应地增长了验证新块所需的时间。因此,经过调整区块难度,协议能够调整验证区块所需的时间。(挖矿的过程就是要找出正确的nonce值)区块链
另外一方面,若是验证时间变的愈来愈慢,协议就会下降难度。这样的话,验证时间自我调节以保持恒定的速率—平均每15s一个块。ui
这就是以太坊的出块时间可以保持15秒的缘由,由于当验证时间变长,就会下降难度;验证时间变短,则会增强难度,那么难度是怎么计算的呢?atom
eip | title | author | type | category | status | created |
---|---|---|---|---|---|---|
100
|
Change difficulty adjustment to target mean block time including uncles
|
Vitalik Buterin
|
Standards Track
|
Core
|
Final
|
2016-04-28
|
adj_factor = max(1 - ((timestamp - parent.timestamp) // 10), -99) child_diff = int(max(parent.difficulty + (parent.difficulty // BLOCK_DIFF_FACTOR) * adj_factor, min(parent.difficulty, MIN_DIFF)))
而后若是block.number >= BYZANTIUM_FORK_BLKNUM(拜占庭硬分叉),则难度的计算的第一个公式变为:spa
adj_factor = max((2 if len(parent.uncles) else 1) - ((timestamp - parent.timestamp) // 9), -99)
即Byzantium版本.net
diff = (parent_diff +(parent_diff / 2048 * max((2 if len(parent.uncles) else 1) - ((timestamp - parent.timestamp) // 9), -99))) + 2^(periodCount - 2)
有关上面的拜占庭版本的详细介绍看:https://blog.csdn.net/t46414704152abc/article/details/81538361
这个地方没有太懂为何,以后再多多查查资料
这里主要是看:
以太坊中的区块的难度调整公式以下图所示。
区块链难度调整中,创始块的难度被设置为D0=131072 ,此后每一个区块的难度都与其父区块的难度相关。D(H)是本区块的难度,由P(H)Hd+x×ζ2和难度炸弹ϵ构成。
P(H)Hd为父区块的难度,每一个区块的难度都是在父区块难度的基础上进行调整。
x×ζ2用于自适应调节出块难度,维持稳定的出块速度(15秒)。
根据EIP-100给出的式子,能够得出:
P(H)Hd = parent_diff
x = parent_diff / 2048
ζ2 = max((2 if len(parent.uncles) else 1) - ((timestamp - parent.timestamp) // 9), -99)
ϵ = 2^(periodCount - 2)
其中x和ζ2的计算方式以下图所示。
假设当父区块不带叔父区块的时候(y=1),出块时间 = (timestamp - parent.timestamp),调整过程以下:
难度炸弹计算公式以下图所示。
ϵ是2的指数函数,每十万个块扩大一倍,后期增加很是快,这就是难度“炸弹”的由来。
H′i称为fake block number,由真正的block number Hi减小三百万获得。之因此减小三百万,是由于目前proof of stake的工做量证实方式还存在一些问题,pos协议涉及不够完善,可是难度炸弹已经致使挖矿时间变成了30秒左右,为了减少难度,就会减去三百万。
设置难度炸弹的缘由是要下降迁移到PoS协议时发生fork的风险,倘若矿工联合起来抵制POS的工做量证实模式,那就会致使以太坊产生硬分叉;有了难度炸弹,挖矿难度愈来愈大,矿工就有意愿迁移到PoS协议上了。难度炸弹的威力,能够经过下图看出。
区块数量到370万以后,挖矿难度忽然递增,到430万时,难度已经很是之大了,这时候挖矿时间已经变为为30秒,可是POS协议尚未完善,因而以太坊将挖矿难度公式进行调整,使得每次计算时,当前区块号减去三百万,这样就下降了挖矿难度,而且在这个时期,对以太坊出块奖励进行了调整,从原来的5个ETH变为3个ETH。
// CalcDifficulty is the difficulty adjustment algorithm. It returns // the difficulty that a new block should have when created at time // given the parent block's time and difficulty. func (ethash *Ethash) CalcDifficulty(chain consensus.ChainReader, time uint64, parent *types.Header) *big.Int { return CalcDifficulty(chain.Config(), time, parent) } // CalcDifficulty is the difficulty adjustment algorithm. It returns // the difficulty that a new block should have when created at time // given the parent block's time and difficulty. func CalcDifficulty(config *params.ChainConfig, time uint64, parent *types.Header) *big.Int { next := new(big.Int).Add(parent.Number, big1) switch { case config.IsByzantium(next): return calcDifficultyByzantium(time, parent) case config.IsHomestead(next): return calcDifficultyHomestead(time, parent) default: return calcDifficultyFrontier(time, parent) } } // Some weird constants to avoid constant memory allocs for them. var ( expDiffPeriod = big.NewInt(100000) big1 = big.NewInt(1) big2 = big.NewInt(2) big9 = big.NewInt(9) big10 = big.NewInt(10) bigMinus99 = big.NewInt(-99) big2999999 = big.NewInt(2999999) )
以太坊中难度计算公式以下图所示,因为目前处于以太坊发展的Metropolis中的Byzantium阶段,因此难度计算公式的函数名称为calcDifficultyByzantium
// calcDifficultyByzantium is the difficulty adjustment algorithm. It returns // the difficulty that a new block should have when created at time given the // parent block's time and difficulty. The calculation uses the Byzantium rules. func calcDifficultyByzantium(time uint64, parent *types.Header) *big.Int { // https://github.com/ethereum/EIPs/issues/100. // algorithm: // diff = (parent_diff + // (parent_diff / 2048 * max((2 if len(parent.uncles) else 1) - ((timestamp - parent.timestamp) // 9), -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) // (2 if len(parent_uncles) else 1) - (block_timestamp - parent_timestamp) // 9 x.Sub(bigTime, bigParentTime) // x = block_timestamp - parent_timestamp x.Div(x, big9) // x = (block_timestamp - parent_timestamp) //9,整除9 if parent.UncleHash == types.EmptyUncleHash {//判断有无叔父区块 x.Sub(big1, x) //无为1, x = 1 - (block_timestamp - parent_timestamp) //9 } else { x.Sub(big2, x)//有为2 , x = 2 - (block_timestamp - parent_timestamp) //9 } // max((2 if len(parent_uncles) else 1) - (block_timestamp - parent_timestamp) // 9, -99) if x.Cmp(bigMinus99) < 0 { x与-99比,看谁大,小于0则说明-99大 x.Set(bigMinus99) //x的值设为-99,此时x = ζ2 } // parent_diff + (parent_diff / 2048 * max((2 if len(parent.uncles) else 1) - ((timestamp - parent.timestamp) // 9), -99)) y.Div(parent.Difficulty, params.DifficultyBoundDivisor) //y = parent_diff / 2048 x.Mul(y, x) // x = y * ζ2 x.Add(parent.Difficulty, x) // x = parent_diff + y * ζ2 // minimum difficulty can ever be (before exponential factor) if x.Cmp(params.MinimumDifficulty) < 0 { //与创世区块的难度比较,创世区块的难度是难度的最低值,若是算出来的难度低于它,那就设置为创世区块的难度 x.Set(params.MinimumDifficulty) } // calculate a fake block number for the ice-age delay: // https://github.com/ethereum/EIPs/pull/669 // fake_block_number = max(0, block.number - 3_000_000) fakeBlockNumber := new(big.Int) if parent.Number.Cmp(big2999999) >= 0 { //当父区块号 >= 2999999,说明本区块 >= 3000000,因此要减去3百万,以此来下降难度,因此用父区块数 - 2999999便可 fakeBlockNumber = fakeBlockNumber.Sub(parent.Number, big2999999) // Note, parent is 1 less than the actual block number } // for the exponential factor periodCount := fakeBlockNumber periodCount.Div(periodCount, expDiffPeriod) //periodCount = periodCount / 100000 // 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 }
frontier版本已经不使用了,它存在的问题是没有考虑到到13秒的距离。出块1秒和12秒有一样的难度计算值
而后还找到了另外一个很好地解释了难度的帖子,可是它讲的是Homestead的版本:
https://blog.csdn.net/Metal1/article/details/80151535
这个是Homestead版本的计算方法,与Byzantium版本的不一样之处就在于adj_factor = max(1 - ((timestamp - parent.timestamp) // 10), -99)
block_diff = parent_diff + 难度调整 + 难度炸弹
难度调整 = parent_diff // 2048 * MAX(1 - (block_timestamp - parent_timestamp) // 10, -99)
即难度调整 = parent_diff // 2048 * adj_factor (BLOCK_DIFF_FACTOR在这里设置为2048)
难度炸弹 = INT(2**((block_number // 100000) - 2)) (向下取整)
该版本是在区块数尚未到达三百万时候的版本,想在超过三百万后,为了下降难度,减小出块的时间,都用上面的Byzantium版本了
https://github.com/ethereum/go-ethereum/blob/master/consensus/ethash/consensus.go
// calcDifficultyHomestead is the difficulty adjustment algorithm. It returns // the difficulty that a new block should have when created at time given the // parent block's time and difficulty. The calculation uses the Homestead rules. func calcDifficultyHomestead(time uint64, parent *types.Header) *big.Int { // https://github.com/ethereum/EIPs/blob/master/EIPS/eip-2.md // 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) //这里与Byzantium版本不一样处是不减三百万,直接父区块数加1得现在的区块数便可 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 }