github.com/kisexp/xdchain@v0.0.0-20211206025815-490d6b732aa7/consensus/istanbul/ibft/core/core.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  	"bytes"
    21  	"math"
    22  	"math/big"
    23  	"sync"
    24  	"time"
    25  
    26  	"github.com/kisexp/xdchain/common"
    27  	"github.com/kisexp/xdchain/consensus/istanbul"
    28  	ibfttypes "github.com/kisexp/xdchain/consensus/istanbul/ibft/types"
    29  	"github.com/kisexp/xdchain/core/types"
    30  	"github.com/kisexp/xdchain/event"
    31  	"github.com/kisexp/xdchain/log"
    32  	metrics "github.com/kisexp/xdchain/metrics"
    33  	"gopkg.in/karalabe/cookiejar.v2/collections/prque"
    34  )
    35  
    36  var (
    37  	roundMeter     = metrics.NewRegisteredMeter("consensus/istanbul/core/round", nil)
    38  	sequenceMeter  = metrics.NewRegisteredMeter("consensus/istanbul/core/sequence", nil)
    39  	consensusTimer = metrics.NewRegisteredTimer("consensus/istanbul/core/consensus", nil)
    40  )
    41  
    42  // New creates an Istanbul consensus core
    43  func New(backend istanbul.Backend, config *istanbul.Config) *core {
    44  	c := &core{
    45  		config:             config,
    46  		address:            backend.Address(),
    47  		state:              ibfttypes.StateAcceptRequest,
    48  		handlerWg:          new(sync.WaitGroup),
    49  		logger:             log.New("address", backend.Address()),
    50  		backend:            backend,
    51  		backlogs:           make(map[common.Address]*prque.Prque),
    52  		backlogsMu:         new(sync.Mutex),
    53  		pendingRequests:    prque.New(),
    54  		pendingRequestsMu:  new(sync.Mutex),
    55  		consensusTimestamp: time.Time{},
    56  	}
    57  
    58  	c.validateFn = c.checkValidatorSignature
    59  	return c
    60  }
    61  
    62  // ----------------------------------------------------------------------------
    63  
    64  type core struct {
    65  	config  *istanbul.Config
    66  	address common.Address
    67  	state   ibfttypes.State
    68  	logger  log.Logger
    69  
    70  	backend               istanbul.Backend
    71  	events                *event.TypeMuxSubscription
    72  	finalCommittedSub     *event.TypeMuxSubscription
    73  	timeoutSub            *event.TypeMuxSubscription
    74  	futurePreprepareTimer *time.Timer
    75  
    76  	valSet                istanbul.ValidatorSet
    77  	waitingForRoundChange bool
    78  	validateFn            func([]byte, []byte) (common.Address, error)
    79  
    80  	backlogs   map[common.Address]*prque.Prque
    81  	backlogsMu *sync.Mutex
    82  
    83  	current   *roundState
    84  	handlerWg *sync.WaitGroup
    85  
    86  	roundChangeSet   *roundChangeSet
    87  	roundChangeTimer *time.Timer
    88  
    89  	pendingRequests   *prque.Prque
    90  	pendingRequestsMu *sync.Mutex
    91  
    92  	consensusTimestamp time.Time
    93  }
    94  
    95  func (c *core) finalizeMessage(msg *ibfttypes.Message) ([]byte, error) {
    96  	var err error
    97  	// Add sender address
    98  	msg.Address = c.Address()
    99  
   100  	// Assign the CommittedSeal if it's a COMMIT message and proposal is not nil
   101  	if msg.Code == ibfttypes.MsgCommit && c.current.Proposal() != nil {
   102  		msg.CommittedSeal = []byte{}
   103  		seal := PrepareCommittedSeal(c.current.Proposal().Hash())
   104  		// Add proof of consensus
   105  		msg.CommittedSeal, err = c.backend.Sign(seal)
   106  		if err != nil {
   107  			return nil, err
   108  		}
   109  	}
   110  
   111  	// Sign message
   112  	data, err := msg.PayloadNoSig()
   113  	if err != nil {
   114  		return nil, err
   115  	}
   116  	msg.Signature, err = c.backend.Sign(data)
   117  	if err != nil {
   118  		return nil, err
   119  	}
   120  
   121  	// Convert to payload
   122  	payload, err := msg.Payload()
   123  	if err != nil {
   124  		return nil, err
   125  	}
   126  
   127  	return payload, nil
   128  }
   129  
   130  func (c *core) broadcast(msg *ibfttypes.Message) {
   131  	logger := c.logger.New("state", c.state)
   132  
   133  	payload, err := c.finalizeMessage(msg)
   134  	if err != nil {
   135  		logger.Error("Failed to finalize message", "msg", msg, "err", err)
   136  		return
   137  	}
   138  
   139  	// Broadcast payload
   140  	if err = c.backend.Broadcast(c.valSet, msg.Code, payload); err != nil {
   141  		logger.Error("Failed to broadcast message", "msg", msg, "err", err)
   142  		return
   143  	}
   144  }
   145  
   146  func (c *core) currentView() *istanbul.View {
   147  	return &istanbul.View{
   148  		Sequence: new(big.Int).Set(c.current.Sequence()),
   149  		Round:    new(big.Int).Set(c.current.Round()),
   150  	}
   151  }
   152  
   153  func (c *core) IsProposer() bool {
   154  	v := c.valSet
   155  	if v == nil {
   156  		return false
   157  	}
   158  	return v.IsProposer(c.backend.Address())
   159  }
   160  
   161  func (c *core) IsCurrentProposal(blockHash common.Hash) bool {
   162  	return c.current != nil && c.current.pendingRequest != nil && c.current.pendingRequest.Proposal.Hash() == blockHash
   163  }
   164  
   165  func (c *core) commit() {
   166  	c.setState(ibfttypes.StateCommitted)
   167  
   168  	proposal := c.current.Proposal()
   169  	if proposal != nil {
   170  		committedSeals := make([][]byte, c.current.Commits.Size())
   171  		for i, v := range c.current.Commits.Values() {
   172  			committedSeals[i] = make([]byte, types.IstanbulExtraSeal)
   173  			copy(committedSeals[i][:], v.CommittedSeal[:])
   174  		}
   175  
   176  		if err := c.backend.Commit(proposal, committedSeals, big.NewInt(-1)); err != nil {
   177  			c.current.UnlockHash() //Unlock block when insertion fails
   178  			c.sendNextRoundChange()
   179  			return
   180  		}
   181  	}
   182  }
   183  
   184  // startNewRound starts a new round. if round equals to 0, it means to starts a new sequence
   185  func (c *core) startNewRound(round *big.Int) {
   186  	var logger log.Logger
   187  	if c.current == nil {
   188  		logger = c.logger.New("old_round", -1, "old_seq", 0)
   189  	} else {
   190  		logger = c.logger.New("old_round", c.current.Round(), "old_seq", c.current.Sequence())
   191  	}
   192  
   193  	logger.Trace("Start new ibft round")
   194  
   195  	roundChange := false
   196  	// Try to get last proposal
   197  	lastProposal, lastProposer := c.backend.LastProposal()
   198  	if c.current == nil {
   199  		logger.Trace("Start to the initial round")
   200  	} else if lastProposal.Number().Cmp(c.current.Sequence()) >= 0 {
   201  		diff := new(big.Int).Sub(lastProposal.Number(), c.current.Sequence())
   202  		sequenceMeter.Mark(new(big.Int).Add(diff, common.Big1).Int64())
   203  
   204  		if !c.consensusTimestamp.IsZero() {
   205  			consensusTimer.UpdateSince(c.consensusTimestamp)
   206  			c.consensusTimestamp = time.Time{}
   207  		}
   208  		logger.Trace("Catch up latest proposal", "number", lastProposal.Number().Uint64(), "hash", lastProposal.Hash())
   209  	} else if lastProposal.Number().Cmp(big.NewInt(c.current.Sequence().Int64()-1)) == 0 {
   210  		if round.Cmp(common.Big0) == 0 {
   211  			// same seq and round, don't need to start new round
   212  			return
   213  		} else if round.Cmp(c.current.Round()) < 0 {
   214  			logger.Warn("New round should not be smaller than current round", "seq", lastProposal.Number().Int64(), "new_round", round, "old_round", c.current.Round())
   215  			return
   216  		}
   217  		roundChange = true
   218  	} else {
   219  		logger.Warn("New sequence should be larger than current sequence", "new_seq", lastProposal.Number().Int64())
   220  		return
   221  	}
   222  
   223  	var newView *istanbul.View
   224  	if roundChange {
   225  		newView = &istanbul.View{
   226  			Sequence: new(big.Int).Set(c.current.Sequence()),
   227  			Round:    new(big.Int).Set(round),
   228  		}
   229  	} else {
   230  		newView = &istanbul.View{
   231  			Sequence: new(big.Int).Add(lastProposal.Number(), common.Big1),
   232  			Round:    new(big.Int),
   233  		}
   234  		c.valSet = c.backend.Validators(lastProposal)
   235  	}
   236  
   237  	// If new round is 0, then check if qbftConsensus needs to be enabled
   238  	if round.Uint64() == 0 && c.backend.IsQBFTConsensusAt(newView.Sequence) {
   239  		logger.Trace("Starting qbft consensus as qbftBlock has passed")
   240  		if err := c.backend.StartQBFTConsensus(); err != nil {
   241  			// If err is returned, then QBFT consensus is started for the next block
   242  			logger.Error("Unable to start QBFT Consensus, retrying for the next block", "error", err)
   243  		}
   244  		return
   245  	}
   246  
   247  	// Update logger
   248  	logger = logger.New("old_proposer", c.valSet.GetProposer())
   249  	// Clear invalid ROUND CHANGE messages
   250  	c.roundChangeSet = newRoundChangeSet(c.valSet)
   251  	// New snapshot for new round
   252  	c.updateRoundState(newView, c.valSet, roundChange)
   253  	// Calculate new proposer
   254  	c.valSet.CalcProposer(lastProposer, newView.Round.Uint64())
   255  	c.waitingForRoundChange = false
   256  	c.setState(ibfttypes.StateAcceptRequest)
   257  	if roundChange && c.IsProposer() && c.current != nil {
   258  		// If it is locked, propose the old proposal
   259  		// If we have pending request, propose pending request
   260  		if c.current.IsHashLocked() {
   261  			r := &istanbul.Request{
   262  				Proposal: c.current.Proposal(), //c.current.Proposal would be the locked proposal by previous proposer, see updateRoundState
   263  			}
   264  			c.sendPreprepare(r)
   265  		} else if c.current.pendingRequest != nil {
   266  			c.sendPreprepare(c.current.pendingRequest)
   267  		}
   268  	}
   269  	c.newRoundChangeTimer()
   270  
   271  	logger.Debug("New round", "new_round", newView.Round, "new_seq", newView.Sequence, "new_proposer", c.valSet.GetProposer(), "valSet", c.valSet.List(), "size", c.valSet.Size(), "IsProposer", c.IsProposer())
   272  }
   273  
   274  func (c *core) catchUpRound(view *istanbul.View) {
   275  	logger := c.logger.New("old_round", c.current.Round(), "old_seq", c.current.Sequence(), "old_proposer", c.valSet.GetProposer())
   276  
   277  	if view.Round.Cmp(c.current.Round()) > 0 {
   278  		roundMeter.Mark(new(big.Int).Sub(view.Round, c.current.Round()).Int64())
   279  	}
   280  	c.waitingForRoundChange = true
   281  
   282  	// Need to keep block locked for round catching up
   283  	c.updateRoundState(view, c.valSet, true)
   284  	c.roundChangeSet.Clear(view.Round)
   285  	c.newRoundChangeTimer()
   286  
   287  	logger.Trace("Catch up round", "new_round", view.Round, "new_seq", view.Sequence, "new_proposer", c.valSet)
   288  }
   289  
   290  // updateRoundState updates round state by checking if locking block is necessary
   291  func (c *core) updateRoundState(view *istanbul.View, validatorSet istanbul.ValidatorSet, roundChange bool) {
   292  	// Lock only if both roundChange is true and it is locked
   293  	if roundChange && c.current != nil {
   294  		if c.current.IsHashLocked() {
   295  			c.current = newRoundState(view, validatorSet, c.current.GetLockedHash(), c.current.Preprepare, c.current.pendingRequest, c.backend.HasBadProposal)
   296  		} else {
   297  			c.current = newRoundState(view, validatorSet, common.Hash{}, nil, c.current.pendingRequest, c.backend.HasBadProposal)
   298  		}
   299  	} else {
   300  		c.current = newRoundState(view, validatorSet, common.Hash{}, nil, nil, c.backend.HasBadProposal)
   301  	}
   302  }
   303  
   304  func (c *core) setState(state ibfttypes.State) {
   305  	if c.state != state {
   306  		c.state = state
   307  	}
   308  	if state == ibfttypes.StateAcceptRequest {
   309  		c.processPendingRequests()
   310  	}
   311  	c.processBacklog()
   312  }
   313  
   314  func (c *core) Address() common.Address {
   315  	return c.address
   316  }
   317  
   318  func (c *core) stopFuturePreprepareTimer() {
   319  	if c.futurePreprepareTimer != nil {
   320  		c.futurePreprepareTimer.Stop()
   321  	}
   322  }
   323  
   324  func (c *core) stopTimer() {
   325  	c.stopFuturePreprepareTimer()
   326  	if c.roundChangeTimer != nil {
   327  		c.roundChangeTimer.Stop()
   328  	}
   329  }
   330  
   331  func (c *core) newRoundChangeTimer() {
   332  	c.stopTimer()
   333  
   334  	// set timeout based on the round number
   335  	timeout := time.Duration(c.config.RequestTimeout) * time.Millisecond
   336  	round := c.current.Round().Uint64()
   337  	if round > 0 {
   338  		timeout += time.Duration(math.Pow(2, float64(round))) * time.Second
   339  	}
   340  	c.roundChangeTimer = time.AfterFunc(timeout, func() {
   341  		c.sendEvent(timeoutEvent{})
   342  	})
   343  }
   344  
   345  func (c *core) checkValidatorSignature(data []byte, sig []byte) (common.Address, error) {
   346  	return istanbul.CheckValidatorSignature(c.valSet, data, sig)
   347  }
   348  
   349  func (c *core) QuorumSize() int {
   350  	if c.config.Ceil2Nby3Block == nil || (c.current != nil && c.current.sequence.Cmp(c.config.Ceil2Nby3Block) < 0) {
   351  		c.logger.Trace("Confirmation Formula used 2F+ 1")
   352  		return (2 * c.valSet.F()) + 1
   353  	}
   354  	c.logger.Trace("Confirmation Formula used ceil(2N/3)")
   355  	return int(math.Ceil(float64(2*c.valSet.Size()) / 3))
   356  }
   357  
   358  // PrepareCommittedSeal returns a committed seal for the given hash
   359  func PrepareCommittedSeal(hash common.Hash) []byte {
   360  	var buf bytes.Buffer
   361  	buf.Write(hash.Bytes())
   362  	buf.Write([]byte{byte(ibfttypes.MsgCommit)})
   363  	return buf.Bytes()
   364  }