以太坊1.9consensus包解析

源码中/consensus/consensus.go中的type Engine interface {}定义了与算法无关的共识引擎,然后在ethash和clique中分别实现了各自的共识方式,其中ethash实现的是pow,clique实现的是poa。接下来对这个接口中定义的方法逐个分析,以ethash包为主。

首先看下Engine中都定义了哪些接口:

type Engine interface {
  
   Author(header *types.Header) (common.Address, error)

   VerifyHeader(chain ChainHeaderReader, header *types.Header, seal bool) error

   VerifyHeaders(chain ChainHeaderReader, headers []*types.Header, seals []bool) (chan<- struct{}, <-chan error)

   VerifyUncles(chain ChainReader, block *types.Block) error

   VerifySeal(chain ChainHeaderReader, header *types.Header) error

   Prepare(chain ChainHeaderReader, header *types.Header) error

   Finalize(chain ChainHeaderReader, header *types.Header, state *state.StateDB, txs []*types.Transaction,
      uncles []*types.Header)

   FinalizeAndAssemble(chain ChainHeaderReader, header *types.Header, state *state.StateDB, txs []*types.Transaction,
      uncles []*types.Header, receipts []*types.Receipt) (*types.Block, error)

   Seal(chain ChainHeaderReader, block *types.Block, results chan<- *types.Block, stop <-chan struct{}) error

   SealHash(header *types.Header) common.Hash

   CalcDifficulty(chain ChainHeaderReader, time uint64, parent *types.Header) *big.Int

   APIs(chain ChainHeaderReader) []rpc.API

   Close() error
}

接下来看看各自的实现与作用

1,Author

返回传入区块头的coinbase,即挖矿人

func (ethash *Ethash) Author(header *types.Header) (common.Address, error) {
   return header.Coinbase, nil
}

2,VerifyHeader

主要是用来校验区块头是否满足共识算法规则

func (ethash *Ethash) VerifyHeader(chain consensus.ChainHeaderReader, header *types.Header, seal bool) error {
   // 非正式环境,直接通过
   if ethash.config.PowMode == ModeFullFake {
      return nil
   }
   // 如果是已经存在的区块头,通过
   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
   }
   return ethash.verifyHeader(chain, header, parent, false, seal)
}

看下verifyHeader方法,该方法多出被用到

func (ethash *Ethash) verifyHeader(chain consensus.ChainHeaderReader, header, parent *types.Header, uncle bool, seal bool) error {
   // Extra字段不能超长
   if uint64(len(header.Extra)) > params.MaximumExtraDataSize {
      return fmt.Errorf("extra-data too long: %d > %d", len(header.Extra), params.MaximumExtraDataSize)
   }
   // 时间戳在未来太远
   if !uncle {
      if header.Time > uint64(time.Now().Add(allowedFutureBlockTime).Unix()) {
         return consensus.ErrFutureBlock
      }
   }
   // 时间戳太小,小于父块
   if header.Time <= parent.Time {
      return errOlderBlockTime
   }
   // 调用CalcDifficulty方法查看当前区块的难度值,该方法后文会介绍
   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)
   }
   // GasLimit超标
   cap := uint64(0x7fffffffffffffff)
   if header.GasLimit > cap {
      return fmt.Errorf("invalid gasLimit: have %v, max %v", header.GasLimit, cap)
   }
   // gasUsed不能大于gasLimit(<= 2^63-1)
   if header.GasUsed > header.GasLimit {
      return fmt.Errorf("invalid gasUsed: have %d, gasLimit %d", header.GasUsed, header.GasLimit)
   }

   // GasLimit不能超过允许的范围:  abs(gasLimit - 父gasLimit) < (父gasLimit/1024) &&  gasLimit > 5000
   diff := int64(parent.GasLimit) - int64(header.GasLimit)
   if diff < 0 {
      diff *= -1
   }
   limit := parent.GasLimit / params.GasLimitBoundDivisor

   if uint64(diff) >= limit || header.GasLimit < params.MinGasLimit {
      return fmt.Errorf("invalid gas limit: have %d, want %d += %d", header.GasLimit, parent.GasLimit, limit)
   }
   // 区块高度是父块+1
   if diff := new(big.Int).Sub(header.Number, parent.Number); diff.Cmp(big.NewInt(1)) != 0 {
      return consensus.ErrInvalidNumber
   }
   // 调用VerifySeal验证是否满足pow难度要求,该方法后文介绍
   if seal {
      if err := ethash.VerifySeal(chain, header); err != nil {
         return err
      }
   }
   // 验证硬分叉的字段
   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
}

