github.com/klaytn/klaytn@v1.10.2/consensus/istanbul/backend/backend.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/backend/backend.go (2018/06/04).
    19  // Modified and improved for the klaytn development.
    20  
    21  package backend
    22  
    23  import (
    24  	"crypto/ecdsa"
    25  	"math/big"
    26  	"sync"
    27  	"sync/atomic"
    28  	"time"
    29  
    30  	lru "github.com/hashicorp/golang-lru"
    31  	"github.com/klaytn/klaytn/blockchain"
    32  	"github.com/klaytn/klaytn/blockchain/types"
    33  	"github.com/klaytn/klaytn/common"
    34  	"github.com/klaytn/klaytn/consensus"
    35  	"github.com/klaytn/klaytn/consensus/istanbul"
    36  	istanbulCore "github.com/klaytn/klaytn/consensus/istanbul/core"
    37  	"github.com/klaytn/klaytn/consensus/istanbul/validator"
    38  	"github.com/klaytn/klaytn/crypto"
    39  	"github.com/klaytn/klaytn/event"
    40  	"github.com/klaytn/klaytn/governance"
    41  	"github.com/klaytn/klaytn/log"
    42  	"github.com/klaytn/klaytn/reward"
    43  	"github.com/klaytn/klaytn/storage/database"
    44  )
    45  
    46  const (
    47  	// fetcherID is the ID indicates the block is from Istanbul engine
    48  	fetcherID = "istanbul"
    49  )
    50  
    51  var logger = log.NewModuleLogger(log.ConsensusIstanbulBackend)
    52  
    53  func New(rewardbase common.Address, config *istanbul.Config, privateKey *ecdsa.PrivateKey, db database.DBManager, governance governance.Engine, nodetype common.ConnType) consensus.Istanbul {
    54  	recents, _ := lru.NewARC(inmemorySnapshots)
    55  	recentMessages, _ := lru.NewARC(inmemoryPeers)
    56  	knownMessages, _ := lru.NewARC(inmemoryMessages)
    57  	backend := &backend{
    58  		config:            config,
    59  		istanbulEventMux:  new(event.TypeMux),
    60  		privateKey:        privateKey,
    61  		address:           crypto.PubkeyToAddress(privateKey.PublicKey),
    62  		logger:            logger.NewWith(),
    63  		db:                db,
    64  		commitCh:          make(chan *types.Result, 1),
    65  		recents:           recents,
    66  		candidates:        make(map[common.Address]bool),
    67  		coreStarted:       false,
    68  		recentMessages:    recentMessages,
    69  		knownMessages:     knownMessages,
    70  		rewardbase:        rewardbase,
    71  		governance:        governance,
    72  		nodetype:          nodetype,
    73  		rewardDistributor: reward.NewRewardDistributor(governance),
    74  	}
    75  	backend.currentView.Store(&istanbul.View{Sequence: big.NewInt(0), Round: big.NewInt(0)})
    76  	backend.core = istanbulCore.New(backend, backend.config)
    77  	return backend
    78  }
    79  
    80  // ----------------------------------------------------------------------------
    81  
    82  type backend struct {
    83  	config           *istanbul.Config
    84  	istanbulEventMux *event.TypeMux
    85  	privateKey       *ecdsa.PrivateKey
    86  	address          common.Address
    87  	core             istanbulCore.Engine
    88  	logger           log.Logger
    89  	db               database.DBManager
    90  	chain            consensus.ChainReader
    91  	currentBlock     func() *types.Block
    92  	hasBadBlock      func(hash common.Hash) bool
    93  
    94  	// the channels for istanbul engine notifications
    95  	commitCh          chan *types.Result
    96  	proposedBlockHash common.Hash
    97  	sealMu            sync.Mutex
    98  	coreStarted       bool
    99  	coreMu            sync.RWMutex
   100  
   101  	// Current list of candidates we are pushing
   102  	candidates map[common.Address]bool
   103  	// Protects the signer fields
   104  	candidatesLock sync.RWMutex
   105  	// Snapshots for recent block to speed up reorgs
   106  	recents *lru.ARCCache
   107  
   108  	// event subscription for ChainHeadEvent event
   109  	broadcaster consensus.Broadcaster
   110  
   111  	recentMessages *lru.ARCCache // the cache of peer's messages
   112  	knownMessages  *lru.ARCCache // the cache of self messages
   113  
   114  	rewardbase  common.Address
   115  	currentView atomic.Value //*istanbul.View
   116  
   117  	// Reference to the governance.Engine
   118  	governance governance.Engine
   119  	// Last Block Number which has current Governance Config
   120  	lastGovernanceBlock uint64
   121  
   122  	rewardDistributor *reward.RewardDistributor
   123  
   124  	// Node type
   125  	nodetype common.ConnType
   126  }
   127  
   128  func (sb *backend) NodeType() common.ConnType {
   129  	return sb.nodetype
   130  }
   131  
   132  func (sb *backend) GetRewardBase() common.Address {
   133  	return sb.rewardbase
   134  }
   135  
   136  func (sb *backend) SetCurrentView(view *istanbul.View) {
   137  	sb.currentView.Store(view)
   138  }
   139  
   140  // Address implements istanbul.Backend.Address
   141  func (sb *backend) Address() common.Address {
   142  	return sb.address
   143  }
   144  
   145  // Validators implements istanbul.Backend.Validators
   146  func (sb *backend) Validators(proposal istanbul.Proposal) istanbul.ValidatorSet {
   147  	return sb.getValidators(proposal.Number().Uint64(), proposal.Hash())
   148  }
   149  
   150  // Broadcast implements istanbul.Backend.Broadcast
   151  func (sb *backend) Broadcast(prevHash common.Hash, valSet istanbul.ValidatorSet, payload []byte) error {
   152  	// send to others
   153  	// TODO Check gossip again in event handle
   154  	// sb.Gossip(valSet, payload)
   155  	// send to self
   156  	msg := istanbul.MessageEvent{
   157  		Hash:    prevHash,
   158  		Payload: payload,
   159  	}
   160  	go sb.istanbulEventMux.Post(msg)
   161  	return nil
   162  }
   163  
   164  // Broadcast implements istanbul.Backend.Gossip
   165  func (sb *backend) Gossip(valSet istanbul.ValidatorSet, payload []byte) error {
   166  	hash := istanbul.RLPHash(payload)
   167  	sb.knownMessages.Add(hash, true)
   168  
   169  	if sb.broadcaster != nil {
   170  		ps := sb.broadcaster.GetCNPeers()
   171  		for addr, p := range ps {
   172  			ms, ok := sb.recentMessages.Get(addr)
   173  			var m *lru.ARCCache
   174  			if ok {
   175  				m, _ = ms.(*lru.ARCCache)
   176  				if _, k := m.Get(hash); k {
   177  					// This peer had this event, skip it
   178  					continue
   179  				}
   180  			} else {
   181  				m, _ = lru.NewARC(inmemoryMessages)
   182  			}
   183  
   184  			m.Add(hash, true)
   185  			sb.recentMessages.Add(addr, m)
   186  
   187  			cmsg := &istanbul.ConsensusMsg{
   188  				PrevHash: common.Hash{},
   189  				Payload:  payload,
   190  			}
   191  
   192  			// go p.Send(IstanbulMsg, payload)
   193  			go p.Send(IstanbulMsg, cmsg)
   194  		}
   195  	}
   196  	return nil
   197  }
   198  
   199  // checkInSubList checks if the node is in a sublist
   200  func (sb *backend) checkInSubList(prevHash common.Hash, valSet istanbul.ValidatorSet) bool {
   201  	return valSet.CheckInSubList(prevHash, sb.currentView.Load().(*istanbul.View), sb.Address())
   202  }
   203  
   204  // getTargetReceivers returns a map of nodes which need to receive a message
   205  func (sb *backend) getTargetReceivers(prevHash common.Hash, valSet istanbul.ValidatorSet) map[common.Address]bool {
   206  	targets := make(map[common.Address]bool)
   207  
   208  	cv, ok := sb.currentView.Load().(*istanbul.View)
   209  	if !ok {
   210  		logger.Error("Failed to assert type from sb.currentView!!", "cv", cv)
   211  		return nil
   212  	}
   213  	view := &istanbul.View{
   214  		Round:    big.NewInt(cv.Round.Int64()),
   215  		Sequence: big.NewInt(cv.Sequence.Int64()),
   216  	}
   217  
   218  	proposer := valSet.GetProposer()
   219  	for i := 0; i < 2; i++ {
   220  		committee := valSet.SubListWithProposer(prevHash, proposer.Address(), view)
   221  		for _, val := range committee {
   222  			if val.Address() != sb.Address() {
   223  				targets[val.Address()] = true
   224  			}
   225  		}
   226  		view.Round = view.Round.Add(view.Round, common.Big1)
   227  		proposer = valSet.Selector(valSet, common.Address{}, view.Round.Uint64())
   228  	}
   229  	return targets
   230  }
   231  
   232  // GossipSubPeer implements istanbul.Backend.Gossip
   233  func (sb *backend) GossipSubPeer(prevHash common.Hash, valSet istanbul.ValidatorSet, payload []byte) map[common.Address]bool {
   234  	if !sb.checkInSubList(prevHash, valSet) {
   235  		return nil
   236  	}
   237  
   238  	hash := istanbul.RLPHash(payload)
   239  	sb.knownMessages.Add(hash, true)
   240  
   241  	targets := sb.getTargetReceivers(prevHash, valSet)
   242  
   243  	if sb.broadcaster != nil && len(targets) > 0 {
   244  		ps := sb.broadcaster.FindCNPeers(targets)
   245  		for addr, p := range ps {
   246  			ms, ok := sb.recentMessages.Get(addr)
   247  			var m *lru.ARCCache
   248  			if ok {
   249  				m, _ = ms.(*lru.ARCCache)
   250  				if _, k := m.Get(hash); k {
   251  					// This peer had this event, skip it
   252  					continue
   253  				}
   254  			} else {
   255  				m, _ = lru.NewARC(inmemoryMessages)
   256  			}
   257  
   258  			m.Add(hash, true)
   259  			sb.recentMessages.Add(addr, m)
   260  
   261  			cmsg := &istanbul.ConsensusMsg{
   262  				PrevHash: prevHash,
   263  				Payload:  payload,
   264  			}
   265  
   266  			go p.Send(IstanbulMsg, cmsg)
   267  		}
   268  	}
   269  	return targets
   270  }
   271  
   272  // Commit implements istanbul.Backend.Commit
   273  func (sb *backend) Commit(proposal istanbul.Proposal, seals [][]byte) error {
   274  	// Check if the proposal is a valid block
   275  	block, ok := proposal.(*types.Block)
   276  	if !ok {
   277  		sb.logger.Error("Invalid proposal, %v", proposal)
   278  		return errInvalidProposal
   279  	}
   280  	h := block.Header()
   281  	round := sb.currentView.Load().(*istanbul.View).Round.Int64()
   282  	h = types.SetRoundToHeader(h, round)
   283  	// Append seals into extra-data
   284  	err := writeCommittedSeals(h, seals)
   285  	if err != nil {
   286  		return err
   287  	}
   288  	// update block's header
   289  	block = block.WithSeal(h)
   290  
   291  	sb.logger.Info("Committed", "number", proposal.Number().Uint64(), "hash", proposal.Hash(), "address", sb.Address())
   292  	// - if the proposed and committed blocks are the same, send the proposed hash
   293  	//   to commit channel, which is being watched inside the engine.Seal() function.
   294  	// - otherwise, we try to insert the block.
   295  	// -- if success, the ChainHeadEvent event will be broadcasted, try to build
   296  	//    the next block and the previous Seal() will be stopped.
   297  	// -- otherwise, a error will be returned and a round change event will be fired.
   298  	if sb.proposedBlockHash == block.Hash() {
   299  		// feed block hash to Seal() and wait the Seal() result
   300  		sb.commitCh <- &types.Result{Block: block, Round: round}
   301  		return nil
   302  	}
   303  
   304  	if sb.broadcaster != nil {
   305  		sb.broadcaster.Enqueue(fetcherID, block)
   306  	}
   307  	return nil
   308  }
   309  
   310  // EventMux implements istanbul.Backend.EventMux
   311  func (sb *backend) EventMux() *event.TypeMux {
   312  	return sb.istanbulEventMux
   313  }
   314  
   315  // Verify implements istanbul.Backend.Verify
   316  func (sb *backend) Verify(proposal istanbul.Proposal) (time.Duration, error) {
   317  	// Check if the proposal is a valid block
   318  	block, ok := proposal.(*types.Block)
   319  	if !ok {
   320  		sb.logger.Error("Invalid proposal, %v", proposal)
   321  		return 0, errInvalidProposal
   322  	}
   323  
   324  	// check bad block
   325  	if sb.HasBadProposal(block.Hash()) {
   326  		return 0, blockchain.ErrBlacklistedHash
   327  	}
   328  
   329  	// check block body
   330  	txnHash := types.DeriveSha(block.Transactions(), block.Number())
   331  	if txnHash != block.Header().TxHash {
   332  		return 0, errMismatchTxhashes
   333  	}
   334  
   335  	// verify the header of proposed block
   336  	err := sb.VerifyHeader(sb.chain, block.Header(), false)
   337  	// ignore errEmptyCommittedSeals error because we don't have the committed seals yet
   338  	if err == nil || err == errEmptyCommittedSeals {
   339  		return 0, nil
   340  	} else if err == consensus.ErrFutureBlock {
   341  		return time.Unix(block.Header().Time.Int64(), 0).Sub(now()), consensus.ErrFutureBlock
   342  	}
   343  	return 0, err
   344  }
   345  
   346  // Sign implements istanbul.Backend.Sign
   347  func (sb *backend) Sign(data []byte) ([]byte, error) {
   348  	hashData := crypto.Keccak256([]byte(data))
   349  	return crypto.Sign(hashData, sb.privateKey)
   350  }
   351  
   352  // CheckSignature implements istanbul.Backend.CheckSignature
   353  func (sb *backend) CheckSignature(data []byte, address common.Address, sig []byte) error {
   354  	signer, err := cacheSignatureAddresses(data, sig)
   355  	if err != nil {
   356  		logger.Error("Failed to get signer address", "err", err)
   357  		return err
   358  	}
   359  	// Compare derived addresses
   360  	if signer != address {
   361  		return errInvalidSignature
   362  	}
   363  	return nil
   364  }
   365  
   366  // HasPropsal implements istanbul.Backend.HashBlock
   367  func (sb *backend) HasPropsal(hash common.Hash, number *big.Int) bool {
   368  	return sb.chain.GetHeader(hash, number.Uint64()) != nil
   369  }
   370  
   371  // GetProposer implements istanbul.Backend.GetProposer
   372  func (sb *backend) GetProposer(number uint64) common.Address {
   373  	if h := sb.chain.GetHeaderByNumber(number); h != nil {
   374  		a, _ := sb.Author(h)
   375  		return a
   376  	}
   377  	return common.Address{}
   378  }
   379  
   380  // ParentValidators implements istanbul.Backend.GetParentValidators
   381  func (sb *backend) ParentValidators(proposal istanbul.Proposal) istanbul.ValidatorSet {
   382  	if block, ok := proposal.(*types.Block); ok {
   383  		return sb.getValidators(block.Number().Uint64()-1, block.ParentHash())
   384  	}
   385  
   386  	// TODO-Klaytn-Governance The following return case should not be called. Refactor it to error handling.
   387  	return validator.NewValidatorSet(nil, nil,
   388  		istanbul.ProposerPolicy(sb.chain.Config().Istanbul.ProposerPolicy),
   389  		sb.chain.Config().Istanbul.SubGroupSize,
   390  		sb.chain)
   391  }
   392  
   393  func (sb *backend) getValidators(number uint64, hash common.Hash) istanbul.ValidatorSet {
   394  	snap, err := sb.snapshot(sb.chain, number, hash, nil, false)
   395  	if err != nil {
   396  		logger.Error("Snapshot not found.", "err", err)
   397  		// TODO-Klaytn-Governance The following return case should not be called. Refactor it to error handling.
   398  		return validator.NewValidatorSet(nil, nil,
   399  			istanbul.ProposerPolicy(sb.chain.Config().Istanbul.ProposerPolicy),
   400  			sb.chain.Config().Istanbul.SubGroupSize,
   401  			sb.chain)
   402  	}
   403  	return snap.ValSet
   404  }
   405  
   406  func (sb *backend) LastProposal() (istanbul.Proposal, common.Address) {
   407  	block := sb.currentBlock()
   408  
   409  	var proposer common.Address
   410  	if block.Number().Cmp(common.Big0) > 0 {
   411  		var err error
   412  		proposer, err = sb.Author(block.Header())
   413  		if err != nil {
   414  			sb.logger.Error("Failed to get block proposer", "err", err)
   415  			return nil, common.Address{}
   416  		}
   417  	}
   418  
   419  	// Return header only block here since we don't need block body
   420  	return block, proposer
   421  }
   422  
   423  func (sb *backend) HasBadProposal(hash common.Hash) bool {
   424  	if sb.hasBadBlock == nil {
   425  		return false
   426  	}
   427  	return sb.hasBadBlock(hash)
   428  }