github.com/dim4egster/coreth@v0.10.2/plugin/evm/gossiper.go (about)

     1  // (c) 2019-2021, Ava Labs, Inc. All rights reserved.
     2  // See the file LICENSE for licensing terms.
     3  
     4  package evm
     5  
     6  import (
     7  	"container/heap"
     8  	"math/big"
     9  	"sync"
    10  	"time"
    11  
    12  	"github.com/dim4egster/qmallgo/codec"
    13  
    14  	"github.com/dim4egster/coreth/peer"
    15  
    16  	"github.com/dim4egster/qmallgo/cache"
    17  	"github.com/dim4egster/qmallgo/ids"
    18  	"github.com/dim4egster/qmallgo/snow"
    19  	"github.com/dim4egster/qmallgo/utils/wrappers"
    20  
    21  	"github.com/ethereum/go-ethereum/common"
    22  	"github.com/ethereum/go-ethereum/log"
    23  	"github.com/ethereum/go-ethereum/rlp"
    24  
    25  	"github.com/dim4egster/coreth/core"
    26  	"github.com/dim4egster/coreth/core/state"
    27  	"github.com/dim4egster/coreth/core/types"
    28  	"github.com/dim4egster/coreth/plugin/evm/message"
    29  )
    30  
    31  const (
    32  	// We allow [recentCacheSize] to be fairly large because we only store hashes
    33  	// in the cache, not entire transactions.
    34  	recentCacheSize = 512
    35  
    36  	// [ethTxsGossipInterval] is how often we attempt to gossip newly seen
    37  	// transactions to other nodes.
    38  	ethTxsGossipInterval = 500 * time.Millisecond
    39  )
    40  
    41  // Gossiper handles outgoing gossip of transactions
    42  type Gossiper interface {
    43  	// GossipAtomicTxs sends AppGossip message containing the given [txs]
    44  	GossipAtomicTxs(txs []*Tx) error
    45  	// GossipEthTxs sends AppGossip message containing the given [txs]
    46  	GossipEthTxs(txs []*types.Transaction) error
    47  }
    48  
    49  // pushGossiper is used to gossip transactions to the network
    50  type pushGossiper struct {
    51  	ctx                  *snow.Context
    52  	gossipActivationTime time.Time
    53  	config               Config
    54  
    55  	client        peer.NetworkClient
    56  	blockchain    *core.BlockChain
    57  	txPool        *core.TxPool
    58  	atomicMempool *Mempool
    59  
    60  	// We attempt to batch transactions we need to gossip to avoid runaway
    61  	// amplification of mempol chatter.
    62  	ethTxsToGossipChan chan []*types.Transaction
    63  	ethTxsToGossip     map[common.Hash]*types.Transaction
    64  	lastGossiped       time.Time
    65  	shutdownChan       chan struct{}
    66  	shutdownWg         *sync.WaitGroup
    67  
    68  	// [recentAtomicTxs] and [recentEthTxs] prevent us from over-gossiping the
    69  	// same transaction in a short period of time.
    70  	recentAtomicTxs *cache.LRU
    71  	recentEthTxs    *cache.LRU
    72  
    73  	codec codec.Manager
    74  	stats GossipSentStats
    75  }
    76  
    77  // createGossiper constructs and returns a pushGossiper or noopGossiper
    78  // based on whether vm.chainConfig.ApricotPhase4BlockTimestamp is set
    79  func (vm *VM) createGossiper(stats GossipStats) Gossiper {
    80  	if vm.chainConfig.ApricotPhase4BlockTimestamp == nil {
    81  		return &noopGossiper{}
    82  	}
    83  
    84  	net := &pushGossiper{
    85  		ctx:                  vm.ctx,
    86  		gossipActivationTime: time.Unix(vm.chainConfig.ApricotPhase4BlockTimestamp.Int64(), 0),
    87  		config:               vm.config,
    88  		client:               vm.client,
    89  		blockchain:           vm.blockChain,
    90  		txPool:               vm.txPool,
    91  		atomicMempool:        vm.mempool,
    92  		ethTxsToGossipChan:   make(chan []*types.Transaction),
    93  		ethTxsToGossip:       make(map[common.Hash]*types.Transaction),
    94  		shutdownChan:         vm.shutdownChan,
    95  		shutdownWg:           &vm.shutdownWg,
    96  		recentAtomicTxs:      &cache.LRU{Size: recentCacheSize},
    97  		recentEthTxs:         &cache.LRU{Size: recentCacheSize},
    98  		codec:                vm.networkCodec,
    99  		stats:                stats,
   100  	}
   101  	net.awaitEthTxGossip()
   102  	return net
   103  }
   104  
   105  // queueExecutableTxs attempts to select up to [maxTxs] from the tx pool for
   106  // regossiping.
   107  //
   108  // We assume that [txs] contains an array of nonce-ordered transactions for a given
   109  // account. This array of transactions can have gaps and start at a nonce lower
   110  // than the current state of an account.
   111  func (n *pushGossiper) queueExecutableTxs(state *state.StateDB, baseFee *big.Int, txs map[common.Address]types.Transactions, maxTxs int) types.Transactions {
   112  	// Setup heap for transactions
   113  	heads := make(types.TxByPriceAndTime, 0, len(txs))
   114  	for addr, accountTxs := range txs {
   115  		// Short-circuit here to avoid performing an unnecessary state lookup
   116  		if len(accountTxs) == 0 {
   117  			continue
   118  		}
   119  
   120  		// Ensure any transactions regossiped are immediately executable
   121  		var (
   122  			currentNonce = state.GetNonce(addr)
   123  			tx           *types.Transaction
   124  		)
   125  		for _, accountTx := range accountTxs {
   126  			// The tx pool may be out of sync with current state, so we iterate
   127  			// through the account transactions until we get to one that is
   128  			// executable.
   129  			if accountTx.Nonce() == currentNonce {
   130  				tx = accountTx
   131  				break
   132  			}
   133  			// There may be gaps in the tx pool and we could jump past the nonce we'd
   134  			// like to execute.
   135  			if accountTx.Nonce() > currentNonce {
   136  				break
   137  			}
   138  		}
   139  		if tx == nil {
   140  			continue
   141  		}
   142  
   143  		// Don't try to regossip a transaction too frequently
   144  		if time.Since(tx.FirstSeen()) < n.config.TxRegossipFrequency.Duration {
   145  			continue
   146  		}
   147  
   148  		// Ensure the fee the transaction pays is valid at tip
   149  		wrapped, err := types.NewTxWithMinerFee(tx, baseFee)
   150  		if err != nil {
   151  			log.Debug(
   152  				"not queuing tx for regossip",
   153  				"tx", tx.Hash(),
   154  				"err", err,
   155  			)
   156  			continue
   157  		}
   158  
   159  		heads = append(heads, wrapped)
   160  	}
   161  	heap.Init(&heads)
   162  
   163  	// Add up to [maxTxs] transactions to be gossiped
   164  	queued := make([]*types.Transaction, 0, maxTxs)
   165  	for len(heads) > 0 && len(queued) < maxTxs {
   166  		tx := heads[0].Tx
   167  		queued = append(queued, tx)
   168  		heap.Pop(&heads)
   169  	}
   170  
   171  	return queued
   172  }
   173  
   174  // queueRegossipTxs finds the best transactions in the mempool and adds up to
   175  // [TxRegossipMaxSize] of them to [ethTxsToGossip].
   176  func (n *pushGossiper) queueRegossipTxs() types.Transactions {
   177  	// Fetch all pending transactions
   178  	pending := n.txPool.Pending(true)
   179  
   180  	// Split the pending transactions into locals and remotes
   181  	localTxs := make(map[common.Address]types.Transactions)
   182  	remoteTxs := pending
   183  	for _, account := range n.txPool.Locals() {
   184  		if txs := remoteTxs[account]; len(txs) > 0 {
   185  			delete(remoteTxs, account)
   186  			localTxs[account] = txs
   187  		}
   188  	}
   189  
   190  	// Add best transactions to be gossiped (preferring local txs)
   191  	tip := n.blockchain.CurrentBlock()
   192  	state, err := n.blockchain.StateAt(tip.Root())
   193  	if err != nil || state == nil {
   194  		log.Debug(
   195  			"could not get state at tip",
   196  			"tip", tip.Hash(),
   197  			"err", err,
   198  		)
   199  		return nil
   200  	}
   201  	localQueued := n.queueExecutableTxs(state, tip.BaseFee(), localTxs, n.config.TxRegossipMaxSize)
   202  	localCount := len(localQueued)
   203  	n.stats.IncEthTxsRegossipQueuedLocal(localCount)
   204  	if localCount >= n.config.TxRegossipMaxSize {
   205  		n.stats.IncEthTxsRegossipQueued()
   206  		return localQueued
   207  	}
   208  	remoteQueued := n.queueExecutableTxs(state, tip.BaseFee(), remoteTxs, n.config.TxRegossipMaxSize-localCount)
   209  	n.stats.IncEthTxsRegossipQueuedRemote(len(remoteQueued))
   210  	if localCount+len(remoteQueued) > 0 {
   211  		// only increment the regossip stat when there are any txs queued
   212  		n.stats.IncEthTxsRegossipQueued()
   213  	}
   214  	return append(localQueued, remoteQueued...)
   215  }
   216  
   217  // awaitEthTxGossip periodically gossips transactions that have been queued for
   218  // gossip at least once every [ethTxsGossipInterval].
   219  func (n *pushGossiper) awaitEthTxGossip() {
   220  	n.shutdownWg.Add(1)
   221  	go n.ctx.Log.RecoverAndPanic(func() {
   222  		defer n.shutdownWg.Done()
   223  
   224  		var (
   225  			gossipTicker   = time.NewTicker(ethTxsGossipInterval)
   226  			regossipTicker = time.NewTicker(n.config.TxRegossipFrequency.Duration)
   227  		)
   228  
   229  		for {
   230  			select {
   231  			case <-gossipTicker.C:
   232  				if attempted, err := n.gossipEthTxs(false); err != nil {
   233  					log.Warn(
   234  						"failed to send eth transactions",
   235  						"len(txs)", attempted,
   236  						"err", err,
   237  					)
   238  				}
   239  			case <-regossipTicker.C:
   240  				for _, tx := range n.queueRegossipTxs() {
   241  					n.ethTxsToGossip[tx.Hash()] = tx
   242  				}
   243  				if attempted, err := n.gossipEthTxs(true); err != nil {
   244  					log.Warn(
   245  						"failed to send eth transactions",
   246  						"len(txs)", attempted,
   247  						"err", err,
   248  					)
   249  				}
   250  			case txs := <-n.ethTxsToGossipChan:
   251  				for _, tx := range txs {
   252  					n.ethTxsToGossip[tx.Hash()] = tx
   253  				}
   254  				if attempted, err := n.gossipEthTxs(false); err != nil {
   255  					log.Warn(
   256  						"failed to send eth transactions",
   257  						"len(txs)", attempted,
   258  						"err", err,
   259  					)
   260  				}
   261  			case <-n.shutdownChan:
   262  				return
   263  			}
   264  		}
   265  	})
   266  }
   267  
   268  func (n *pushGossiper) GossipAtomicTxs(txs []*Tx) error {
   269  	if time.Now().Before(n.gossipActivationTime) {
   270  		log.Trace(
   271  			"not gossiping atomic tx before the gossiping activation time",
   272  			"txs", txs,
   273  		)
   274  		return nil
   275  	}
   276  
   277  	errs := wrappers.Errs{}
   278  	for _, tx := range txs {
   279  		errs.Add(n.gossipAtomicTx(tx))
   280  	}
   281  	return errs.Err
   282  }
   283  
   284  func (n *pushGossiper) gossipAtomicTx(tx *Tx) error {
   285  	txID := tx.ID()
   286  	// Don't gossip transaction if it has been recently gossiped.
   287  	if _, has := n.recentAtomicTxs.Get(txID); has {
   288  		return nil
   289  	}
   290  	// If the transaction is not pending according to the mempool
   291  	// then there is no need to gossip it further.
   292  	if _, pending := n.atomicMempool.GetPendingTx(txID); !pending {
   293  		return nil
   294  	}
   295  	n.recentAtomicTxs.Put(txID, nil)
   296  
   297  	msg := message.AtomicTxGossip{
   298  		Tx: tx.SignedBytes(),
   299  	}
   300  	msgBytes, err := message.BuildGossipMessage(n.codec, msg)
   301  	if err != nil {
   302  		return err
   303  	}
   304  
   305  	log.Trace(
   306  		"gossiping atomic tx",
   307  		"txID", txID,
   308  	)
   309  	n.stats.IncAtomicGossipSent()
   310  	return n.client.Gossip(msgBytes)
   311  }
   312  
   313  func (n *pushGossiper) sendEthTxs(txs []*types.Transaction) error {
   314  	if len(txs) == 0 {
   315  		return nil
   316  	}
   317  
   318  	txBytes, err := rlp.EncodeToBytes(txs)
   319  	if err != nil {
   320  		return err
   321  	}
   322  	msg := message.EthTxsGossip{
   323  		Txs: txBytes,
   324  	}
   325  	msgBytes, err := message.BuildGossipMessage(n.codec, msg)
   326  	if err != nil {
   327  		return err
   328  	}
   329  
   330  	log.Trace(
   331  		"gossiping eth txs",
   332  		"len(txs)", len(txs),
   333  		"size(txs)", len(msg.Txs),
   334  	)
   335  	n.stats.IncEthTxsGossipSent()
   336  	return n.client.Gossip(msgBytes)
   337  }
   338  
   339  func (n *pushGossiper) gossipEthTxs(force bool) (int, error) {
   340  	if (!force && time.Since(n.lastGossiped) < ethTxsGossipInterval) || len(n.ethTxsToGossip) == 0 {
   341  		return 0, nil
   342  	}
   343  	n.lastGossiped = time.Now()
   344  	txs := make([]*types.Transaction, 0, len(n.ethTxsToGossip))
   345  	for _, tx := range n.ethTxsToGossip {
   346  		txs = append(txs, tx)
   347  		delete(n.ethTxsToGossip, tx.Hash())
   348  	}
   349  
   350  	selectedTxs := make([]*types.Transaction, 0)
   351  	for _, tx := range txs {
   352  		txHash := tx.Hash()
   353  		txStatus := n.txPool.Status([]common.Hash{txHash})[0]
   354  		if txStatus != core.TxStatusPending {
   355  			continue
   356  		}
   357  
   358  		if n.config.RemoteTxGossipOnlyEnabled && n.txPool.HasLocal(txHash) {
   359  			continue
   360  		}
   361  
   362  		// We check [force] outside of the if statement to avoid an unnecessary
   363  		// cache lookup.
   364  		if !force {
   365  			if _, has := n.recentEthTxs.Get(txHash); has {
   366  				continue
   367  			}
   368  		}
   369  		n.recentEthTxs.Put(txHash, nil)
   370  
   371  		selectedTxs = append(selectedTxs, tx)
   372  	}
   373  
   374  	if len(selectedTxs) == 0 {
   375  		return 0, nil
   376  	}
   377  
   378  	// Attempt to gossip [selectedTxs]
   379  	msgTxs := make([]*types.Transaction, 0)
   380  	msgTxsSize := common.StorageSize(0)
   381  	for _, tx := range selectedTxs {
   382  		size := tx.Size()
   383  		if msgTxsSize+size > message.EthMsgSoftCapSize {
   384  			if err := n.sendEthTxs(msgTxs); err != nil {
   385  				return len(selectedTxs), err
   386  			}
   387  			msgTxs = msgTxs[:0]
   388  			msgTxsSize = 0
   389  		}
   390  		msgTxs = append(msgTxs, tx)
   391  		msgTxsSize += size
   392  	}
   393  
   394  	// Send any remaining [msgTxs]
   395  	return len(selectedTxs), n.sendEthTxs(msgTxs)
   396  }
   397  
   398  // GossipEthTxs enqueues the provided [txs] for gossiping. At some point, the
   399  // [pushGossiper] will attempt to gossip the provided txs to other nodes
   400  // (usually right away if not under load).
   401  //
   402  // NOTE: We never return a non-nil error from this function but retain the
   403  // option to do so in case it becomes useful.
   404  func (n *pushGossiper) GossipEthTxs(txs []*types.Transaction) error {
   405  	if time.Now().Before(n.gossipActivationTime) {
   406  		log.Trace(
   407  			"not gossiping eth txs before the gossiping activation time",
   408  			"len(txs)", len(txs),
   409  		)
   410  		return nil
   411  	}
   412  
   413  	select {
   414  	case n.ethTxsToGossipChan <- txs:
   415  	case <-n.shutdownChan:
   416  	}
   417  	return nil
   418  }
   419  
   420  // GossipHandler handles incoming gossip messages
   421  type GossipHandler struct {
   422  	vm            *VM
   423  	atomicMempool *Mempool
   424  	txPool        *core.TxPool
   425  	stats         GossipReceivedStats
   426  }
   427  
   428  func NewGossipHandler(vm *VM, stats GossipReceivedStats) *GossipHandler {
   429  	return &GossipHandler{
   430  		vm:            vm,
   431  		atomicMempool: vm.mempool,
   432  		txPool:        vm.txPool,
   433  		stats:         stats,
   434  	}
   435  }
   436  
   437  func (h *GossipHandler) HandleAtomicTx(nodeID ids.NodeID, msg message.AtomicTxGossip) error {
   438  	log.Trace(
   439  		"AppGossip called with AtomicTxGossip",
   440  		"peerID", nodeID,
   441  	)
   442  
   443  	if len(msg.Tx) == 0 {
   444  		log.Trace(
   445  			"AppGossip received empty AtomicTxGossip Message",
   446  			"peerID", nodeID,
   447  		)
   448  		return nil
   449  	}
   450  
   451  	// In the case that the gossip message contains a transaction,
   452  	// attempt to parse it and add it as a remote.
   453  	tx := Tx{}
   454  	if _, err := Codec.Unmarshal(msg.Tx, &tx); err != nil {
   455  		log.Trace(
   456  			"AppGossip provided invalid tx",
   457  			"err", err,
   458  		)
   459  		return nil
   460  	}
   461  	unsignedBytes, err := Codec.Marshal(codecVersion, &tx.UnsignedAtomicTx)
   462  	if err != nil {
   463  		log.Trace(
   464  			"AppGossip failed to marshal unsigned tx",
   465  			"err", err,
   466  		)
   467  		return nil
   468  	}
   469  	tx.Initialize(unsignedBytes, msg.Tx)
   470  
   471  	txID := tx.ID()
   472  	h.stats.IncAtomicGossipReceived()
   473  	if _, dropped, found := h.atomicMempool.GetTx(txID); found {
   474  		h.stats.IncAtomicGossipReceivedKnown()
   475  		return nil
   476  	} else if dropped {
   477  		h.stats.IncAtomicGossipReceivedDropped()
   478  		return nil
   479  	}
   480  
   481  	h.stats.IncAtomicGossipReceivedNew()
   482  	if err := h.vm.issueTx(&tx, false /*=local*/); err != nil {
   483  		log.Trace(
   484  			"AppGossip provided invalid transaction",
   485  			"peerID", nodeID,
   486  			"err", err,
   487  		)
   488  	}
   489  
   490  	return nil
   491  }
   492  
   493  func (h *GossipHandler) HandleEthTxs(nodeID ids.NodeID, msg message.EthTxsGossip) error {
   494  	log.Trace(
   495  		"AppGossip called with EthTxsGossip",
   496  		"peerID", nodeID,
   497  		"size(txs)", len(msg.Txs),
   498  	)
   499  
   500  	if len(msg.Txs) == 0 {
   501  		log.Trace(
   502  			"AppGossip received empty EthTxsGossip Message",
   503  			"peerID", nodeID,
   504  		)
   505  		return nil
   506  	}
   507  
   508  	// The maximum size of this encoded object is enforced by the codec.
   509  	txs := make([]*types.Transaction, 0)
   510  	if err := rlp.DecodeBytes(msg.Txs, &txs); err != nil {
   511  		log.Trace(
   512  			"AppGossip provided invalid txs",
   513  			"peerID", nodeID,
   514  			"err", err,
   515  		)
   516  		return nil
   517  	}
   518  	h.stats.IncEthTxsGossipReceived()
   519  	errs := h.txPool.AddRemotes(txs)
   520  	for i, err := range errs {
   521  		if err != nil {
   522  			log.Trace(
   523  				"AppGossip failed to add to mempool",
   524  				"err", err,
   525  				"tx", txs[i].Hash(),
   526  			)
   527  			if err == core.ErrAlreadyKnown {
   528  				h.stats.IncEthTxsGossipReceivedKnown()
   529  			} else {
   530  				h.stats.IncAtomicGossipReceivedError()
   531  			}
   532  			continue
   533  		}
   534  		h.stats.IncEthTxsGossipReceivedNew()
   535  	}
   536  	return nil
   537  }
   538  
   539  // noopGossiper should be used when gossip communication is not supported
   540  type noopGossiper struct{}
   541  
   542  func (n *noopGossiper) GossipAtomicTxs([]*Tx) error {
   543  	return nil
   544  }
   545  func (n *noopGossiper) GossipEthTxs([]*types.Transaction) error {
   546  	return nil
   547  }