github.com/codingfuture/orig-energi3@v0.8.4/energi/service/masternode.go (about)

     1  // Copyright 2019 The Energi Core Authors
     2  // This file is part of the Energi Core library.
     3  //
     4  // The Energi Core 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 Energi Core 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 Energi Core library. If not, see <http://www.gnu.org/licenses/>.
    16  
    17  package service
    18  
    19  import (
    20  	"errors"
    21  	"math/big"
    22  	"sync/atomic"
    23  	"time"
    24  
    25  	"github.com/ethereum/go-ethereum/accounts/abi/bind"
    26  	"github.com/ethereum/go-ethereum/common"
    27  	"github.com/ethereum/go-ethereum/core"
    28  	"github.com/ethereum/go-ethereum/core/types"
    29  	"github.com/ethereum/go-ethereum/crypto"
    30  	"github.com/ethereum/go-ethereum/eth"
    31  	"github.com/ethereum/go-ethereum/eth/downloader"
    32  	"github.com/ethereum/go-ethereum/log"
    33  	"github.com/ethereum/go-ethereum/node"
    34  	"github.com/ethereum/go-ethereum/p2p"
    35  	"github.com/ethereum/go-ethereum/rpc"
    36  
    37  	energi_abi "energi.world/core/gen3/energi/abi"
    38  	energi_common "energi.world/core/gen3/energi/common"
    39  	energi_params "energi.world/core/gen3/energi/params"
    40  )
    41  
    42  var (
    43  	heartbeatInterval = time.Duration(30) * time.Minute
    44  	recheckInterval   = time.Minute
    45  )
    46  
    47  const (
    48  	masternodeCallGas uint64 = 500000
    49  
    50  	// cpChanBufferSize defines the number of checkpoint to be pushed into the
    51  	// checkpoints channel before it can be considered to be full.
    52  	cpChanBufferSize  = 16
    53  	chainHeadChanSize = 10
    54  )
    55  
    56  type checkpointVote struct {
    57  	address   common.Address
    58  	signature []byte
    59  }
    60  
    61  type MasternodeService struct {
    62  	server *p2p.Server
    63  	eth    *eth.Ethereum
    64  
    65  	inSync int32
    66  
    67  	address  common.Address
    68  	owner    common.Address
    69  	registry *energi_abi.IMasternodeRegistryV2Session
    70  
    71  	cpRegistry  *energi_abi.ICheckpointRegistrySession
    72  	lastCPBlock uint64
    73  	cpVoteChan  chan *checkpointVote
    74  
    75  	nextHB   time.Time
    76  	features *big.Int
    77  
    78  	validator *peerValidator
    79  }
    80  
    81  func NewMasternodeService(ethServ *eth.Ethereum, owner common.Address) (node.Service, error) {
    82  	r := &MasternodeService{
    83  		eth:      ethServ,
    84  		inSync:   1,
    85  		features: energi_common.SWVersionToInt(),
    86  		owner:    owner,
    87  		// NOTE: we need to avoid triggering DoS on restart.
    88  		// There is no reliable way to check blockchain and all pools in the network.
    89  		nextHB: time.Now().Add(recheckInterval),
    90  
    91  		cpVoteChan: make(chan *checkpointVote, cpChanBufferSize),
    92  	}
    93  	go r.listenDownloader()
    94  	return r, nil
    95  }
    96  
    97  func (m *MasternodeService) Protocols() []p2p.Protocol {
    98  	return nil
    99  }
   100  
   101  func (m *MasternodeService) APIs() []rpc.API {
   102  	return nil
   103  }
   104  
   105  func (m *MasternodeService) Start(server *p2p.Server) error {
   106  	address := crypto.PubkeyToAddress(server.PrivateKey.PublicKey)
   107  	m.address = address
   108  
   109  	//---
   110  	m.eth.TxPool().RemoveBySender(address)
   111  
   112  	//---
   113  	contract, err := energi_abi.NewIMasternodeRegistryV2(
   114  		energi_params.Energi_MasternodeRegistry, m.eth.APIBackend)
   115  	if err != nil {
   116  		return err
   117  	}
   118  
   119  	m.registry = &energi_abi.IMasternodeRegistryV2Session{
   120  		Contract: contract,
   121  		CallOpts: bind.CallOpts{
   122  			From: address,
   123  		},
   124  		TransactOpts: bind.TransactOpts{
   125  			From: address,
   126  			Signer: func(
   127  				signer types.Signer,
   128  				addr common.Address,
   129  				tx *types.Transaction,
   130  			) (*types.Transaction, error) {
   131  				if addr != address {
   132  					log.Error("Invalid Masternode address!", "addr", addr)
   133  					return nil, errors.New("Invalid MN address")
   134  				}
   135  
   136  				return types.SignTx(tx, signer, server.PrivateKey)
   137  			},
   138  			Value:    common.Big0,
   139  			GasLimit: masternodeCallGas,
   140  			GasPrice: common.Big0,
   141  		},
   142  	}
   143  
   144  	//---
   145  
   146  	cpContract, err := energi_abi.NewICheckpointRegistry(
   147  		energi_params.Energi_CheckpointRegistry, m.eth.APIBackend)
   148  	if err != nil {
   149  		return err
   150  	}
   151  
   152  	m.cpRegistry = &energi_abi.ICheckpointRegistrySession{
   153  		Contract:     cpContract,
   154  		CallOpts:     m.registry.CallOpts,
   155  		TransactOpts: m.registry.TransactOpts,
   156  	}
   157  
   158  	m.server = server
   159  	m.validator = newPeerValidator(common.Address{}, m)
   160  	go m.loop()
   161  
   162  	log.Info("Started Energi Masternode", "addr", address)
   163  	return nil
   164  }
   165  
   166  func (m *MasternodeService) Stop() error {
   167  	log.Info("Shutting down Energi Masternode", "addr", m.address)
   168  	m.validator.cancel()
   169  	return nil
   170  }
   171  
   172  func (m *MasternodeService) listenDownloader() {
   173  	events := m.eth.EventMux().Subscribe(
   174  		downloader.StartEvent{},
   175  		downloader.DoneEvent{},
   176  		downloader.FailedEvent{},
   177  	)
   178  	defer events.Unsubscribe()
   179  
   180  	for {
   181  		select {
   182  		case ev := <-events.Chan():
   183  			if ev == nil {
   184  				return
   185  			}
   186  			switch ev.Data.(type) {
   187  			case downloader.StartEvent:
   188  				atomic.StoreInt32(&m.inSync, 0)
   189  				log.Debug("Masternode is not in sync")
   190  			case downloader.DoneEvent, downloader.FailedEvent:
   191  				atomic.StoreInt32(&m.inSync, 1)
   192  				log.Debug("Masternode is in sync")
   193  				return
   194  			}
   195  		}
   196  	}
   197  }
   198  
   199  func (m *MasternodeService) isActive() bool {
   200  	if atomic.LoadInt32(&m.inSync) == 0 {
   201  		return false
   202  	}
   203  
   204  	if m.owner != (common.Address{}) {
   205  		mninfo, err := m.registry.Info(m.address)
   206  		if err != nil {
   207  			log.Error("Masternode info fetch Err: %v", err)
   208  			return false
   209  		}
   210  
   211  		if mninfo.Owner != m.owner {
   212  			log.Error("Masternode owner mismatch", " needed=", m.owner, " got=", mninfo.Owner)
   213  			return false
   214  		}
   215  	}
   216  
   217  	res, err := m.registry.IsActive(m.address)
   218  
   219  	if err != nil {
   220  		log.Error("Masternode check failed", "err", err)
   221  		return false
   222  	}
   223  
   224  	return res
   225  }
   226  
   227  func (m *MasternodeService) loop() {
   228  	bc := m.eth.BlockChain()
   229  
   230  	chainHeadCh := make(chan core.ChainHeadEvent, chainHeadChanSize)
   231  	headSub := bc.SubscribeChainHeadEvent(chainHeadCh)
   232  	defer headSub.Unsubscribe()
   233  
   234  	events := m.eth.EventMux().Subscribe(
   235  		CheckpointProposalEvent{},
   236  	)
   237  	defer events.Unsubscribe()
   238  
   239  	//---
   240  	for {
   241  		select {
   242  		case ev := <-events.Chan():
   243  			if ev == nil {
   244  				return
   245  			}
   246  			switch ev.Data.(type) {
   247  			case CheckpointProposalEvent:
   248  				m.onCheckpoint(ev.Data.(CheckpointProposalEvent))
   249  			}
   250  			break
   251  
   252  		case ev := <-chainHeadCh:
   253  			m.onChainHead(ev.Block)
   254  			break
   255  
   256  		// Shutdown
   257  		case <-headSub.Err():
   258  			return
   259  		}
   260  	}
   261  }
   262  
   263  func (m *MasternodeService) onCheckpoint(cpe CheckpointProposalEvent) {
   264  	cpAddr := cpe.Proposal
   265  
   266  	cp, err := energi_abi.NewICheckpointV2Caller(cpAddr, m.eth.APIBackend)
   267  	if err != nil {
   268  		log.Error("Failed to create the checkpoint iface", "cp", cpAddr, "err", err)
   269  		return
   270  	}
   271  
   272  	callOpts := &m.cpRegistry.CallOpts
   273  
   274  	// Check if the current masternode has voted and vote on it if not yet.
   275  	can_vote, err := cp.CanVote(callOpts, m.address)
   276  	if err != nil {
   277  		log.Warn("Failed at Checkpoint.canVote()", "cp", cpAddr, "err", err)
   278  		return
   279  	}
   280  
   281  	if !can_vote {
   282  		return
   283  	}
   284  
   285  	if h := m.eth.BlockChain().GetHeaderByNumber(cpe.Number); h == nil {
   286  		log.Warn("Checkpoint Proposal is ahead of this MN blockchain",
   287  			"number", cpe.Number, "cp", cpe.Hash)
   288  		return
   289  	} else if h.Hash() != cpe.Hash {
   290  		log.Warn("Checkpoint Proposal is not aligned with this MN blockchain",
   291  			"number", cpe.Number, "header", h.Hash(), "cp", cpe.Hash)
   292  		return
   293  	}
   294  
   295  	log.Info("MN checkpoint signature not found, now generating a new one", "cp", cpAddr)
   296  
   297  	baseHash, err := cp.SignatureBase(callOpts)
   298  	if err != nil {
   299  		log.Error("Failed to get base hash", "cp", cpAddr, "err", err)
   300  		return
   301  	}
   302  
   303  	signature, err := crypto.Sign(baseHash[:], m.server.PrivateKey)
   304  	if err != nil {
   305  		log.Error("Failed to sign base hash", "cp", cpAddr, "err", err)
   306  		return
   307  	}
   308  
   309  	signature[64] += 27
   310  
   311  	m.cpVoteChan <- &checkpointVote{
   312  		address:   cpAddr,
   313  		signature: signature,
   314  	}
   315  }
   316  
   317  // voteOnCheckpoints recieves the identified checkpoints vote information and
   318  // attempts to vote them in.
   319  func (m *MasternodeService) voteOnCheckpoints() {
   320  	var cpVote *checkpointVote
   321  
   322  	for {
   323  		select {
   324  		case cpVote = <-m.cpVoteChan:
   325  			// Only vote on the latest due to zero fee triggers
   326  			break
   327  
   328  		default:
   329  			if cpVote != nil {
   330  				tx, err := m.cpRegistry.Sign(cpVote.address, cpVote.signature)
   331  				if tx != nil {
   332  					txhash := tx.Hash()
   333  					log.Warn("Voting on checkpoint", "addr", cpVote.address, "tx", txhash.Hex())
   334  				}
   335  
   336  				if err != nil {
   337  					log.Error("Checkpoint vote failed", "checkpoint", cpVote.address, "err", err)
   338  				}
   339  			}
   340  
   341  			return
   342  		}
   343  	}
   344  }
   345  
   346  func (m *MasternodeService) onChainHead(block *types.Block) {
   347  	if !m.isActive() {
   348  		do_cleanup := m.validator.target != common.Address{}
   349  		m.validator.cancel()
   350  
   351  		if do_cleanup {
   352  			m.eth.TxPool().RemoveBySender(m.address)
   353  		}
   354  		return
   355  	}
   356  
   357  	// MN-4 - Heartbeats
   358  	now := time.Now()
   359  
   360  	if now.After(m.nextHB) {
   361  		// It is more important than invalidation duty.
   362  		// Some chance of race is still left, but at acceptable probability.
   363  		m.validator.cancel()
   364  
   365  		// Only present in IMasternodeRegistryV2
   366  		if ok, err := m.registry.CanHeartbeat(m.address); err == nil && !ok {
   367  			return
   368  		}
   369  
   370  		// Ensure heartbeat on clean queue
   371  		if !m.eth.TxPool().RemoveBySender(m.address) {
   372  			current := m.eth.BlockChain().CurrentHeader()
   373  			tx, err := m.registry.Heartbeat(current.Number, current.Hash(), m.features)
   374  
   375  			if err == nil {
   376  				log.Info("Masternode Heartbeat", "tx", tx.Hash())
   377  				m.nextHB = now.Add(heartbeatInterval)
   378  			} else {
   379  				log.Error("Failed to send Masternode Heartbeat", "err", err)
   380  				m.nextHB = now.Add(recheckInterval)
   381  			}
   382  		} else {
   383  			// NOTE: we need to recover from Nonce mismatch to enable heartbeats
   384  			//       as soon as possible.
   385  			log.Warn("Delaying Masternode Heartbeat due to pending zero-fee tx")
   386  		}
   387  
   388  		return
   389  	}
   390  
   391  	// Vote on the identified checkpoints.
   392  	m.voteOnCheckpoints()
   393  
   394  	//
   395  	target, err := m.registry.ValidationTarget(m.address)
   396  	if err != nil {
   397  		log.Warn("MNTarget error", "mn", m.address, "err", err)
   398  		m.validator.cancel()
   399  		return
   400  	}
   401  
   402  	// MN-14: validation duty
   403  	if old_target := m.validator.target; old_target != target {
   404  		m.validator.cancel()
   405  		m.validator = newPeerValidator(target, m)
   406  
   407  		// Only present in IMasternodeRegistryV2
   408  		if ok, err := m.registry.CanInvalidate(m.address); err == nil && !ok {
   409  			return
   410  		}
   411  
   412  		// Skip the first validation cycle to prevent possible DoS trigger on restart
   413  		if (old_target != common.Address{}) {
   414  			go m.validator.validate()
   415  		}
   416  	}
   417  }
   418  
   419  type peerValidator struct {
   420  	target   common.Address
   421  	mnsvc    *MasternodeService
   422  	cancelCh chan struct{}
   423  }
   424  
   425  func newPeerValidator(
   426  	target common.Address,
   427  	mnsvc *MasternodeService,
   428  ) *peerValidator {
   429  	return &peerValidator{
   430  		target:   target,
   431  		mnsvc:    mnsvc,
   432  		cancelCh: make(chan struct{}),
   433  	}
   434  }
   435  
   436  func (v *peerValidator) cancel() {
   437  	if v.mnsvc != nil {
   438  		close(v.cancelCh)
   439  		v.mnsvc = nil
   440  	}
   441  }
   442  
   443  func (v *peerValidator) validate() {
   444  	log.Debug("Masternode validation started", "target", v.target.Hex())
   445  	defer log.Debug("Masternode validation stopped", "target", v.target.Hex())
   446  
   447  	mnsvc := v.mnsvc
   448  	if mnsvc == nil {
   449  		return
   450  	}
   451  	server := mnsvc.server
   452  
   453  	//---
   454  	mninfo, err := mnsvc.registry.Info(v.target)
   455  	if err != nil {
   456  		log.Warn("MNInfo error", "mn", v.target, "err", err)
   457  		return
   458  	}
   459  
   460  	cfg := mnsvc.eth.BlockChain().Config()
   461  	enode := energi_common.MastenodeEnode(mninfo.Ipv4address, mninfo.Enode, cfg)
   462  
   463  	if enode == nil {
   464  		log.Debug("Invalid ipv4address or public key was used")
   465  		return
   466  	}
   467  
   468  	// Check if the node is already connected as a peer and
   469  	// skip MN Validation if true.
   470  	if isFound := server.IsPeerActive(enode); isFound {
   471  		log.Debug("Masternode validation skipped since peer is already connected",
   472  			"target", v.target.Hex())
   473  		return
   474  	}
   475  
   476  	peerCh := make(chan *p2p.PeerEvent)
   477  	defer close(peerCh)
   478  
   479  	peerSub := server.SubscribeEvents(peerCh)
   480  
   481  	// EnableMsg Events.
   482  	server.EnableMsgEvents = true
   483  	defer peerSub.Unsubscribe()
   484  
   485  	server.AddPeer(enode)
   486  
   487  	defer func() {
   488  		// Disconnect this peer if more than half of the max peers are connected.
   489  		if server.PeerCount() > server.MaxPeers/2 {
   490  			server.RemovePeer(enode)
   491  		}
   492  	}()
   493  
   494  	//---
   495  	deadline := time.Now().Add(time.Minute)
   496  
   497  	for {
   498  		select {
   499  		case <-v.cancelCh:
   500  			return
   501  		case pe := <-peerCh:
   502  			if pe.Peer != enode.ID() || pe.Type != p2p.PeerEventTypeMsgRecv {
   503  				break
   504  			}
   505  			// TODO: validate block availability as per MN-14
   506  			return
   507  		case <-time.After(deadline.Sub(time.Now())):
   508  			log.Info("MN Invalidation", "mn", v.target)
   509  			_, err := mnsvc.registry.Invalidate(v.target)
   510  			if err != nil {
   511  				log.Warn("MN Invalidate error", "mn", v.target, "err", err)
   512  			}
   513  			return
   514  		}
   515  	}
   516  }