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