github.com/Blockdaemon/celo-blockchain@v0.0.0-20200129231733-e667f6b08419/consensus/istanbul/core/commit.go (about)

     1  // Copyright 2017 The go-ethereum Authors
     2  // This file is part of the go-ethereum library.
     3  //
     4  // The go-ethereum library is free software: you can redistribute it and/or modify
     5  // it under the terms of the GNU Lesser General Public License as published by
     6  // the Free Software Foundation, either version 3 of the License, or
     7  // (at your option) any later version.
     8  //
     9  // The go-ethereum library is distributed in the hope that it will be useful,
    10  // but WITHOUT ANY WARRANTY; without even the implied warranty of
    11  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
    12  // GNU Lesser General Public License for more details.
    13  //
    14  // You should have received a copy of the GNU Lesser General Public License
    15  // along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
    16  
    17  package core
    18  
    19  import (
    20  	"reflect"
    21  
    22  	"github.com/ethereum/go-ethereum/consensus/istanbul"
    23  	blscrypto "github.com/ethereum/go-ethereum/crypto/bls"
    24  )
    25  
    26  func (c *core) sendCommit() {
    27  	logger := c.newLogger("func", "sendCommit")
    28  	logger.Trace("Sending commit")
    29  	sub := c.current.Subject()
    30  	c.broadcastCommit(sub)
    31  }
    32  
    33  func (c *core) generateCommittedSeal(sub *istanbul.Subject) (blscrypto.SerializedSignature, error) {
    34  	seal := PrepareCommittedSeal(sub.Digest, sub.View.Round)
    35  	committedSeal, err := c.backend.SignBlockHeader(seal)
    36  	if err != nil {
    37  		return blscrypto.SerializedSignature{}, err
    38  	}
    39  	return committedSeal, nil
    40  }
    41  
    42  func (c *core) generateEpochValidatorSetData(blockNumber uint64, newValSet istanbul.ValidatorSet) ([]byte, error) {
    43  	if !istanbul.IsLastBlockOfEpoch(blockNumber, c.config.Epoch) {
    44  		return nil, errNotLastBlockInEpoch
    45  	}
    46  	blsPubKeys := []blscrypto.SerializedPublicKey{}
    47  	for _, v := range newValSet.List() {
    48  		blsPubKeys = append(blsPubKeys, v.BLSPublicKey())
    49  	}
    50  	maxNonSignersPlusOne := uint32(newValSet.Size() - newValSet.MinQuorumSize() + 1)
    51  	epochData, err := blscrypto.EncodeEpochSnarkData(blsPubKeys, maxNonSignersPlusOne, uint16(istanbul.GetEpochNumber(blockNumber, c.config.Epoch)))
    52  	if err != nil {
    53  		return nil, err
    54  	}
    55  
    56  	return epochData, nil
    57  }
    58  
    59  func (c *core) broadcastCommit(sub *istanbul.Subject) {
    60  	logger := c.newLogger("func", "broadcastCommit")
    61  
    62  	committedSeal, err := c.generateCommittedSeal(sub)
    63  	if err != nil {
    64  		logger.Error("Failed to commit seal", "err", err)
    65  		return
    66  	}
    67  
    68  	currentBlockNumber := c.current.Proposal().Number().Uint64()
    69  	newValSet, err := c.backend.NextBlockValidators(c.current.Proposal())
    70  	if err != nil {
    71  		logger.Error("Failed to get next block's validators", "err", err)
    72  		return
    73  	}
    74  	epochValidatorSetData, err := c.generateEpochValidatorSetData(currentBlockNumber, newValSet)
    75  	if err != nil && err != errNotLastBlockInEpoch {
    76  		logger.Error("Failed to create epoch validator set data", "err", err)
    77  		return
    78  	}
    79  	var epochValidatorSetSeal blscrypto.SerializedSignature
    80  	if err == nil {
    81  		epochValidatorSetSeal, err = c.backend.SignBLSWithCompositeHash(epochValidatorSetData[:])
    82  		if err != nil {
    83  			logger.Error("Failed to sign epoch validator set seal", "err", err)
    84  			return
    85  		}
    86  	}
    87  
    88  	committedSub := &istanbul.CommittedSubject{
    89  		Subject:               sub,
    90  		CommittedSeal:         committedSeal[:],
    91  		EpochValidatorSetSeal: epochValidatorSetSeal[:],
    92  	}
    93  	encodedCommittedSubject, err := Encode(committedSub)
    94  	if err != nil {
    95  		logger.Error("Failed to encode committedSubject", committedSub)
    96  	}
    97  
    98  	istMsg := istanbul.Message{
    99  		Code: istanbul.MsgCommit,
   100  		Msg:  encodedCommittedSubject,
   101  	}
   102  	c.broadcast(&istMsg)
   103  }
   104  
   105  func (c *core) handleCommit(msg *istanbul.Message) error {
   106  	// Decode COMMIT message
   107  	var commit *istanbul.CommittedSubject
   108  	err := msg.Decode(&commit)
   109  	if err != nil {
   110  		return errFailedDecodeCommit
   111  	}
   112  
   113  	err = c.checkMessage(istanbul.MsgCommit, commit.Subject.View)
   114  
   115  	if err == errOldMessage {
   116  		// Discard messages from previous views, unless they are commits from the previous sequence,
   117  		// with the same round as what we wound up finalizing, as we would be able to include those
   118  		// to create the ParentAggregatedSeal for our next proposal.
   119  		lastSubject, err := c.backend.LastSubject()
   120  		if err != nil {
   121  			return err
   122  		} else if commit.Subject.View.Cmp(lastSubject.View) != 0 {
   123  			return errOldMessage
   124  		}
   125  		return c.handleCheckedCommitForPreviousSequence(msg, commit)
   126  	} else if err != nil {
   127  		return err
   128  	}
   129  
   130  	return c.handleCheckedCommitForCurrentSequence(msg, commit)
   131  }
   132  
   133  func (c *core) handleCheckedCommitForPreviousSequence(msg *istanbul.Message, commit *istanbul.CommittedSubject) error {
   134  	logger := c.newLogger("func", "handleCheckedCommitForPreviousSequence", "tag", "handleMsg", "msg_view", commit.Subject.View)
   135  	headBlock := c.backend.GetCurrentHeadBlock()
   136  	// Retrieve the validator set for the previous proposal (which should
   137  	// match the one broadcast)
   138  	parentValset := c.backend.ParentBlockValidators(headBlock)
   139  	_, validator := parentValset.GetByAddress(msg.Address)
   140  	if validator == nil {
   141  		return errInvalidValidatorAddress
   142  	}
   143  	if err := c.verifyCommittedSeal(commit, validator); err != nil {
   144  		return errInvalidCommittedSeal
   145  	}
   146  	if headBlock.Number().Uint64() > 0 {
   147  		if err := c.verifyEpochValidatorSetSeal(commit, headBlock.Number().Uint64(), c.current.ValidatorSet(), validator); err != nil {
   148  			return errInvalidEpochValidatorSetSeal
   149  		}
   150  	}
   151  
   152  	// Ensure that the commit's digest (ie the received proposal's hash) matches the head block's hash
   153  	if headBlock.Number().Uint64() > 0 && commit.Subject.Digest != headBlock.Hash() {
   154  		logger.Debug("Received a commit message for the previous sequence with an unexpected hash", "expected", headBlock.Hash().String(), "received", commit.Subject.Digest.String())
   155  		return errInconsistentSubject
   156  	}
   157  
   158  	// Add the ParentCommit to current round state
   159  	if err := c.current.AddParentCommit(msg); err != nil {
   160  		logger.Error("Failed to record parent seal", "m", msg, "err", err)
   161  		return err
   162  	}
   163  	return nil
   164  }
   165  
   166  func (c *core) handleCheckedCommitForCurrentSequence(msg *istanbul.Message, commit *istanbul.CommittedSubject) error {
   167  	logger := c.newLogger("func", "handleCheckedCommitForCurrentSequence", "tag", "handleMsg")
   168  	validator := c.current.GetValidatorByAddress(msg.Address)
   169  	if validator == nil {
   170  		return errInvalidValidatorAddress
   171  	}
   172  
   173  	if err := c.verifyCommittedSeal(commit, validator); err != nil {
   174  		return errInvalidCommittedSeal
   175  	}
   176  
   177  	newValSet, err := c.backend.NextBlockValidators(c.current.Proposal())
   178  	if err != nil {
   179  		return err
   180  	}
   181  
   182  	if err := c.verifyEpochValidatorSetSeal(commit, c.current.Proposal().Number().Uint64(), newValSet, validator); err != nil {
   183  		return errInvalidEpochValidatorSetSeal
   184  	}
   185  
   186  	// ensure that the commit is in the current proposal
   187  	if err := c.verifyCommit(commit); err != nil {
   188  		return err
   189  	}
   190  
   191  	// Add the COMMIT message to current round state
   192  	if err := c.current.AddCommit(msg); err != nil {
   193  		logger.Error("Failed to record commit message", "m", msg, "err", err)
   194  		return err
   195  	}
   196  	numberOfCommits := c.current.Commits().Size()
   197  	minQuorumSize := c.current.ValidatorSet().MinQuorumSize()
   198  	logger.Trace("Accepted commit for current sequence", "Number of commits", numberOfCommits)
   199  
   200  	// Commit the proposal once we have enough COMMIT messages and we are not in the Committed state.
   201  	//
   202  	// If we already have a proposal, we may have chance to speed up the consensus process
   203  	// by committing the proposal without PREPARE messages.
   204  	// TODO(joshua): Remove state comparisons (or change the cmp function)
   205  	if numberOfCommits >= minQuorumSize && c.current.State().Cmp(StateCommitted) < 0 {
   206  		logger.Trace("Got a quorum of commits", "tag", "stateTransition", "commits", c.current.Commits)
   207  		err := c.commit()
   208  		if err != nil {
   209  			logger.Error("Failed to commit()", "err", err)
   210  			return err
   211  		}
   212  
   213  	} else if c.current.GetPrepareOrCommitSize() >= minQuorumSize && c.current.State().Cmp(StatePrepared) < 0 {
   214  		err := c.current.TransitionToPrepared(minQuorumSize)
   215  		if err != nil {
   216  			logger.Error("Failed to create and set preprared certificate", "err", err)
   217  			return err
   218  		}
   219  		// Process Backlog Messages
   220  		c.backlog.updateState(c.current.View(), c.current.State())
   221  
   222  		logger.Trace("Got quorum prepares or commits", "tag", "stateTransition", "commits", c.current.Commits, "prepares", c.current.Prepares)
   223  		c.sendCommit()
   224  	}
   225  	return nil
   226  
   227  }
   228  
   229  // verifyCommit verifies if the received COMMIT message is equivalent to our subject
   230  func (c *core) verifyCommit(commit *istanbul.CommittedSubject) error {
   231  	logger := c.newLogger("func", "verifyCommit")
   232  
   233  	sub := c.current.Subject()
   234  	if !reflect.DeepEqual(commit.Subject, sub) {
   235  		logger.Warn("Inconsistent subjects between commit and proposal", "expected", sub, "got", commit)
   236  		return errInconsistentSubject
   237  	}
   238  
   239  	return nil
   240  }
   241  
   242  // verifyCommittedSeal verifies the commit seal in the received COMMIT message
   243  func (c *core) verifyCommittedSeal(comSub *istanbul.CommittedSubject, src istanbul.Validator) error {
   244  	seal := PrepareCommittedSeal(comSub.Subject.Digest, comSub.Subject.View.Round)
   245  	return blscrypto.VerifySignature(src.BLSPublicKey(), seal, []byte{}, comSub.CommittedSeal, false)
   246  }
   247  
   248  // verifyEpochValidatorSetSeal verifies the epoch validator set seal in the received COMMIT message
   249  func (c *core) verifyEpochValidatorSetSeal(comSub *istanbul.CommittedSubject, blockNumber uint64, newValSet istanbul.ValidatorSet, src istanbul.Validator) error {
   250  	if blockNumber == 0 {
   251  		return nil
   252  	}
   253  	epochData, err := c.generateEpochValidatorSetData(blockNumber, newValSet)
   254  	if err != nil {
   255  		if err == errNotLastBlockInEpoch {
   256  			return nil
   257  		}
   258  		return err
   259  	}
   260  	return blscrypto.VerifySignature(src.BLSPublicKey(), epochData[:], []byte{}, comSub.EpochValidatorSetSeal, true)
   261  }