POW
二、源代码分析
Ethash模块实现的源码位于go-ethereum/consensus/ethash目录下,此目录下的文件及其相应的代码功能如下:
algorithm.go
此文件实现了Dagger-Hashimoto算法的所有功能,包括dataset、cache的生成以及计算挖矿哈希等
api.go
此文件实现了供RPC使用的api方法
consensus.go
此文件实现了以太坊共识接口的部分代码
ethash.go
此文件实现了cache结构体和dataset结构体及它们各自的方法
sealer.go
此文件实现了共识接口的Seal方法以及ethash的挖矿功能
ethash模块下的几个重要函数分别为MakeCache()函数、MakeDataset()函数、Ethash.Seal()和Ethash.VerifySeal()函数。它们的作用分别为产生缓存集、产生哈希运算数据集、挖矿打包区块以及验证被打包的区块,这些函数最终都会都会调用到generateCache()或者generateDataset()这两个函数来产生相应的数据,这些函数的调用流程如下图所示:
2.1 共识引擎接口
// Engine 引擎是一种算法无关的共识引擎
type Engine interface {
// Author 获取创建给定区块的账户的以太坊地址,如果共识引擎基于签名,可能与头的coinbase不同。
Author(header *types.Header) (common.Address, error)
//VerifyHeader 用于校验区块头,通过共识规则来校验,验证区块可以在这里进行也科通通过VerifySeal方法
VerifyHeader(chain ChainHeaderReader, header *types.Header, seal bool) error
// VerifyHeaders与VerifyHeader相似,同时这个用于批量操作校验头。这个方法返回一个退出信号
VerifyHeaders(chain ChainHeaderReader, headers []*types.Header, seals []bool) (chan<- struct{}, <-chan error)
// VerifyUncles 用于校验叔块以符合共识引擎的规则
VerifyUncles(chain ChainReader, block *types.Block) error
//根据特定引擎的规则,准备初始化块头的共识字段。这些改变是内联执行的。
Prepare(chain ChainHeaderReader, header *types.Header) error
//Finalize运行任何事务后的状态修改(例如块奖励),但不组装块。
//注意:块头和状态数据库可能会被更新,以反映在结束时发生的任何共识规则(例如块奖励)。
Finalize(chain ChainHeaderReader, header *types.Header, state *state.StateDB, txs []*types.Transaction,
uncles []*types.Header)
//与Finalize类似,FinalizeAndAssemble运行任何事务后的状态修改(例如块奖励),但会组装最终的块
//其他的都一样
FinalizeAndAssemble(chain ChainHeaderReader, header *types.Header, state *state.StateDB, txs []*types.Transaction,
uncles []*types.Header, receipts []*types.Receipt) (*types.Block, error)
//Seal 为给定的输入块生成一个新的密封请求(新的区块),并将结果推送到给定的通道中。
//注意,该方法立即返回并将发送异步结果。根据共识算法,还可以返回多个结果。
Seal(chain ChainHeaderReader, block *types.Block, results chan<- *types.Block, stop <-chan struct{}) error
//SealHash 返回一个块被密封之前的哈希值。
SealHash(header *types.Header) common.Hash
//CalcDifficulty 是难度调整算法。它返回一个新方块应该具有的难度。
CalcDifficulty(chain ChainHeaderReader, time uint64, parent *types.Header) *big.Int
//APIs 返回这个共识引擎提供的RPC api。
APIs(chain ChainHeaderReader) []rpc.API
//Close 终止由共识引擎维护的所有后台线程。
Close() error
}
2.2 共识引擎接口的实现
2.2.1 Author
实现了consensus.Engine 该方法获取了挖出这个块的矿工地址。
func (ethash *Ethash) Author(header *types.Header) (common.Address, error) {
return header.Coinbase, nil
}
2.2.2 VerifyHeader
检查一个头是否符合以太坊ethash引擎的共识规则,返回时又调用了另一个verifyHeader方法
func (ethash *Ethash) VerifyHeader(chain consensus.ChainHeaderReader, header *types.Header, seal bool) error {
// If we're running a full engine faking, accept any input as valid
// 如果处于ModeFullFake模式,接受任何有效的输入
if ethash.config.PowMode == ModeFullFake {
return nil
}
// Short circuit if the header is known, or its parent not
//如果header是已知的,或者父header是未知的,则返回
number := header.Number.Uint64()
if chain.GetHeader(header.Hash(), number) != nil {
return nil
}
parent := chain.GetHeader(header.ParentHash, number-1)
if parent == nil { // 获取父结点失败
return consensus.ErrUnknownAncestor
}
// Sanity checks passed, do a proper verification
//合理的通过检查,做适当的验证
return ethash.verifyHeader(chain, header, parent, false, seal, time.Now().Unix())
}
2.2.3 另一个verifyHeader
verifyHeader检查一个头是否符合以太坊ethash引擎的共识规则。参见YP 4.3.4节。“块头有效性”
func (ethash *Ethash) verifyHeader(chain consensus.ChainHeaderReader, header, parent *types.Header, uncle bool, seal bool, unixNow int64) error {
// Ensure that the header's extra-data section is of a reasonable size
// 确保额外数据段具有合理的长度
if uint64(len(header.Extra)) > params.MaximumExtraDataSize {
return fmt.Errorf("extra-data too long: %d > %d", len(header.Extra), params.MaximumExtraDataSize)
}
// Verify the header's timestamp
// 校验时间戳
if !uncle {
if header.Time > uint64(unixNow+allowedFutureBlockTimeSeconds) {
return consensus.ErrFutureBlock
}
}
// 要求当前时间戳大于父时间戳
if header.Time <= parent.Time {
return errOlderBlockTime
}
// Verify the block's difficulty based on its timestamp and parent's difficulty
// 根据时间戳和父级块的难度校验块的难度。
expected := ethash.CalcDifficulty(chain, header.Time, parent)
//判断计算值与预期值是否一致
if expected.Cmp(header.Difficulty) != 0 {
return fmt.Errorf("invalid difficulty: have %v, want %v", header.Difficulty, expected)
}
// 校验gas limit <= 2^63-1
if header.GasLimit > params.MaxGasLimit {
return fmt.Errorf("invalid gasLimit: have %v, max %v", header.GasLimit, params.MaxGasLimit)
}
// 校验 gasUsed <= gasLimit
if header.GasUsed > header.GasLimit {
return fmt.Errorf("invalid gasUsed: have %d, gasLimit %d", header.GasUsed, header.GasLimit)
}
// Verify the block's gas usage and (if applicable) verify the base fee.
// 核实区块的gas使用情况,并核实基础费用(如果适用)。
if !chain.Config().IsLondon(header.Number) {
// Verify BaseFee not present before EIP-1559 fork.
//验证BaseFee这一操作在EIP-1559分支前是不存在的
if header.BaseFee != nil {
return fmt.Errorf("invalid baseFee before fork: have %d, expected 'nil'", header.BaseFee)
}
if err := misc.VerifyGaslimit(parent.GasLimit, header.GasLimit); err != nil {
return err
}
} else if err := misc.VerifyEip1559Header(chain.Config(), parent, header); err != nil {
// Verify the header's EIP-1559 attributes.
return err
}
// 验证块号是父级的+1
if diff := new(big.Int).Sub(header.Number, parent.Number); diff.Cmp(big.NewInt(1)) != 0 {
return consensus.ErrInvalidNumber
}
// Verify the engine specific seal securing the block
//校验特定的块是否符合要求
if seal {
if err := ethash.verifySeal(chain, header, false); err != nil {
return err
}
}
// If all checks passed, validate any special fields for hard forks
// 如果所有检查通过,则验证硬分叉的特殊字段。
if err := misc.VerifyDAOHeaderExtraData(chain.Config(), header); err != nil {
return err
}
if err := misc.VerifyForkHashes(chain.Config(), header, uncle); err != nil {
return err
}
return nil
}
2.2.4 VerifyHeaders
VerifyHeaders和VerifyHeader类似,只是VerifyHeaders进行批量校验操作。
创建多个goroutine用于执行校验操作,再创建一个goroutine用于赋值控制任务分配和结果获取。最后返回一个结果channel
func (ethash *Ethash) VerifyHeaders(chain consensus.ChainHeaderReader, headers []*types.Header, seals []bool) (chan<- struct{}, <-chan error) {
// If we're running a full engine faking, accept any input as valid
//ModeFullFake模式下,任何输入都是有效的
if ethash.config.PowMode == ModeFullFake || len(headers) == 0 {
abort, results := make(chan struct{}), make(chan error, len(headers))
for i := 0; i < len(headers); i++ {
results <- nil
}
return abort, results
}
// Spawn as many workers as allowed threads
//生成尽可能多的工作线程
workers := runtime.GOMAXPROCS(0)
if len(headers) < workers {
workers = len(headers)
}
// Create a task channel and spawn the verifiers
//创建任务通道并生成验证器
var (
inputs = make(chan int)
done = make(chan int, workers)
errors = make([]error, len(headers))
abort = make(chan struct{})
unixNow = time.Now().Unix()
)
for i := 0; i < workers; i++ {
// 产生workers个goroutine用于校验头
go func() {
for index := range inputs {
errors[index] = ethash.verifyHeaderWorker(chain, headers, seals, index, unixNow)
done <- index
}
}()
}
// goroutine 用于发送消息到workers个goroutine上
errorsOut := make(chan error, len(headers))
go func() {
defer close(inputs)
var (
in, out = 0, 0
checked = make([]bool, len(headers))
inputs = inputs
)
for {
select {
case inputs <- in:
if in++; in == len(headers) {
// Reached end of headers. Stop sending to workers.
inputs = nil
}
// 统计结果,并把错误消息发送到errorsOut上
case index := <-done:
for checked[index] = true; checked[out]; out++ {
errorsOut <- errors[out]
if out == len(headers)-1 {
return
}
}
case <-abort:
return
}
}
}()
return abort, errorsOut
}
2.2.5 Prepare
Prepare实现共识引擎的Prepare接口,用于填充区块头的难度字段,使之符合ethash协议。这个改变是在线的。
func (ethash *Ethash) Prepare(chain consensus.ChainHeaderReader, header *types.Header) error {
parent := chain.GetHeader(header.ParentHash, header.Number.Uint64()-1)
if parent == nil {
return consensus.ErrUnknownAncestor
}
header.Difficulty = ethash.CalcDifficulty(chain, header.Time, parent)
return nil
}
2.2.6 Finalize
Finalize实现共识引擎的Finalize接口,奖励挖到区块账户和叔块账户,并填充状态树的根的值。并返回新的区块。
func (ethash *Ethash) Finalize(chain consensus.ChainHeaderReader, header *types.Header, state *state.StateDB, txs []*types.Transaction, uncles []*types.Header) {
// Accumulate any block and uncle rewards and commit the final state root
accumulateRewards(chain.Config(), state, header, uncles)
header.Root = state.IntermediateRoot(chain.Config().IsEIP158(header.Number))
}
accumulateRewards用于计算奖励,总奖励包括静态块奖励和包含叔叔的奖励。每个叔叔块的coinbase也会得到奖励。
func accumulateRewards(config *params.ChainConfig, state *state.StateDB, header *types.Header, uncles []*types.Header) {
// Select the correct block reward based on chain progression
//根据进程选择正确的区块奖励
blockReward := FrontierBlockReward
//拜占庭和君士坦丁堡两个阶段的奖励是不同的
if config.IsByzantium(header.Number) {
blockReward = ByzantiumBlockReward
}
if config.IsConstantinople(header.Number) {
blockReward = ConstantinopleBlockReward
}
// Accumulate the rewards for the miner and any included uncles
//基础的blockReward挖矿奖励 再加上其他叔块的奖励
reward := new(big.Int).Set(blockReward)
r := new(big.Int)
// 叔块奖励为(叔块number+8 - 当前块number) * blockReward/8
// 引用叔块奖励为标准块的32分之一
for _, uncle := range uncles {
// (叔块number+8 - 当前块number) * blockReward/8
r.Add(uncle.Number, big8)
r.Sub(r, header.Number)
r.Mul(r, blockReward)
r.Div(r, big8)
//AddBalance添加金额到与addr相关联的帐户。
state.AddBalance(uncle.Coinbase, r)
//正常块奖励的32分之一
r.Div(blockReward, big32)
reward.Add(reward, r)
}
// 奖励coinbase账户
state.AddBalance(header.Coinbase, reward)
}
2.2.7 Seal (设置挖矿要用的nonce)
在CPU挖矿部分,CpuAgent的mine函数,执行挖矿操作的时候调用了Seal函数。Seal函数尝试找出一个满足区块难度的nonce值。
在ModeFake和ModeFullFake模式下,快速返回,并且直接将nonce值取0。
在shared PoW模式下,使用shared的Seal函数。
开启threads个goroutine进行挖矿(查找符合条件的nonce值)。
// 尝试去寻找一个满足区块难度要求的nonce
func (ethash *Ethash) Seal(chain consensus.ChainHeaderReader, block *types.Block, results chan<- *types.Block, stop <-chan struct{}) error {
// If we're running a fake PoW, simply return a 0 nonce immediately
//如果我们正在运行一个假的PoW,只需立即返回一个0
if ethash.config.PowMode == ModeFake || ethash.config.PowMode == ModeFullFake {
header := block.Header()
header.Nonce, header.MixDigest = types.BlockNonce{}, common.Hash{}
select {
case results <- block.WithSeal(header):
default:
ethash.config.Log.Warn("Sealing result is not read by miner", "mode", "fake", "sealhash", ethash.SealHash(block.Header()))
}
return nil
}
// If we're running a shared PoW, delegate sealing to it
//如果我们正在运行一个共享的PoW,将密封委托给它
if ethash.shared != nil {
return ethash.shared.Seal(chain, block, results, stop)
}
// Create a runner and the multiple search threads it directs
abort := make(chan struct{})
ethash.lock.Lock()
// 使用多线程去寻找nonce
threads := ethash.threads
if ethash.rand == nil {
seed, err := crand.Int(crand.Reader, big.NewInt(math.MaxInt64))
if err != nil {
ethash.lock.Unlock()
return err
}
ethash.rand = rand.New(rand.NewSource(seed.Int64()))
}
ethash.lock.Unlock()
if threads == 0 {
threads = runtime.NumCPU()
}
if threads < 0 {
threads = 0 // Allows disabling local mining without extra logic around local/remote
}
// Push new work to remote sealer
if ethash.remote != nil {
ethash.remote.workCh <- &sealTask{block: block, results: results}
}
var (
pend sync.WaitGroup
locals = make(chan *types.Block)
)
for i := 0; i < threads; i++ {
pend.Add(1)
go func(id int, nonce uint64) {
defer pend.Done()
ethash.mine(block, id, nonce, abort, locals)
}(i, uint64(ethash.rand.Int63()))
}
// Wait until sealing is terminated or a nonce is found
// 等待直到密封终止或发现一个瞬间
go func() {
var result *types.Block
select {
case <-stop:
// Outside abort, stop all miner threads
// 在外部中止,停止所有矿工线程
close(abort)
case result = <-locals:
// One of the threads found a block, abort all others
// 其中一个线程发现了一个块,中止所有其他线程
select {
case results <- result:
default:
ethash.config.Log.Warn("Sealing result is not read by miner", "mode", "local", "sealhash", ethash.SealHash(block.Header()))
}
close(abort)
case <-ethash.update:
// Thread count was changed on user request, restart
//线程数在用户请求时改变,重新启动
close(abort)
if err := ethash.Seal(chain, block, results, stop); err != nil {
ethash.config.Log.Error("Failed to restart sealing after update", "err", err)
}
}
// Wait for all miners to terminate and return the block
// 等待所有的挖矿goroutine返回
pend.Wait()
}()
return nil
}
2.2.8 mine
mine是真正的查找nonce值的函数,它不断遍历查找nonce值,并计算PoW值与目标值进行比较。
其原理可以简述为下:
RAND(h, n) <= M / d
这里M表示一个极大的数,这里是2^256-1;d表示Header成员Difficulty。RAND()是一个概念函数,它代表了一系列复杂的运算,并最终产生一个类似随机的数。这个函数包括两个基本入参:h是Header的哈希值(Header.HashNoNonce()),n表示Header成员Nonce。整个关系式可以大致理解为,在最大不超过M的范围内,以某个方式试图找到一个数,如果这个数符合条件(<=M/d),那么就认为Seal()成功。
由上面的公式可以得知,M恒定,d越大则可取范围越小。所以当难度值增加时,挖出区块的难度也在增加。
// mine是真正的查找nonce值的函数,它不断遍历查找nonce值,并计算PoW值与目标值进行比较。
func (ethash *Ethash) mine(block *types.Block, id int, seed uint64, abort chan struct{}, found chan *types.Block) {
// Extract some data from the header
// 从区块头中获取一些数据
var (
header = block.Header()
hash = ethash.SealHash(header).Bytes()
// target 即查找的PoW的上限 target = maxUint256/Difficulty
// 其中maxUint256 = 2^256-1 Difficulty即难度值
target = new(big.Int).Div(two256, header.Difficulty)
number = header.Number.Uint64()
dataset = ethash.dataset(number, false)
)
// Start generating random nonces until we abort or find a good one
// 尝试查找一个nonce值,直到终止或者找到目标值
var (
attempts = int64(0)
nonce = seed
powBuffer = new(big.Int)
)
logger := ethash.config.Log.New("miner", id)
logger.Trace("Started ethash search for new nonces", "seed", seed)
search:
for {
select {
case <-abort:
// Mining terminated, update stats and abort
// 终止挖矿
logger.Trace("Ethash nonce search aborted", "attempts", nonce-seed)
ethash.hashrate.Mark(attempts)
break search
default:
// We don't have to update hash rate on every nonce, so update after after 2^X nonces
// 不必在每个nonce值都更新hash rate,每2^x个nonce值更新一次hash rate
attempts++
if (attempts % (1 << 15)) == 0 {
ethash.hashrate.Mark(attempts)
attempts = 0
}
// Compute the PoW value of this nonce
// 用这个nonce计算PoW值
digest, result := hashimotoFull(dataset.dataset, hash, nonce)
// 将计算的结果与目标值比较,如果小于目标值,则查找成功。
if powBuffer.SetBytes(result).Cmp(target) <= 0 {
// Correct nonce found, create a new header with it
// 查找到nonce值,更新区块头
header = types.CopyHeader(header)
header.Nonce = types.EncodeNonce(nonce)
header.MixDigest = common.BytesToHash(digest)
// Seal and return a block (if still needed)
// 打包区块头并返回
select {
// WithSeal 将新的区块头替换旧的区块头
case found <- block.WithSeal(header):
logger.Trace("Ethash nonce found and reported", "attempts", nonce-seed, "nonce", nonce)
case <-abort:
logger.Trace("Ethash nonce found but discarded", "attempts", nonce-seed, "nonce", nonce)
}
break search
}
nonce++
}
}
// Datasets are unmapped in a finalizer. Ensure that the dataset stays live
// during sealing so it's not unmapped while being read.
runtime.KeepAlive(dataset)
}
2.2.9 hashimotoFull
上述函数调用了hashimotoFull函数用来计算PoW的值。
hashimoto用于聚合数据以产生特定的后部的hash和nonce值。
func hashimotoFull(dataset []uint32, hash []byte, nonce uint64) ([]byte, []byte) {
lookup := func(index uint32) []uint32 {
offset := index * hashWords
return dataset[offset : offset+hashWords]
}
return hashimoto(hash, nonce, uint64(len(dataset))*4, lookup)
}
之后又调用了hashimoto函数:
简述该部分流程:
- 首先,hashimoto()函数将入参@hash和@nonce合并成一个40
bytes长的数组,取它的SHA-512哈希值取名seed,长度为64 bytes。 - 然后,将seed[]转化成以uint32为元素的数组mix[],注意一个uint32数等于4
bytes,故而seed[]只能转化成16个uint32数,而mix[]数组长度32,所以此时mix[]数组前后各半是等值的。 - 接着,lookup()函数登场。用一个循环,不断调用lookup()从外部数据集中取出uint32元素类型数组,向mix[]数组中混入未知的数据。循环的次数可用参数调节,目前设为64次。每次循环中,变化生成参数index,从而使得每次调用lookup()函数取出的数组都各不相同。这里混入数据的方式是一种类似向量“异或”的操作,来自于FNV算法。
- 待混淆数据完成后,得到一个基本上面目全非的mix[],长度为32的uint32数组。这时,将其折叠(压缩)成一个长度缩小成原长1/4的uint32数组,折叠的操作方法还是来自FNV算法。
- 最后,将折叠后的mix[]由长度为8的uint32型数组直接转化成一个长度32的byte数组,这就是返回值digest;同时将之前的seed[]数组与digest合并再取一次SHA-256哈希值,得到的长度32的byte数组,即返回值result。
//以特定的头哈希和nonce生成最终值。
func hashimoto(hash []byte, nonce uint64, size uint64, lookup func(index uint32) []uint32) ([]byte, []byte) {
// Calculate the number of theoretical rows (we use one buffer nonetheless)
// 计算理论行数
rows := uint32(size / mixBytes)
// Combine header+nonce into a 64 byte seed
// 将 header+nonce into 装换为64字节的seed
seed := make([]byte, 40)
copy(seed, hash)
binary.LittleEndian.PutUint64(seed[32:], nonce)
seed = crypto.Keccak512(seed)
seedHead := binary.LittleEndian.Uint32(seed)
// Start the mix with replicated seed
// 将seed[]转化成以uint32为元素的数组mix[]
mix := make([]uint32, mixBytes/4)
for i := 0; i < len(mix); i++ {
mix[i] = binary.LittleEndian.Uint32(seed[i%16*4:])
}
// Mix in random dataset nodes
// 向mix[]数组中混入未知的数据
temp := make([]uint32, len(mix))
for i := 0; i < loopAccesses; i++ {
parent := fnv(uint32(i)^seedHead, mix[i%len(mix)]) % rows
for j := uint32(0); j < mixBytes/hashBytes; j++ {
copy(temp[j*hashWords:], lookup(2*parent+j))
}
fnvHash(mix, temp)
}
// Compress mix
// 压缩成一个长度缩小成原长1/4的uint32数组
for i := 0; i < len(mix); i += 4 {
mix[i/4] = fnv(fnv(fnv(mix[i], mix[i+1]), mix[i+2]), mix[i+3])
}
mix = mix[:len(mix)/4]
digest := make([]byte, common.HashLength)
for i, val := range mix {
binary.LittleEndian.PutUint32(digest[i*4:], val)
}
return digest, crypto.Keccak256(append(seed, digest...))
}
最终经过一系列多次、多种的哈希运算,hashimoto()返回两个长度均为32的byte数组 - digest[]和result[]。回忆一下ethash.mine()函数中,对于hashimotoFull()的两个返回值,会直接以big.int整型数形式比较result和target;如果符合要求,则将digest取SHA3-256的哈希值(256 bits),并存于Header.MixDigest中,待以后Ethash.VerifySeal()可以加以验证。
上述hashimoto()函数中,函数型入参lookup()其实表示的是一次以非线性表查找方式进行的哈希运算,lookup()以入参为key,从所关联的数据集中按照定义好的一段逻辑取出64 bytes长的数据作为hash value并返回,注意返回值以uint32的形式则相应变成16个uint32长。返回的数据会在hashimoto()函数被其他的哈希运算所使用。
以非线性表的查找方式进行的哈希运算(hashing by nonlinear table lookup),属于众多哈希函数中的一种实现,在Ethash算法的核心环节有大量使用,所使用到的非线性表被定义成两种结构体,分别叫cache{}和dataset{}。二者的差异主要是表格的规模和调用场景:dataset{}中的数据规模更加巨大,从而会被hashimotoFull()调用从而服务于Ethash.Seal();cache{}内含数据规模相对较小,会被hashimotoLight()调用并服务于Ethash.VerifySeal()。
以cache{}的结构体声明为例,成员cache就是实际使用的一块内存Buffer,mmap是内存映射对象,dump是该内存buffer存储于磁盘空间的文件对象,epoch是共享这个cache{}对象的一组区块的序号。从上述UML图来看,cache和dataset的结构体声明基本一样,这也暗示了它们无论是原理还是行为都极为相似。
2.3 EthHash分析
2.3.1 POW接口
// PoW is a consensus engine based on proof-of-work.
//PoW是一个基于工作证明的共识引擎。
type PoW interface {
Engine
// Hashrate returns the current mining hashrate of a PoW consensus engine.
//返回PoW共识引擎的当前挖掘哈希率。
Hashrate() float64
}
hashrate:每秒可以完成哈希碰撞的次数
哈希碰撞:矿工要解的数学题难度,相当于扔1亿个骰子,扔出小于1亿零6的数字,谁先扔出来,谁就赢得记账权。
1亿零6,就是哈希值。扔骰子的过程,就是哈希碰撞。
2.3.2 Ethash 结构体
Ethhash是实现PoW的具体实现,由于要使用到大量的数据集,所有有两个指向lru的指针。并且通过threads控制挖矿线程数。并在测试模式或fake模式下,简单快速处理,使之快速得到结果。
type Ethash struct {
//Config是ethash的配置参数。
config Config
//缓存
caches *lru // In memory caches to avoid regenerating too often
//内存数据集
datasets *lru // In memory datasets to avoid regenerating too often
// Mining related fields
//挖矿相关字段
//随机数种子
rand *rand.Rand // Properly seeded random source for nonces
// 挖矿线程数量
threads int // Number of threads to mine on if mining
// channel 用于更新挖矿通知
update chan struct{} // Notification channel to update mining parameters
//平均哈希率记录表
hashrate metrics.Meter // Meter tracking the average hashrate
remote *remoteSealer
// The fields below are hooks for testing
// 测试网络相关参数
//共享PoW验证器,以避免缓存再生
shared *Ethash // Shared PoW verifier to avoid cache regeneration
//即使在假模式下也不能通过PoW检查的方块号
Fail uint64 // Block number which fails PoW check even in fake mode
//从verify返回前的延迟睡眠时间
fakeDelay time.Duration // Time delay to sleep for before returning from verify
//确保缓存和挖矿的线程安全锁
lock sync.Mutex // Ensures thread safety for the in-memory caches and mining fields
//确保出口通道不会被关闭两次。
closeOnce sync.Once // Ensures exit channel will not be closed twice.
}
2.4 Ethash算法总结
回看一下Ethash共识算法最基本的形态,如果把整个result[]的生成过程视作那个概念上的函数RAND(),则如何能更加随机,分布更加均匀的生成数组,关系到整个Ethash算法的安全性。毕竟如果result[]生成过程存在被破译的途径,那么必然有方法可以更快地找到符合条件的数组,通过更快的挖掘出区块,在整个以太坊系统中逐渐占据主导。所以Ethash共识算法应用了非常复杂的一系列运算,包含了多次、多种不同的哈希函数运算:
- 大量使用SHA3哈希函数,包括256-bit和512-bit形式的,用它们来对数据(组)作哈希运算,或者充当其他更复杂哈希计算的某个原型 – 比如调用makeHasher()。而SHA3哈希函数,是一种典型的可应对长度变化的输入数据的哈希函数,输出结果长度统一(可指定256bits或512bits)。
- lookup()函数提供了非线性表格查找方式的哈希函数,相关联的dataset{}和cache{}规模巨大,其中数据的生成/填充过程中也大量使用哈希函数。
- 在一些计算过程中,有意将[]byte数组转化为uint32或uint64整型数进行操作(比如XOR,以及类XOR的FNV()函数)。因为理论证实,在32位或64位CPU机器上,以32位/64位整型数进行操作时,速度更快。