共识算法之POA一

标签:共识算法
发布时间:2019年01月14日 价值:20000.00 / 共识:26

POA共识机制

POA(Proof of Authority),即授权证明,是通过一组授过权的节点来生成新的区块和验证新的区块。
也叫(Proof of Activity)活跃证明,因为这组授权节点是从所有活跃节点中产生的。

授权节点(signers)

就是矿工节点,负责打包交易,产生区块,由授权的signer投票超过50%产生新的signer。如果signer作恶,他最多只能攻击连续块(SIGNER_COUNT / 2) + 1)中的一个,作恶期间可以由其他的signer投票踢出作恶的signer。

算法公式

  1. n = F(signer,msgHash)

其中F()是数字签名函数,n是签名后生成的数据,signer是授权节点,在以太坊中是一个Address类型(20 bytes)的地址。mshHash是要签名的数据摘要,对应以太坊中Hash类型(32bytes)的哈希值。签名算法F(),采用的是椭圆曲线数字签名算法(ECDSA)。
这个授权节点有一个限制,就是必须是认证(authorized)的。

现在我们通过源码看一下POA签名过程。

  1. func (s *Ethereum) StartMining(threads int) error {
  2. ~~~~~~代码省略
  3. // Configure the local mining address
  4. eb, err := s.Etherbase()
  5. if err != nil {
  6. log.Error("Cannot start mining without etherbase", "err", err)
  7. return fmt.Errorf("etherbase missing: %v", err)
  8. }
  9. if clique, ok := s.engine.(*clique.Clique); ok {
  10. wallet, err := s.accountManager.Find(accounts.Account{Address: eb})
  11. if wallet == nil || err != nil {
  12. log.Error("Etherbase account unavailable locally", "err", err)
  13. return fmt.Errorf("signer missing: %v", err)
  14. }
  15. clique.Authorize(eb, wallet.SignHash)
  16. }
  17. }

上面的代码在backend.go中。这里是Clique的初始化,通过判断当前s.engine的类型是否是Clique来初始化签名者(signer)和签名函数(SignHash)。
上面的代码中eb是当前节点的主帐户地址(acounts[0]),通过 clique.Authorize(eb, wallet.SignHash)配置签名者和签名函数,签名函数是通过wallet 对象获取的。
下面是Authorize和SignHash方法

  1. // Authorize injects a private key into the consensus engine to mint new blocks
  2. // with.
  3. func (c *Clique) Authorize(signer common.Address, signFn SignerFn) {
  4. c.lock.Lock()
  5. defer c.lock.Unlock()
  6. c.signer = signer
  7. c.signFn = signFn
  8. }
  1. // SignHash calculates a ECDSA signature for the given hash. The produced
  2. // signature is in the [R || S || V] format where V is 0 or 1.
  3. func (ks *KeyStore) SignHash(a accounts.Account, hash []byte) ([]byte, error) {
  4. // Look up the key to sign with and abort if it cannot be found
  5. ks.mu.RLock()
  6. defer ks.mu.RUnlock()
  7. unlockedKey, found := ks.unlocked[a.Address]
  8. if !found {
  9. return nil, ErrLocked
  10. }
  11. // Sign the hash using plain ECDSA operations
  12. return crypto.Sign(hash, unlockedKey.PrivateKey)
  13. }

可以看到SignHash函数的功能就是用签名者账户的私钥对hash数值进行签名。