3,VerifyHeaders

该方法与VerifyHeader类似,区别在于VerifyHeaders可以传入多个header进行校验,调用多个协程同步进行,并以channel的方式异步返回错误

4,VerifyUncles

校验叔块是否合规,传入的block中带有uncles字段

func (ethash *Ethash) VerifyUncles(chain consensus.ChainReader, block *types.Block) error {
   if ethash.config.PowMode == ModeFullFake {
      return nil
   }
   // 叔块最多有2个
   if len(block.Uncles()) > maxUncles {
      return errTooManyUncles
   }
   if len(block.Uncles()) == 0 {
      return nil
   }
   // 第一步:
   // 获取当前block往前数7个块以及block自身,一共8个块,放入ancestors中(超过7个块,叔块就失效了)
   // 获取ancestors中所有的叔块及block自身,放入uncles中
   uncles, ancestors := mapset.NewSet(), make(map[common.Hash]*types.Header)

   number, parent := block.NumberU64()-1, block.ParentHash()
   for i := 0; i < 7; i++ {
      ancestor := chain.GetBlock(parent, number)
      if ancestor == nil {
         break
      }
      ancestors[ancestor.Hash()] = ancestor.Header()
      for _, uncle := range ancestor.Uncles() {
         uncles.Add(uncle.Hash())
      }
      parent, number = ancestor.ParentHash(), number-1
   }
   ancestors[block.Hash()] = block.Header()
   uncles.Add(block.Hash())

   // 校验每个叔块,保证叔块是最新的,而不是包含在ancestors中的祖先区块或者是uncles中的祖先区块的叔块
   for _, uncle := range block.Uncles() {
      // Make sure every uncle is rewarded only once
      hash := uncle.Hash()
      if uncles.Contains(hash) {
         return errDuplicateUncle
      }
      uncles.Add(hash)

      // Make sure the uncle has a valid ancestry
      if ancestors[hash] != nil {
         return errUncleIsAncestor
      }
      if ancestors[uncle.ParentHash] == nil || uncle.ParentHash == block.ParentHash() {
         return errDanglingUncle
      }
      // 调用verifyHeader方法校验叔块的有效性,上文介绍过
      if err := ethash.verifyHeader(chain, uncle, ancestors[uncle.ParentHash], true, true); err != nil {
         return err
      }
   }
   return nil
}

5,VerifySeal

VerifySeal方法是检查区块是否符合pow的难度要求

