本部分针对在ethashseal工程中Ethash类分析,Ethash类主要完成工作量证明相关工作
Ethash.h的代码在 https://github.com/ethereum/cpp-ethereum/blob/develop/libethashseal/Ethash.h
1、难度计算
难度(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
难度(Difficulty)通过控制合格的解在空间中的数量来控制平均求解所需要尝试的次数,也就可以间接的控制产生一个区块需要的时间,这样就可以使区块以一个合理而稳定的速度产生。
当挖矿的人很多,单位时间能够尝试更多次时,难度就会增大,当挖矿的人减少,单位时间能够尝试的次数变少时,难度就降低。这样产生一个区块需要的时间就可以做到稳定。
有了难度,我们还需要一种算法,用来确定和调整合理的难度值是多少。
以太坊的目前难度计算公式
block_diff = parent_diff + 难度调整 + 难度炸弹
难度调整 = parent_diff // 2048 * MAX(1 - (block_timestamp - parent_timestamp) // 10, -99)
难度炸弹 = INT(2**((block_number // 100000) - 2))
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))
----------------------------------------------------------------------------------------------------
符号//整数除法
计算a//b时,先计算a/b,然后取不大于a/b的最大整数。
例如:
-11.3 // 5 = -3
11.3 // 5 = 2
以太坊(Ethereum ETH)是如何计算难度的,可以看到在ethash类中有一个难度计算函数
u256 calculateDifficulty(BlockHeader const& _bi, BlockHeader const& _parent) const;
传入参数:
BlockHeader const& _bi, 当前区块头
BlockHeader const& _parent 上一个区块头
代码中涉及以太坊分叉的信息,先说明一下:
以太坊的创始人们为它设定了4个发展阶段:Frontier,Homestead,Metropolis(Byzantium和Constantinople),Serenity,阶段之间的转换需要通过硬分叉的方式实现。
c++代码并没严格遵循公式
block_diff = parent_diff + parent_diff / 2048 * max(1 - (block_timestamp - parent_timestamp) / 10, -99) + int(2^((block.number / 100000) - 2))
因为代码里涉及很多分叉的处理,不同分叉使用不同的难度计算
u256 Ethash::calculateDifficulty(BlockHeader const& _bi, BlockHeader const& _parent) const
{
const unsigned c_expDiffPeriod = 100000;
if (!_bi.number()) //如果当前区块号为空,则抛出创世区块不能计算错误
throw GenesisBlockCannotBeCalculated();
auto const& minimumDifficulty = chainParams().minimumDifficulty; //区块链上的最小难度
auto const& difficultyBoundDivisor = chainParams().difficultyBoundDivisor;
auto const& durationLimit = chainParams().durationLimit;
bigint target; // stick to a bigint for the target. Don't want to risk going negative.
if (_bi.number() < chainParams().homesteadForkBlock) //如果当前区块号小于Homestead分叉号
// Frontier-era difficulty adjustment
target = _bi.timestamp() >= _parent.timestamp() + durationLimit ? _parent.difficulty() - (_parent.difficulty() / difficultyBoundDivisor) : (_parent.difficulty() + (_parent.difficulty() / difficultyBoundDivisor));
else
{
bigint const timestampDiff = bigint(_bi.timestamp()) - _parent.timestamp();
bigint const adjFactor = _bi.number() < chainParams().byzantiumForkBlock ?
max(1 - timestampDiff / 10, -99) : // Homestead-era difficulty adjustment
max((_parent.hasUncles() ? 2 : 1) - timestampDiff / 9, -99); // Byzantium-era difficulty adjustment
target = _parent.difficulty() + _parent.difficulty() / 2048 * adjFactor;
}
bigint o = target;
unsigned exponentialIceAgeBlockNumber = unsigned(_parent.number() + 1);
// EIP-649 modifies exponentialIceAgeBlockNumber
if (_bi.number() >= chainParams().byzantiumForkBlock) //如果当前区块号大于拜占庭分叉区块号
{
if (exponentialIceAgeBlockNumber >= 3000000)
exponentialIceAgeBlockNumber -= 3000000;
else
exponentialIceAgeBlockNumber = 0;
}
unsigned periodCount = exponentialIceAgeBlockNumber / c_expDiffPeriod;
if (periodCount > 1)
o += (bigint(1) << (periodCount - 2)); // latter will eventually become huge, so ensure it's a bigint.
o = max(minimumDifficulty, o);
return u256(min(o, std::numeric_limits::max()));
}
2、难度校验
难度校验在libethash/internal.h 文件中实现
代码地址:https://github.com/ethereum/cpp-ethereum/blob/develop/libethash/internal.h
// Returns if hash is less than or equal to boundary (2^256/difficulty)
static inline bool ethash_check_difficulty(
ethash_h256_t const* hash,
ethash_h256_t const* boundary
)
{
// Boundary is big endian
for (int i = 0; i < 32; i++) {
if (ethash_h256_get(hash, i) == ethash_h256_get(boundary, i)) {
continue;
}
return ethash_h256_get(hash, i) < ethash_h256_get(boundary, i);
}
return true;
}
可以看到该段代码hash表示mixhash, boundary表示难度,均为32个字节256位二进制,
通过循环32次,每次取一个字节比较,如果有字节不同,则返回比较结果,否则进行下一个字节比较