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