func (ethash *Ethash) VerifySeal(chain consensus.ChainHeaderReader, header *types.Header) error {
   return ethash.verifySeal(chain, header, false)
}
func (ethash *Ethash) verifySeal(chain consensus.ChainHeaderReader, header *types.Header, fulldag bool) error {
   if ethash.config.PowMode == ModeFake || ethash.config.PowMode == ModeFullFake {
      time.Sleep(ethash.fakeDelay)
      if ethash.fakeFail == header.Number.Uint64() {
         return errInvalidPoW
      }
      return nil
   }
   if ethash.shared != nil {
      return ethash.shared.verifySeal(chain, header, fulldag)
   }
   // 确保难度合法
   if header.Difficulty.Sign() <= 0 {
      return errInvalidDifficulty
   }
   
   number := header.Number.Uint64()

   var (
      digest []byte
      result []byte
   )
   // 根据传入的fulldag参数,决定是采用dataset还是cache的方式去计算digest和result
   if fulldag {
      dataset := ethash.dataset(number, true)
      if dataset.generated() {
         digest, result = hashimotoFull(dataset.dataset, ethash.SealHash(header).Bytes(), header.Nonce.Uint64())

         // Datasets are unmapped in a finalizer. Ensure that the dataset stays alive
         // until after the call to hashimotoFull so it's not unmapped while being used.
         runtime.KeepAlive(dataset)
      } else {
         // Dataset not yet generated, don't hang, use a cache instead
         fulldag = false
      }
   }
   
   if !fulldag {
      cache := ethash.cache(number)

      size := datasetSize(number)
      if ethash.config.PowMode == ModeTest {
         size = 32 * 1024
      }
      digest, result = hashimotoLight(size, cache.cache, ethash.SealHash(header).Bytes(), header.Nonce.Uint64()).
      runtime.KeepAlive(cache)
   }
   // 比较digest是否相同
   if !bytes.Equal(header.MixDigest[:], digest) {
      return errInvalidMixDigest
   }
   // 比较难度是否符合要求
   target := new(big.Int).Div(two256, header.Difficulty)
   if new(big.Int).SetBytes(result).Cmp(target) > 0 {
      return errInvalidPoW
   }
   return nil
}

6,Prepare

该方法是将传递进来的区块头填充Difficulty字段进去

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
   }
   // 调用CalcDifficulty方法查看当前区块的难度值,该方法后文会介绍
   header.Difficulty = ethash.CalcDifficulty(chain, header.Time, parent)
   return nil
}

7,Finalize

计算块的奖励,根据不同的协议,给与不同的奖励数量,并设置Root字段

func (ethash *Ethash) Finalize(chain consensus.ChainHeaderReader, header *types.Header, state *state.StateDB, txs []*types.Transaction, uncles []*types.Header) {
   // 计算块的奖励,根据不同的协议,给与不同的奖励数量。叔块的奖励也是在这里计算并保存
   accumulateRewards(chain.Config(), state, header, uncles)
   // 设置header的Root字段
   header.Root = state.IntermediateRoot(chain.Config().IsEIP158(header.Number))
}

8,FinalizeAndAssemble

与Finalize方法相同,之后再组装成block并返回

func (ethash *Ethash) FinalizeAndAssemble(chain consensus.ChainHeaderReader, header *types.Header, state *state.StateDB, txs []*types.Transaction, uncles []*types.Header, receipts []*types.Receipt) (*types.Block, error) {
   //前面两步与Finalize方法相同
   accumulateRewards(chain.Config(), state, header, uncles)
   header.Root = state.IntermediateRoot(chain.Config().IsEIP158(header.Number))

   // 区别在于会组装成一个block并返回
   return types.NewBlock(header, txs, uncles, receipts, new(trie.Trie)), nil
}

9,Seal

Seal方法是寻找一个符合pow难度要求的nonce的过程

func (ethash *Ethash) Seal(chain consensus.ChainHeaderReader, block *types.Block, results chan<- *types.Block, stop <-chan struct{}) error {
   // 如果是非正式环境,直接返回0作为nonce,返回值通过chan results进行传递
   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
   }
   
   // 如果是shared PoW, 使用shared的Seal函数
   if ethash.shared != nil {
      return ethash.shared.Seal(chain, block, results, stop)
   }
   
   abort := make(chan struct{})

   ethash.lock.Lock()
   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
   }

   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()))
   }
   
   go func() {
      var result *types.Block
      select {
      case <-stop:
         
         close(abort)
      case result = <-locals:
         
         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:
         
         close(abort)
         if err := ethash.Seal(chain, block, results, stop); err != nil {
            ethash.config.Log.Error("Failed to restart sealing after update", "err", err)
         }
      }
      // 等待所有的挖矿goroutine返回
      pend.Wait()
   }()
   return nil
}

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越大则可取范围越小。所以当难度值增加时,挖出区块的难度也在增加。

