github.com/electroneum/electroneum-sc@v0.0.0-20230105223411-3bc1d078281e/consensus/istanbul/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  	"math"
    21  	"math/big"
    22  	"sync"
    23  	"time"
    24  
    25  	"github.com/electroneum/electroneum-sc/common"
    26  	"github.com/electroneum/electroneum-sc/consensus/istanbul"
    27  	qbfttypes "github.com/electroneum/electroneum-sc/consensus/istanbul/types"
    28  	"github.com/electroneum/electroneum-sc/core/types"
    29  	"github.com/electroneum/electroneum-sc/event"
    30  	"github.com/electroneum/electroneum-sc/log"
    31  	metrics "github.com/electroneum/electroneum-sc/metrics"
    32  	"gopkg.in/karalabe/cookiejar.v2/collections/prque"
    33  )
    34  
    35  var (
    36  	roundMeter     = metrics.NewRegisteredMeter("consensus/istanbul/qbft/core/round", nil)
    37  	sequenceMeter  = metrics.NewRegisteredMeter("consensus/istanbul/qbft/core/sequence", nil)
    38  	consensusTimer = metrics.NewRegisteredTimer("consensus/istanbul/qbft/core/consensus", nil)
    39  )
    40  
    41  // New creates an Istanbul consensus core
    42  func New(backend istanbul.Backend, config *istanbul.Config) istanbul.Core {
    43  	c := &core{
    44  		config:             config,
    45  		address:            backend.Address(),
    46  		state:              StateAcceptRequest,
    47  		handlerWg:          new(sync.WaitGroup),
    48  		logger:             log.New("address", backend.Address()),
    49  		backend:            backend,
    50  		backlogs:           make(map[common.Address]*prque.Prque),
    51  		backlogsMu:         new(sync.Mutex),
    52  		pendingRequests:    prque.New(),
    53  		pendingRequestsMu:  new(sync.Mutex),
    54  		consensusTimestamp: time.Time{},
    55  	}
    56  
    57  	c.validateFn = c.checkValidatorSignature
    58  	return c
    59  }
    60  
    61  // ----------------------------------------------------------------------------
    62  
    63  type core struct {
    64  	config  *istanbul.Config
    65  	address common.Address
    66  	state   State
    67  	logger  log.Logger
    68  
    69  	backend               istanbul.Backend
    70  	events                *event.TypeMuxSubscription
    71  	finalCommittedSub     *event.TypeMuxSubscription
    72  	timeoutSub            *event.TypeMuxSubscription
    73  	futurePreprepareTimer *time.Timer
    74  
    75  	valSet     istanbul.ValidatorSet
    76  	validateFn func([]byte, []byte) (common.Address, error)
    77  
    78  	backlogs   map[common.Address]*prque.Prque
    79  	backlogsMu *sync.Mutex
    80  
    81  	current   *roundState
    82  	handlerWg *sync.WaitGroup
    83  
    84  	roundChangeSet   *roundChangeSet
    85  	roundChangeTimer *time.Timer
    86  
    87  	QBFTPreparedPrepares []*qbfttypes.Prepare
    88  
    89  	pendingRequests   *prque.Prque
    90  	pendingRequestsMu *sync.Mutex
    91  
    92  	consensusTimestamp time.Time
    93  }
    94  
    95  func (c *core) currentView() *istanbul.View {
    96  	return &istanbul.View{
    97  		Sequence: new(big.Int).Set(c.current.Sequence()),
    98  		Round:    new(big.Int).Set(c.current.Round()),
    99  	}
   100  }
   101  
   102  func (c *core) IsProposer() bool {
   103  	v := c.valSet
   104  	if v == nil {
   105  		return false
   106  	}
   107  	return v.IsProposer(c.backend.Address())
   108  }
   109  
   110  func (c *core) IsCurrentProposal(blockHash common.Hash) bool {
   111  	return c.current != nil && c.current.pendingRequest != nil && c.current.pendingRequest.Proposal.Hash() == blockHash
   112  }
   113  
   114  // startNewRound starts a new round. if round equals to 0, it means to starts a new sequence
   115  func (c *core) startNewRound(round *big.Int) {
   116  	var logger log.Logger
   117  	if c.current == nil {
   118  		logger = c.logger.New("old.round", -1, "old.seq", 0)
   119  	} else {
   120  		logger = c.currentLogger(false, nil)
   121  	}
   122  	logger = logger.New("target.round", round)
   123  
   124  	roundChange := false
   125  
   126  	// Try to get last proposal
   127  	lastProposal, lastProposer := c.backend.LastProposal()
   128  	if lastProposal != nil {
   129  		logger = logger.New("lastProposal.number", lastProposal.Number().Uint64(), "lastProposal.hash", lastProposal.Hash())
   130  	}
   131  
   132  	logger.Info("IBFT: initialize new round")
   133  
   134  	if c.current == nil {
   135  		logger.Debug("IBFT: start at the initial round")
   136  	} else if lastProposal.Number().Cmp(c.current.Sequence()) >= 0 {
   137  		diff := new(big.Int).Sub(lastProposal.Number(), c.current.Sequence())
   138  		sequenceMeter.Mark(new(big.Int).Add(diff, common.Big1).Int64())
   139  
   140  		if !c.consensusTimestamp.IsZero() {
   141  			consensusTimer.UpdateSince(c.consensusTimestamp)
   142  			c.consensusTimestamp = time.Time{}
   143  		}
   144  		logger.Debug("IBFT: catch up last block proposal")
   145  	} else if lastProposal.Number().Cmp(big.NewInt(c.current.Sequence().Int64()-1)) == 0 {
   146  		if round.Cmp(common.Big0) == 0 {
   147  			// same seq and round, don't need to start new round
   148  			logger.Debug("IBFT: same round, no need to start new round")
   149  			return
   150  		} else if round.Cmp(c.current.Round()) < 0 {
   151  			logger.Warn("IBFT: next round is inferior to current round")
   152  			return
   153  		}
   154  		roundChange = true
   155  	} else {
   156  		logger.Warn("IBFT: next sequence is before last block proposal")
   157  		return
   158  	}
   159  
   160  	var oldLogger log.Logger
   161  	if c.current == nil {
   162  		oldLogger = c.logger.New("old.round", -1, "old.seq", 0)
   163  	} else {
   164  		oldLogger = c.logger.New("old.round", c.current.Round().Uint64(), "old.sequence", c.current.Sequence().Uint64(), "old.state", c.state.String(), "old.proposer", c.valSet.GetProposer())
   165  	}
   166  
   167  	// Create next view
   168  	var newView *istanbul.View
   169  	if roundChange {
   170  		newView = &istanbul.View{
   171  			Sequence: new(big.Int).Set(c.current.Sequence()),
   172  			Round:    new(big.Int).Set(round),
   173  		}
   174  	} else {
   175  		newView = &istanbul.View{
   176  			Sequence: new(big.Int).Add(lastProposal.Number(), common.Big1),
   177  			Round:    new(big.Int),
   178  		}
   179  		c.valSet = c.backend.Validators(lastProposal)
   180  	}
   181  
   182  	// New snapshot for new round
   183  	c.updateRoundState(newView, c.valSet, roundChange)
   184  
   185  	// Calculate new proposer
   186  	c.valSet.CalcProposer(lastProposer, newView.Round.Uint64())
   187  	c.setState(StateAcceptRequest)
   188  
   189  	if round.Cmp(c.current.Round()) > 0 {
   190  		roundMeter.Mark(new(big.Int).Sub(round, c.current.Round()).Int64())
   191  	}
   192  
   193  	// Update RoundChangeSet by deleting older round messages
   194  	if round.Uint64() == 0 {
   195  		c.QBFTPreparedPrepares = nil
   196  		c.roundChangeSet = newRoundChangeSet(c.valSet)
   197  	} else {
   198  		// Clear earlier round messages
   199  		c.roundChangeSet.ClearLowerThan(round)
   200  	}
   201  	c.roundChangeSet.NewRound(round)
   202  
   203  	if round.Uint64() > 0 {
   204  		c.newRoundChangeTimer()
   205  	}
   206  
   207  	oldLogger.Info("IBFT: start new round", "next.round", newView.Round, "next.seq", newView.Sequence, "next.proposer", c.valSet.GetProposer(), "next.valSet", c.valSet.List(), "next.size", c.valSet.Size(), "next.IsProposer", c.IsProposer())
   208  }
   209  
   210  // updateRoundState updates round state by checking if locking block is necessary
   211  func (c *core) updateRoundState(view *istanbul.View, validatorSet istanbul.ValidatorSet, roundChange bool) {
   212  	if roundChange && c.current != nil {
   213  		c.current = newRoundState(view, validatorSet, c.current.Preprepare, c.current.preparedRound, c.current.preparedBlock, c.current.pendingRequest, c.backend.HasBadProposal)
   214  	} else {
   215  		c.current = newRoundState(view, validatorSet, nil, nil, nil, nil, c.backend.HasBadProposal)
   216  	}
   217  }
   218  
   219  func (c *core) setState(state State) {
   220  	if c.state != state {
   221  		oldState := c.state
   222  		c.state = state
   223  		c.currentLogger(false, nil).Info("IBFT: changed state", "old.state", oldState.String(), "new.state", state.String())
   224  	}
   225  	if state == StateAcceptRequest {
   226  		c.processPendingRequests()
   227  	}
   228  
   229  	// each time we change state, we process backlog for possible message that are
   230  	// now ready
   231  	c.processBacklog()
   232  }
   233  
   234  func (c *core) Address() common.Address {
   235  	return c.address
   236  }
   237  
   238  func (c *core) stopFuturePreprepareTimer() {
   239  	if c.futurePreprepareTimer != nil {
   240  		c.futurePreprepareTimer.Stop()
   241  	}
   242  }
   243  
   244  func (c *core) stopTimer() {
   245  	c.stopFuturePreprepareTimer()
   246  	if c.roundChangeTimer != nil {
   247  		c.roundChangeTimer.Stop()
   248  	}
   249  }
   250  
   251  func (c *core) newRoundChangeTimer() {
   252  	c.stopTimer()
   253  
   254  	// set timeout based on the round number
   255  	baseTimeout := time.Duration(c.config.RequestTimeout) * time.Millisecond
   256  	round := c.current.Round().Uint64()
   257  
   258  	timeout := baseTimeout * time.Duration(math.Pow(2, float64(round)))
   259  
   260  	c.currentLogger(true, nil).Trace("IBFT: start new ROUND-CHANGE timer", "timeout", timeout.Seconds())
   261  	c.roundChangeTimer = time.AfterFunc(timeout, func() {
   262  		c.sendEvent(timeoutEvent{})
   263  	})
   264  }
   265  
   266  func (c *core) checkValidatorSignature(data []byte, sig []byte) (common.Address, error) {
   267  	return istanbul.CheckValidatorSignature(c.valSet, data, sig)
   268  }
   269  
   270  func (c *core) QuorumSize() int {
   271  	return int(math.Ceil(float64(2*c.valSet.Size()) / 3))
   272  }
   273  
   274  // PrepareCommittedSeal returns a committed seal for the given header and takes current round under consideration
   275  func PrepareCommittedSeal(header *types.Header, round uint32) []byte {
   276  	h := types.CopyHeader(header)
   277  	return h.QBFTHashWithRoundNumber(round).Bytes()
   278  }