以太坊码源分析:POW(2)

二、源代码分析

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位整型数进行操作时,速度更快。
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

yitahutu79

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值