github.com/klaytn/klaytn@v1.10.2/consensus/istanbul/core/commit.go (about)

     1  // Modifications Copyright 2018 The klaytn Authors
     2  // Copyright 2017 The go-ethereum Authors
     3  // This file is part of the go-ethereum library.
     4  //
     5  // The go-ethereum library is free software: you can redistribute it and/or modify
     6  // it under the terms of the GNU Lesser General Public License as published by
     7  // the Free Software Foundation, either version 3 of the License, or
     8  // (at your option) any later version.
     9  //
    10  // The go-ethereum library is distributed in the hope that it will be useful,
    11  // but WITHOUT ANY WARRANTY; without even the implied warranty of
    12  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
    13  // GNU Lesser General Public License for more details.
    14  //
    15  // You should have received a copy of the GNU Lesser General Public License
    16  // along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
    17  //
    18  // This file is derived from quorum/consensus/istanbul/core/commit.go (2018/06/04).
    19  // Modified and improved for the klaytn development.
    20  
    21  package core
    22  
    23  import (
    24  	"reflect"
    25  
    26  	"github.com/klaytn/klaytn/common"
    27  	"github.com/klaytn/klaytn/consensus/istanbul"
    28  )
    29  
    30  func (c *core) sendCommit() {
    31  	logger := c.logger.NewWith("state", c.state)
    32  	if c.current.Preprepare == nil {
    33  		logger.Error("Failed to get parentHash from roundState in sendCommit")
    34  		return
    35  	}
    36  
    37  	sub := c.current.Subject()
    38  	prevHash := c.current.Proposal().ParentHash()
    39  
    40  	// Do not send message if the owner of the core is not a member of the committee for the `sub.View`
    41  	if !c.valSet.CheckInSubList(prevHash, sub.View, c.Address()) {
    42  		return
    43  	}
    44  
    45  	// TODO-Klaytn-Istanbul: generalize broadcastCommit for all istanbul message types
    46  	c.broadcastCommit(sub)
    47  }
    48  
    49  func (c *core) sendCommitForOldBlock(view *istanbul.View, digest common.Hash, prevHash common.Hash) {
    50  	sub := &istanbul.Subject{
    51  		View:     view,
    52  		Digest:   digest,
    53  		PrevHash: prevHash,
    54  	}
    55  	c.broadcastCommit(sub)
    56  }
    57  
    58  func (c *core) broadcastCommit(sub *istanbul.Subject) {
    59  	logger := c.logger.NewWith("state", c.state)
    60  
    61  	encodedSubject, err := Encode(sub)
    62  	if err != nil {
    63  		logger.Error("Failed to encode", "subject", sub)
    64  		return
    65  	}
    66  
    67  	c.broadcast(&message{
    68  		Hash: sub.PrevHash,
    69  		Code: msgCommit,
    70  		Msg:  encodedSubject,
    71  	})
    72  }
    73  
    74  func (c *core) handleCommit(msg *message, src istanbul.Validator) error {
    75  	// Decode COMMIT message
    76  	var commit *istanbul.Subject
    77  	err := msg.Decode(&commit)
    78  	if err != nil {
    79  		logger.Error("Failed to decode message", "code", msg.Code, "err", err)
    80  		return errInvalidMessage
    81  	}
    82  
    83  	// logger.Error("receive handle commit","num", commit.View.Sequence)
    84  	if err := c.checkMessage(msgCommit, commit.View); err != nil {
    85  		// logger.Error("### istanbul/commit.go checkMessage","num",commit.View.Sequence,"err",err)
    86  		return err
    87  	}
    88  
    89  	if err := c.verifyCommit(commit, src); err != nil {
    90  		return err
    91  	}
    92  
    93  	if !c.valSet.CheckInSubList(msg.Hash, commit.View, src.Address()) {
    94  		logger.Warn("received an istanbul commit message from non-committee",
    95  			"currentSequence", c.current.sequence.Uint64(), "sender", src.Address().String(), "msgView", commit.View.String())
    96  		return errNotFromCommittee
    97  	}
    98  
    99  	c.acceptCommit(msg, src)
   100  
   101  	// Change to Prepared state if we've received enough PREPARE/COMMIT messages or it is locked
   102  	// and we are in earlier state before Prepared state.
   103  	// Both of PREPARE and COMMIT messages are counted since the nodes which is hashlocked in
   104  	// the previous round skip sending PREPARE messages.
   105  	if c.state.Cmp(StatePrepared) < 0 {
   106  		if c.current.IsHashLocked() && commit.Digest == c.current.GetLockedHash() {
   107  			logger.Warn("received commit of the hash locked proposal and change state to prepared", "msgType", msgCommit)
   108  			c.setState(StatePrepared)
   109  			c.sendCommit()
   110  		} else if c.current.GetPrepareOrCommitSize() >= requiredMessageCount(c.valSet) {
   111  			logger.Info("received a quorum of the messages and change state to prepared", "msgType", msgCommit, "valSet", c.valSet.Size())
   112  			c.current.LockHash()
   113  			c.setState(StatePrepared)
   114  			c.sendCommit()
   115  		}
   116  	}
   117  
   118  	// Commit the proposal once we have enough COMMIT messages and we are not in the Committed state.
   119  	//
   120  	// If we already have a proposal, we may have chance to speed up the consensus process
   121  	// by committing the proposal without PREPARE messages.
   122  	//logger.Error("### consensus check","len(commits)",c.current.Commits.Size(),"f(2/3)",2*c.valSet.F(),"state",c.state.Cmp(StateCommitted))
   123  	if c.state.Cmp(StateCommitted) < 0 && c.current.Commits.Size() >= requiredMessageCount(c.valSet) {
   124  		// Still need to call LockHash here since state can skip Prepared state and jump directly to the Committed state.
   125  		c.current.LockHash()
   126  		c.commit()
   127  	}
   128  
   129  	return nil
   130  }
   131  
   132  // verifyCommit verifies if the received COMMIT message is equivalent to our subject
   133  func (c *core) verifyCommit(commit *istanbul.Subject, src istanbul.Validator) error {
   134  	logger := c.logger.NewWith("from", src, "state", c.state)
   135  
   136  	sub := c.current.Subject()
   137  	if !reflect.DeepEqual(commit, sub) {
   138  		logger.Warn("Inconsistent subjects between commit and proposal", "expected", sub, "got", commit)
   139  		return errInconsistentSubject
   140  	}
   141  
   142  	return nil
   143  }
   144  
   145  func (c *core) acceptCommit(msg *message, src istanbul.Validator) error {
   146  	logger := c.logger.NewWith("from", src, "state", c.state)
   147  
   148  	// Add the COMMIT message to current round state
   149  	if err := c.current.Commits.Add(msg); err != nil {
   150  		logger.Error("Failed to record commit message", "msg", msg, "err", err)
   151  		return err
   152  	}
   153  
   154  	return nil
   155  }