github.com/electroneum/electroneum-sc@v0.0.0-20230105223411-3bc1d078281e/consensus/istanbul/backend/backend.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 backend
    18  
    19  import (
    20  	"crypto/ecdsa"
    21  	"math/big"
    22  	"sync"
    23  	"time"
    24  
    25  	"github.com/electroneum/electroneum-sc/common"
    26  	"github.com/electroneum/electroneum-sc/consensus"
    27  	"github.com/electroneum/electroneum-sc/consensus/istanbul"
    28  	istanbulcommon "github.com/electroneum/electroneum-sc/consensus/istanbul/common"
    29  	qbftcore "github.com/electroneum/electroneum-sc/consensus/istanbul/core"
    30  	qbftengine "github.com/electroneum/electroneum-sc/consensus/istanbul/engine"
    31  	qbfttypes "github.com/electroneum/electroneum-sc/consensus/istanbul/types"
    32  	"github.com/electroneum/electroneum-sc/consensus/istanbul/validator"
    33  	"github.com/electroneum/electroneum-sc/core"
    34  	"github.com/electroneum/electroneum-sc/core/types"
    35  	"github.com/electroneum/electroneum-sc/crypto"
    36  	"github.com/electroneum/electroneum-sc/ethdb"
    37  	"github.com/electroneum/electroneum-sc/event"
    38  	"github.com/electroneum/electroneum-sc/log"
    39  	lru "github.com/hashicorp/golang-lru"
    40  )
    41  
    42  const (
    43  	// fetcherID is the ID indicates the block is from Istanbul engine
    44  	fetcherID = "istanbul"
    45  )
    46  
    47  // New creates an Ethereum backend for Istanbul core engine.
    48  func New(config istanbul.Config, privateKey *ecdsa.PrivateKey, db ethdb.Database) *Backend {
    49  	// Allocate the snapshot caches and create the engine
    50  	recents, _ := lru.NewARC(inmemorySnapshots)
    51  	recentsEmission, _ := lru.NewARC(inmemoryEmissions)
    52  	recentMessages, _ := lru.NewARC(inmemoryPeers)
    53  	knownMessages, _ := lru.NewARC(inmemoryMessages)
    54  
    55  	sb := &Backend{
    56  		config:           &config,
    57  		istanbulEventMux: new(event.TypeMux),
    58  		privateKey:       privateKey,
    59  		address:          crypto.PubkeyToAddress(privateKey.PublicKey),
    60  		logger:           log.New(),
    61  		db:               db,
    62  		commitCh:         make(chan *types.Block, 1),
    63  		recents:          recents,
    64  		recentsEmission:  recentsEmission,
    65  		candidates:       make(map[common.Address]bool),
    66  		coreStarted:      false,
    67  		recentMessages:   recentMessages,
    68  		knownMessages:    knownMessages,
    69  	}
    70  
    71  	sb.qbftEngine = qbftengine.NewEngine(sb.config, sb.address, sb.Sign)
    72  
    73  	return sb
    74  }
    75  
    76  // ----------------------------------------------------------------------------
    77  
    78  type Backend struct {
    79  	config *istanbul.Config
    80  
    81  	privateKey *ecdsa.PrivateKey
    82  	address    common.Address
    83  
    84  	core istanbul.Core
    85  
    86  	qbftEngine *qbftengine.Engine
    87  
    88  	istanbulEventMux *event.TypeMux
    89  
    90  	logger log.Logger
    91  
    92  	db ethdb.Database
    93  
    94  	chain        consensus.ChainHeaderReader
    95  	currentBlock func() *types.Block
    96  	hasBadBlock  func(db ethdb.Reader, hash common.Hash) bool
    97  
    98  	// the channels for istanbul engine notifications
    99  	commitCh          chan *types.Block
   100  	proposedBlockHash common.Hash
   101  	sealMu            sync.Mutex
   102  	coreStarted       bool
   103  	coreMu            sync.RWMutex
   104  
   105  	// Current list of candidates we are pushing
   106  	candidates map[common.Address]bool
   107  	// Protects the signer fields
   108  	candidatesLock sync.RWMutex
   109  	// Snapshots for recent block to speed up reorgs
   110  	recents *lru.ARCCache
   111  	// Emission for recent blocks
   112  	recentsEmission *lru.ARCCache
   113  
   114  	// event subscription for ChainHeadEvent event
   115  	broadcaster consensus.Broadcaster
   116  
   117  	recentMessages *lru.ARCCache // the cache of peer's messages
   118  	knownMessages  *lru.ARCCache // the cache of self messages
   119  }
   120  
   121  func (sb *Backend) Engine() istanbul.Engine {
   122  	return sb.EngineForBlockNumber(nil)
   123  }
   124  
   125  func (sb *Backend) EngineForBlockNumber(blockNumber *big.Int) istanbul.Engine {
   126  	return sb.qbftEngine
   127  }
   128  
   129  // zekun: HACK
   130  func (sb *Backend) CalcDifficulty(chain consensus.ChainHeaderReader, time uint64, parent *types.Header) *big.Int {
   131  	return sb.EngineForBlockNumber(parent.Number).CalcDifficulty(chain, time, parent)
   132  }
   133  
   134  // Address implements istanbul.Backend.Address
   135  func (sb *Backend) Address() common.Address {
   136  	return sb.Engine().Address()
   137  }
   138  
   139  // Validators implements istanbul.Backend.Validators
   140  func (sb *Backend) Validators(proposal istanbul.Proposal) istanbul.ValidatorSet {
   141  	return sb.getValidators(proposal.Number().Uint64(), proposal.Hash())
   142  }
   143  
   144  // Broadcast implements istanbul.Backend.Broadcast
   145  func (sb *Backend) Broadcast(valSet istanbul.ValidatorSet, code uint64, payload []byte) error {
   146  	// send to others
   147  	sb.Gossip(valSet, code, payload)
   148  	// send to self
   149  	msg := istanbul.MessageEvent{
   150  		Code:    code,
   151  		Payload: payload,
   152  	}
   153  	go sb.istanbulEventMux.Post(msg)
   154  	return nil
   155  }
   156  
   157  // Gossip implements istanbul.Backend.Gossip
   158  func (sb *Backend) Gossip(valSet istanbul.ValidatorSet, code uint64, payload []byte) error {
   159  	hash := istanbul.RLPHash(payload)
   160  	sb.knownMessages.Add(hash, true)
   161  
   162  	targets := make(map[common.Address]bool)
   163  	for _, val := range valSet.List() {
   164  		if val.Address() != sb.Address() {
   165  			targets[val.Address()] = true
   166  		}
   167  	}
   168  	if sb.broadcaster != nil && len(targets) > 0 {
   169  		ps := sb.broadcaster.FindPeers(targets)
   170  		for addr, p := range ps {
   171  			ms, ok := sb.recentMessages.Get(addr)
   172  			var m *lru.ARCCache
   173  			if ok {
   174  				m, _ = ms.(*lru.ARCCache)
   175  				if _, k := m.Get(hash); k {
   176  					// This peer had this event, skip it
   177  					continue
   178  				}
   179  			} else {
   180  				m, _ = lru.NewARC(inmemoryMessages)
   181  			}
   182  
   183  			m.Add(hash, true)
   184  			sb.recentMessages.Add(addr, m)
   185  
   186  			var outboundCode uint64 = istanbulMsg
   187  			if _, ok := qbfttypes.MessageCodes()[code]; ok {
   188  				outboundCode = code
   189  			}
   190  			go p.SendQBFTConsensus(outboundCode, payload)
   191  		}
   192  	}
   193  	return nil
   194  }
   195  
   196  // Commit implements istanbul.Backend.Commit
   197  func (sb *Backend) Commit(proposal istanbul.Proposal, seals [][]byte, round *big.Int) (err error) {
   198  	// Check if the proposal is a valid block
   199  	block, ok := proposal.(*types.Block)
   200  	if !ok {
   201  		sb.logger.Error("IBFT: invalid block proposal", "proposal", proposal)
   202  		return istanbulcommon.ErrInvalidProposal
   203  	}
   204  
   205  	// Commit header
   206  	h := block.Header()
   207  	err = sb.EngineForBlockNumber(h.Number).CommitHeader(h, seals, round)
   208  	if err != nil {
   209  		return
   210  	}
   211  
   212  	// Remove ValidatorSet added to ProposerPolicy registry, if not done, the registry keeps increasing size with each block height
   213  	sb.config.ProposerPolicy.ClearRegistry()
   214  
   215  	// update block's header
   216  	block = block.WithSeal(h)
   217  
   218  	sb.logger.Info("IBFT: block proposal committed", "author", sb.Address(), "hash", proposal.Hash(), "number", proposal.Number().Uint64())
   219  
   220  	// - if the proposed and committed blocks are the same, send the proposed hash
   221  	//   to commit channel, which is being watched inside the engine.Seal() function.
   222  	// - otherwise, we try to insert the block.
   223  	// -- if success, the ChainHeadEvent event will be broadcasted, try to build
   224  	//    the next block and the previous Seal() will be stopped.
   225  	// -- otherwise, a error will be returned and a round change event will be fired.
   226  	if sb.proposedBlockHash == block.Hash() {
   227  		// feed block hash to Seal() and wait the Seal() result
   228  		sb.commitCh <- block
   229  		return nil
   230  	}
   231  
   232  	if sb.broadcaster != nil {
   233  		sb.broadcaster.Enqueue(fetcherID, block)
   234  	}
   235  
   236  	return nil
   237  }
   238  
   239  // EventMux implements istanbul.Backend.EventMux
   240  func (sb *Backend) EventMux() *event.TypeMux {
   241  	return sb.istanbulEventMux
   242  }
   243  
   244  // Verify implements istanbul.Backend.Verify
   245  func (sb *Backend) Verify(proposal istanbul.Proposal) (time.Duration, error) {
   246  	// Check if the proposal is a valid block
   247  	block, ok := proposal.(*types.Block)
   248  	if !ok {
   249  		sb.logger.Error("IBFT: invalid block proposal", "proposal", proposal)
   250  		return 0, istanbulcommon.ErrInvalidProposal
   251  	}
   252  
   253  	// check bad block
   254  	if sb.HasBadProposal(block.Hash()) {
   255  		sb.logger.Warn("IBFT: bad block proposal", "proposal", proposal)
   256  		return 0, core.ErrBannedHash
   257  	}
   258  
   259  	header := block.Header()
   260  	snap, err := sb.snapshot(sb.chain, header.Number.Uint64()-1, header.ParentHash, nil)
   261  	if err != nil {
   262  		return 0, err
   263  	}
   264  
   265  	return sb.EngineForBlockNumber(header.Number).VerifyBlockProposal(sb.chain, block, snap.ValSet)
   266  }
   267  
   268  // Sign implements istanbul.Backend.Sign
   269  func (sb *Backend) Sign(data []byte) ([]byte, error) {
   270  	hashData := crypto.Keccak256(data)
   271  	return crypto.Sign(hashData, sb.privateKey)
   272  }
   273  
   274  // SignWithoutHashing implements istanbul.Backend.SignWithoutHashing and signs input data with the backend's private key without hashing the input data
   275  func (sb *Backend) SignWithoutHashing(data []byte) ([]byte, error) {
   276  	return crypto.Sign(data, sb.privateKey)
   277  }
   278  
   279  // CheckSignature implements istanbul.Backend.CheckSignature
   280  func (sb *Backend) CheckSignature(data []byte, address common.Address, sig []byte) error {
   281  	signer, err := istanbul.GetSignatureAddress(data, sig)
   282  	if err != nil {
   283  		return err
   284  	}
   285  	// Compare derived addresses
   286  	if signer != address {
   287  		return istanbulcommon.ErrInvalidSignature
   288  	}
   289  
   290  	return nil
   291  }
   292  
   293  // HasPropsal implements istanbul.Backend.HashBlock
   294  func (sb *Backend) HasPropsal(hash common.Hash, number *big.Int) bool {
   295  	return sb.chain.GetHeader(hash, number.Uint64()) != nil
   296  }
   297  
   298  // GetProposer implements istanbul.Backend.GetProposer
   299  func (sb *Backend) GetProposer(number uint64) common.Address {
   300  	if h := sb.chain.GetHeaderByNumber(number); h != nil {
   301  		a, _ := sb.Author(h)
   302  		return a
   303  	}
   304  	return common.Address{}
   305  }
   306  
   307  // ParentValidators implements istanbul.Backend.GetParentValidators
   308  func (sb *Backend) ParentValidators(proposal istanbul.Proposal) istanbul.ValidatorSet {
   309  	if block, ok := proposal.(*types.Block); ok {
   310  		return sb.getValidators(block.Number().Uint64()-1, block.ParentHash())
   311  	}
   312  	return validator.NewSet(nil, sb.config.ProposerPolicy)
   313  }
   314  
   315  func (sb *Backend) getValidators(number uint64, hash common.Hash) istanbul.ValidatorSet {
   316  	snap, err := sb.snapshot(sb.chain, number, hash, nil)
   317  	if err != nil {
   318  		return validator.NewSet(nil, sb.config.ProposerPolicy)
   319  	}
   320  	return snap.ValSet
   321  }
   322  
   323  func (sb *Backend) LastProposal() (istanbul.Proposal, common.Address) {
   324  	block := sb.currentBlock()
   325  
   326  	var proposer common.Address
   327  	if block.Number().Cmp(common.Big0) > 0 {
   328  		var err error
   329  		proposer, err = sb.Author(block.Header())
   330  		if err != nil {
   331  			sb.logger.Error("IBFT: last block proposal invalid", "err", err)
   332  			return nil, common.Address{}
   333  		}
   334  	}
   335  
   336  	// Return header only block here since we don't need block body
   337  	return block, proposer
   338  }
   339  
   340  func (sb *Backend) HasBadProposal(hash common.Hash) bool {
   341  	if sb.hasBadBlock == nil {
   342  		return false
   343  	}
   344  	return sb.hasBadBlock(sb.db, hash)
   345  }
   346  
   347  func (sb *Backend) Close() error {
   348  	return nil
   349  }
   350  
   351  func (sb *Backend) startQBFT() error {
   352  	sb.logger.Info("IBFT: activate QBFT")
   353  	sb.logger.Trace("IBFT: set ProposerPolicy sorter to ValidatorSortByByteFunc")
   354  	sb.config.ProposerPolicy.Use(istanbul.ValidatorSortByByte())
   355  
   356  	sb.core = qbftcore.New(sb, sb.config)
   357  	if err := sb.core.Start(); err != nil {
   358  		sb.logger.Error("IBFT: failed to activate QBFT", "err", err)
   359  		return err
   360  	}
   361  
   362  	return nil
   363  }
   364  
   365  func (sb *Backend) stop() error {
   366  	core := sb.core
   367  	sb.core = nil
   368  
   369  	if core != nil {
   370  		sb.logger.Info("IBFT: deactivate")
   371  		if err := core.Stop(); err != nil {
   372  			sb.logger.Error("IBFT: failed to deactivate", "err", err)
   373  			return err
   374  		}
   375  	}
   376  
   377  	return nil
   378  }
   379  
   380  // StartQBFTConsensus stops existing legacy ibft consensus and starts the new qbft consensus
   381  func (sb *Backend) StartQBFTConsensus() error {
   382  	sb.logger.Info("IBFT: switch from IBFT to QBFT")
   383  	if err := sb.stop(); err != nil {
   384  		return err
   385  	}
   386  
   387  	return sb.startQBFT()
   388  }