github.com/klaytn/klaytn@v1.12.1/consensus/istanbul/core/handler.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/handler.go (2018/06/04).
    19  // Modified and improved for the klaytn development.
    20  
    21  package core
    22  
    23  import (
    24  	"github.com/klaytn/klaytn/common"
    25  	"github.com/klaytn/klaytn/consensus/istanbul"
    26  )
    27  
    28  // Start implements core.Engine.Start
    29  func (c *core) Start() error {
    30  	// Start a new round from last sequence + 1
    31  	c.startNewRound(common.Big0)
    32  
    33  	// Tests will handle events itself, so we have to make subscribeEvents()
    34  	// be able to call in test.
    35  	c.subscribeEvents()
    36  	go c.handleEvents()
    37  
    38  	return nil
    39  }
    40  
    41  // Stop implements core.Engine.Stop
    42  func (c *core) Stop() error {
    43  	c.stopTimer()
    44  	c.unsubscribeEvents()
    45  
    46  	// Make sure the handler goroutine exits
    47  	c.handlerWg.Wait()
    48  	return nil
    49  }
    50  
    51  // ----------------------------------------------------------------------------
    52  
    53  // Subscribe both internal and external events
    54  func (c *core) subscribeEvents() {
    55  	c.events = c.backend.EventMux().Subscribe(
    56  		// external events
    57  		istanbul.RequestEvent{},
    58  		istanbul.MessageEvent{},
    59  		// internal events
    60  		backlogEvent{},
    61  	)
    62  	c.timeoutSub = c.backend.EventMux().Subscribe(
    63  		timeoutEvent{},
    64  	)
    65  	c.finalCommittedSub = c.backend.EventMux().Subscribe(
    66  		istanbul.FinalCommittedEvent{},
    67  	)
    68  }
    69  
    70  // Unsubscribe all events
    71  func (c *core) unsubscribeEvents() {
    72  	c.events.Unsubscribe()
    73  	c.timeoutSub.Unsubscribe()
    74  	c.finalCommittedSub.Unsubscribe()
    75  }
    76  
    77  func (c *core) handleEvents() {
    78  	// Clear state
    79  	defer func() {
    80  		c.current = nil
    81  		c.handlerWg.Done()
    82  	}()
    83  
    84  	c.handlerWg.Add(1)
    85  
    86  	for {
    87  		select {
    88  		case event, ok := <-c.events.Chan():
    89  			if !ok {
    90  				return
    91  			}
    92  			// A real event arrived, process interesting content
    93  			switch ev := event.Data.(type) {
    94  			case istanbul.RequestEvent:
    95  				r := &istanbul.Request{
    96  					Proposal: ev.Proposal,
    97  				}
    98  				err := c.handleRequest(r)
    99  				if err == errFutureMessage {
   100  					c.storeRequestMsg(r)
   101  				}
   102  			case istanbul.MessageEvent:
   103  				if err := c.handleMsg(ev.Payload); err == nil {
   104  					c.backend.GossipSubPeer(ev.Hash, c.valSet, ev.Payload)
   105  					// c.backend.Gossip(c.valSet, ev.Payload)
   106  				}
   107  			case backlogEvent:
   108  				_, src := c.valSet.GetByAddress(ev.src)
   109  				if src == nil {
   110  					c.logger.Error("Invalid address in valSet", "addr", ev.src)
   111  					continue
   112  				}
   113  				// No need to check signature for internal messages
   114  				if err := c.handleCheckedMsg(ev.msg, src); err == nil {
   115  					p, err := ev.msg.Payload()
   116  					if err != nil {
   117  						c.logger.Warn("Get message payload failed", "err", err)
   118  						continue
   119  					}
   120  					c.backend.GossipSubPeer(ev.Hash, c.valSet, p)
   121  					// c.backend.Gossip(c.valSet, p)
   122  				}
   123  			}
   124  		case ev, ok := <-c.timeoutSub.Chan():
   125  			if !ok || ev.Data == nil {
   126  				logger.Error("Drop an empty message from timeout channel")
   127  				return
   128  			}
   129  			data, ok := ev.Data.(timeoutEvent)
   130  			if !ok || data.nextView == nil {
   131  				logger.Error("Invalid message from timeout channel", "msg", ev.Data)
   132  				return
   133  			}
   134  			c.handleTimeoutMsg(data.nextView)
   135  		case event, ok := <-c.finalCommittedSub.Chan():
   136  			if !ok {
   137  				return
   138  			}
   139  			switch event.Data.(type) {
   140  			case istanbul.FinalCommittedEvent:
   141  				c.handleFinalCommitted()
   142  			}
   143  		}
   144  	}
   145  }
   146  
   147  // sendEvent sends events to mux
   148  func (c *core) sendEvent(ev interface{}) {
   149  	c.backend.EventMux().Post(ev)
   150  }
   151  
   152  func (c *core) handleMsg(payload []byte) error {
   153  	logger := c.logger.NewWith()
   154  
   155  	// Decode message and check its signature
   156  	msg := new(message)
   157  	if err := msg.FromPayload(payload, c.validateFn); err != nil {
   158  		if c.backend.NodeType() == common.CONSENSUSNODE {
   159  			if err != istanbul.ErrUnauthorizedAddress {
   160  				logger.Error("Failed to decode message from payload", "err", err)
   161  				return err
   162  			}
   163  
   164  			msgView, msgDecodeErr := msg.GetView()
   165  			if msgDecodeErr != nil {
   166  				logger.Error("Failed to decode message while getting view information", "code", msg.Code, "err", msgDecodeErr)
   167  				return err
   168  			}
   169  
   170  			// Print view and address to help you analyze the node is valid or not.
   171  			// This information will help you to analyze whether the msg sender is valid or not.
   172  			// Furthermore, if the node is still syncing, there is a high probability that msg sender is a valid validator.
   173  			logger.Warn("Received Consensus msg is signed by an unauthorized address. It could happen when the node is unsynced temporarily.", "senderAddress", msg.Address, "nodeView", c.currentView().String(), "msgView", msgView.String())
   174  		}
   175  		return err
   176  	}
   177  
   178  	// Only accept message if the address is valid
   179  	_, src := c.valSet.GetByAddress(msg.Address)
   180  	if src == nil {
   181  		logger.Error("Invalid address in message", "msg", msg)
   182  		return istanbul.ErrUnauthorizedAddress
   183  	}
   184  
   185  	return c.handleCheckedMsg(msg, src)
   186  }
   187  
   188  func (c *core) handleCheckedMsg(msg *message, src istanbul.Validator) error {
   189  	logger := c.logger.NewWith("address", c.address, "from", src)
   190  
   191  	// Store the message if it's a future message
   192  	testBacklog := func(err error) error {
   193  		if err == errFutureMessage {
   194  			c.storeBacklog(msg, src)
   195  		}
   196  
   197  		return err
   198  	}
   199  
   200  	switch msg.Code {
   201  	case msgPreprepare:
   202  		return testBacklog(c.handlePreprepare(msg, src))
   203  	case msgPrepare:
   204  		return testBacklog(c.handlePrepare(msg, src))
   205  	case msgCommit:
   206  		return testBacklog(c.handleCommit(msg, src))
   207  	case msgRoundChange:
   208  		return testBacklog(c.handleRoundChange(msg, src))
   209  	default:
   210  		logger.Error("Invalid message type", "msg", msg)
   211  	}
   212  
   213  	return errInvalidMessage
   214  }
   215  
   216  func (c *core) handleTimeoutMsg(nextView *istanbul.View) {
   217  	// TODO-Klaytn-Istanbul: EN/PN should not handle consensus msgs.
   218  	if c.backend.NodeType() != common.CONSENSUSNODE {
   219  		logger.Trace("PN/EN doesn't need to handle timeout messages",
   220  			"nodeType", c.backend.NodeType().String())
   221  		return
   222  	}
   223  
   224  	lastProposal, _ := c.backend.LastProposal()
   225  	if lastProposal == nil {
   226  		logger.Error("Received timeout message but can't find the last proposal", "msgView", nextView.String())
   227  		return
   228  	}
   229  
   230  	if lastProposal.Number().Cmp(nextView.Sequence) >= 0 {
   231  		logger.Debug("This timeoutMsg is outdated",
   232  			"blockNumber", lastProposal.Number().Uint64(), "msgView", nextView.String())
   233  		return
   234  	}
   235  
   236  	// If we're not waiting for round change yet, we can try to catch up
   237  	// the max round with F+1 round change message. We only need to catch up
   238  	// if the max round is larger than current round.
   239  	if !c.waitingForRoundChange {
   240  		maxRound := c.roundChangeSet.MaxRound(c.valSet.F() + 1)
   241  		if maxRound != nil && maxRound.Cmp(c.current.Round()) > 0 {
   242  			logger.Warn("[RC] Send round change because of timeout event")
   243  			c.sendRoundChange(maxRound)
   244  			return
   245  		}
   246  	}
   247  
   248  	if lastProposal.Number().Cmp(c.current.Sequence()) >= 0 {
   249  		c.logger.Trace("round change timeout, catch up latest sequence", "number", lastProposal.Number().Uint64())
   250  		c.startNewRound(common.Big0)
   251  	} else {
   252  		c.sendRoundChange(nextView.Round)
   253  	}
   254  }