设置了签名者和签名函数后,我们就看下整个POA最主要的部分,就是在签名者对区块进行签名认证的过程 :Seal()函数,在clique.go中

  1. // Seal implements consensus.Engine, attempting to create a sealed block using
  2. // the local signing credentials.
  3. func (c *Clique) Seal(chain consensus.ChainReader, block *types.Block, results chan<- *types.Block, stop <-chan struct{}) error {
  4. header := block.Header()
  5. // Sealing the genesis block is not supported
  6. number := header.Number.Uint64()
  7. if number == 0 {
  8. return errUnknownBlock
  9. }
  10. // For 0-period chains, refuse to seal empty blocks (no reward but would spin sealing)
  11. if c.config.Period == 0 && len(block.Transactions()) == 0 {
  12. log.Info("Sealing paused, waiting for transactions")
  13. return nil
  14. }
  15. // Don't hold the signer fields for the entire sealing procedure
  16. c.lock.RLock()
  17. signer, signFn := c.signer, c.signFn
  18. c.lock.RUnlock()
  19. // Bail out if we're unauthorized to sign a block
  20. snap, err := c.snapshot(chain, number-1, header.ParentHash, nil)
  21. if err != nil {
  22. return err
  23. }
  24. if _, authorized := snap.Signers[signer]; !authorized {
  25. return errUnauthorizedSigner
  26. }
  27. // If we're amongst the recent signers, wait for the next block
  28. for seen, recent := range snap.Recents {
  29. if recent == signer {
  30. // Signer is among recents, only wait if the current block doesn't shift it out
  31. if limit := uint64(len(snap.Signers)/2 + 1); number < limit || seen > number-limit {
  32. log.Info("Signed recently, must wait for others")
  33. return nil
  34. }
  35. }
  36. }
  37. // Sweet, the protocol permits us to sign the block, wait for our time
  38. delay := time.Unix(header.Time.Int64(), 0).Sub(time.Now()) // nolint: gosimple
  39. if header.Difficulty.Cmp(diffNoTurn) == 0 {
  40. // It's not our turn explicitly to sign, delay it a bit
  41. wiggle := time.Duration(len(snap.Signers)/2+1) * wiggleTime
  42. delay += time.Duration(rand.Int63n(int64(wiggle)))
  43. log.Trace("Out-of-turn signing requested", "wiggle", common.PrettyDuration(wiggle))
  44. }
  45. // Sign all the things!
  46. sighash, err := signFn(accounts.Account{Address: signer}, sigHash(header).Bytes())
  47. if err != nil {
  48. return err
  49. }
  50. copy(header.Extra[len(header.Extra)-extraSeal:], sighash)
  51. // Wait until sealing is terminated or delay timeout.
  52. log.Trace("Waiting for slot to sign and propagate", "delay", common.PrettyDuration(delay))
  53. go func() {
  54. select {
  55. case <-stop:
  56. return
  57. case <-time.After(delay):
  58. }
  59. select {
  60. case results <- block.WithSeal(header):
  61. default:
  62. log.Warn("Sealing result is not read by miner", "sealhash", c.SealHash(header))
  63. }
  64. }()
  65. return nil
  66. }

通过代码可以看到签名过程做了5步认证限制:
1.不给创世区块签名

  1. number := header.Number.Uint64()
  2. if number == 0 {
  3. return errUnknownBlock
  4. }

2.如果Period为0并且打包的交易个数为0,则是无效区块。

  1. // For 0-period chains, refuse to seal empty blocks (no reward but would spin sealing)
  2. if c.config.Period == 0 && len(block.Transactions()) == 0 {
  3. log.Info("Sealing paused, waiting for transactions")
  4. return nil
  5. }

3.检查该节点是否是授权节点

  1. // Bail out if we're unauthorized to sign a block
  2. snap, err := c.snapshot(chain, number-1, header.ParentHash, nil)
  3. if err != nil {
  4. return err
  5. }
  6. if _, authorized := snap.Signers[signer]; !authorized {
  7. return errUnauthorizedSigner
  8. }

4.检查该节点是否刚签过名

  1. // If we're amongst the recent signers, wait for the next block
  2. for seen, recent := range snap.Recents {
  3. if recent == signer {
  4. // Signer is among recents, only wait if the current block doesn't shift it out
  5. if limit := uint64(len(snap.Signers)/2 + 1); number < limit || seen > number-limit {
  6. log.Info("Signed recently, must wait for others")
  7. return nil
  8. }
  9. }
  10. }

5.检查是否轮到该节点签名了。

  1. // Sweet, the protocol permits us to sign the block, wait for our time
  2. delay := time.Unix(header.Time.Int64(), 0).Sub(time.Now()) // nolint: gosimple
  3. if header.Difficulty.Cmp(diffNoTurn) == 0 {
  4. // It's not our turn explicitly to sign, delay it a bit
  5. wiggle := time.Duration(len(snap.Signers)/2+1) * wiggleTime
  6. delay += time.Duration(rand.Int63n(int64(wiggle)))
  7. log.Trace("Out-of-turn signing requested", "wiggle", common.PrettyDuration(wiggle))
  8. }

上面五步执行完毕后,就可以使用签名者的SignFn函数签名了,我们接看往下看:

  1. // Sign all the things!
  2. sighash, err := signFn(accounts.Account{Address: signer}, sigHash(header).Bytes())
  3. if err != nil {
  4. return err
  5. }
  6. copy(header.Extra[len(header.Extra)-extraSeal:], sighash)
  7. // Wait until sealing is terminated or delay timeout.
  8. log.Trace("Waiting for slot to sign and propagate", "delay", common.PrettyDuration(delay))
  9. go func() {
  10. select {
  11. case <-stop:
  12. return
  13. case <-time.After(delay):
  14. }
  15. select {
  16. case results <- block.WithSeal(header):
  17. default:
  18. log.Warn("Sealing result is not read by miner", "sealhash", c.SealHash(header))
  19. }
  20. }()

通过上面的代码我们看到,在调用signFn后,将签名后的结果存放在了header的Extra字段中。最后通过block.WithSeal(header)函数 通过区块头重新组装成一个区块,并将结果赋给results,通过通道返回。

通过上面的代码,我们大概了解了POA共识是如何生成新的区块的。大家有时间可以到源码中再了解一下。

  • 分享 收藏
0 条评论
  • 这篇文章暂无评论,赶紧评论一下吧~