func (ethash *Ethash) mine(block *types.Block, id int, seed uint64, abort chan struct{}, found chan *types.Block) {
    // 从区块头中获取一些数据
    var (
        header  = block.Header()
        hash    = header.HashNoNonce().Bytes()
        // target 即查找的PoW的上限 target = maxUint256/Difficulty
        // 其中maxUint256 = 2^256-1  Difficulty即难度值
        target  = new(big.Int).Div(maxUint256, header.Difficulty)
        number  = header.Number.Uint64()
        dataset = ethash.dataset(number)
    )
    // 尝试查找一个nonce值,直到终止或者找到目标值
    var (
        attempts = int64(0)
        nonce    = seed
    )
    logger := log.New("miner", id)
    logger.Trace("Started ethash search for new nonces", "seed", seed)
search:
    for {
        select {
        case <-abort:
            // 终止挖矿
            logger.Trace("Ethash nonce search aborted", "attempts", nonce-seed)
            ethash.hashrate.Mark(attempts)
            break search

        default:
            // 不必在每个nonce值都更新hash rate,每2^x个nonce值更新一次hash rate
            attempts++
            if (attempts % (1 << 15)) == 0 {
                ethash.hashrate.Mark(attempts)
                attempts = 0
            }
            // 用这个nonce计算PoW值
            digest, result := hashimotoFull(dataset.dataset, hash, nonce)
            // 将计算的结果与目标值比较,如果小于目标值,则查找成功。
            if new(big.Int).SetBytes(result).Cmp(target) <= 0 {
                // 查找到nonce值,更新区块头
                header = types.CopyHeader(header)
                header.Nonce = types.EncodeNonce(nonce)
                header.MixDigest = common.BytesToHash(digest)

                // 打包区块头并返回
                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++
        }
    }

    runtime.KeepAlive(dataset)
}

10,SealHash

SealHash方法是在seal之前返回一个区块的hash值

func (ethash *Ethash) SealHash(header *types.Header) (hash common.Hash) {
   hasher := sha3.NewLegacyKeccak256()

   rlp.Encode(hasher, []interface{}{
      header.ParentHash,
      header.UncleHash,
      header.Coinbase,
      header.Root,
      header.TxHash,
      header.ReceiptHash,
      header.Bloom,
      header.Difficulty,
      header.Number,
      header.GasLimit,
      header.GasUsed,
      header.Time,
      header.Extra,
   })
   hasher.Sum(hash[:0])
   return hash
}

11,CalcDifficulty

CalcDifficulty是调整难度的算法,用父块的出块时间和难度作为基数,计算创建新块时候应该具有的难度,不同的版本有不同的计算方式。

func (ethash *Ethash) CalcDifficulty(chain consensus.ChainHeaderReader, time uint64, parent *types.Header) *big.Int {
   return CalcDifficulty(chain.Config(), time, parent)
}

func CalcDifficulty(config *params.ChainConfig, time uint64, parent *types.Header) *big.Int {
   next := new(big.Int).Add(parent.Number, big1)
   switch {
   case config.IsMuirGlacier(next):
      return calcDifficultyEip2384(time, parent)
   case config.IsConstantinople(next):
      return calcDifficultyConstantinople(time, parent)
   case config.IsByzantium(next):
      return calcDifficultyByzantium(time, parent)
   case config.IsHomestead(next):
      return calcDifficultyHomestead(time, parent)
   default:
      return calcDifficultyFrontier(time, parent)
   }
}

12,APIs

注册API

13,Close

通知所有线程关闭


转载请注明来源

×

喜欢就点赞,疼爱就打赏