github.com/MetalBlockchain/subnet-evm@v0.4.9/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  	"math/big"
     8  	"sync"
     9  	"time"
    10  
    11  	"github.com/MetalBlockchain/metalgo/codec"
    12  
    13  	"github.com/MetalBlockchain/subnet-evm/peer"
    14  
    15  	"github.com/MetalBlockchain/metalgo/cache"
    16  	"github.com/MetalBlockchain/metalgo/ids"
    17  	"github.com/MetalBlockchain/metalgo/snow"
    18  
    19  	"github.com/ethereum/go-ethereum/common"
    20  	"github.com/ethereum/go-ethereum/log"
    21  	"github.com/ethereum/go-ethereum/rlp"
    22  
    23  	"github.com/MetalBlockchain/subnet-evm/core"
    24  	"github.com/MetalBlockchain/subnet-evm/core/state"
    25  	"github.com/MetalBlockchain/subnet-evm/core/types"
    26  	"github.com/MetalBlockchain/subnet-evm/plugin/evm/message"
    27  )
    28  
    29  const (
    30  	// We allow [recentCacheSize] to be fairly large because we only store hashes
    31  	// in the cache, not entire transactions.
    32  	recentCacheSize = 512
    33  
    34  	// [txsGossipInterval] is how often we attempt to gossip newly seen
    35  	// transactions to other nodes.
    36  	txsGossipInterval = 500 * time.Millisecond
    37  )
    38  
    39  // Gossiper handles outgoing gossip of transactions
    40  type Gossiper interface {
    41  	// GossipTxs sends AppGossip message containing the given [txs]
    42  	GossipTxs(txs []*types.Transaction) error
    43  }
    44  
    45  // pushGossiper is used to gossip transactions to the network
    46  type pushGossiper struct {
    47  	ctx                  *snow.Context
    48  	gossipActivationTime time.Time
    49  	config               Config
    50  
    51  	client     peer.NetworkClient
    52  	blockchain *core.BlockChain
    53  	txPool     *core.TxPool
    54  
    55  	// We attempt to batch transactions we need to gossip to avoid runaway
    56  	// amplification of mempol chatter.
    57  	txsToGossipChan chan []*types.Transaction
    58  	txsToGossip     map[common.Hash]*types.Transaction
    59  	lastGossiped    time.Time
    60  	shutdownChan    chan struct{}
    61  	shutdownWg      *sync.WaitGroup
    62  
    63  	// [recentTxs] prevent us from over-gossiping the
    64  	// same transaction in a short period of time.
    65  	recentTxs *cache.LRU[common.Hash, interface{}]
    66  
    67  	codec  codec.Manager
    68  	signer types.Signer
    69  	stats  GossipSentStats
    70  }
    71  
    72  // createGossiper constructs and returns a pushGossiper or noopGossiper
    73  // based on whether vm.chainConfig.SubnetEVMTimestamp is set
    74  func (vm *VM) createGossiper(stats GossipStats) Gossiper {
    75  	if vm.chainConfig.SubnetEVMTimestamp == nil {
    76  		return &noopGossiper{}
    77  	}
    78  	net := &pushGossiper{
    79  		ctx:                  vm.ctx,
    80  		gossipActivationTime: time.Unix(vm.chainConfig.SubnetEVMTimestamp.Int64(), 0),
    81  		config:               vm.config,
    82  		client:               vm.client,
    83  		blockchain:           vm.blockChain,
    84  		txPool:               vm.txPool,
    85  		txsToGossipChan:      make(chan []*types.Transaction),
    86  		txsToGossip:          make(map[common.Hash]*types.Transaction),
    87  		shutdownChan:         vm.shutdownChan,
    88  		shutdownWg:           &vm.shutdownWg,
    89  		recentTxs:            &cache.LRU[common.Hash, interface{}]{Size: recentCacheSize},
    90  		codec:                vm.networkCodec,
    91  		signer:               types.LatestSigner(vm.blockChain.Config()),
    92  		stats:                stats,
    93  	}
    94  	net.awaitEthTxGossip()
    95  	return net
    96  }
    97  
    98  // addrStatus used to track the metadata of addresses being queued for
    99  // regossip.
   100  type addrStatus struct {
   101  	nonce    uint64
   102  	txsAdded int
   103  }
   104  
   105  // queueExecutableTxs attempts to select up to [maxTxs] from the tx pool for
   106  // regossiping (with at most [maxAcctTxs] per account).
   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(
   112  	state *state.StateDB,
   113  	baseFee *big.Int,
   114  	txs map[common.Address]types.Transactions,
   115  	regossipFrequency Duration,
   116  	maxTxs int,
   117  	maxAcctTxs int,
   118  ) types.Transactions {
   119  	var (
   120  		stxs     = types.NewTransactionsByPriceAndNonce(n.signer, txs, baseFee)
   121  		statuses = make(map[common.Address]*addrStatus)
   122  		queued   = make([]*types.Transaction, 0, maxTxs)
   123  	)
   124  
   125  	// Iterate over possible transactions until there are none left or we have
   126  	// hit the regossip target.
   127  	for len(queued) < maxTxs {
   128  		next := stxs.Peek()
   129  		if next == nil {
   130  			break
   131  		}
   132  
   133  		sender, _ := types.Sender(n.signer, next)
   134  		status, ok := statuses[sender]
   135  		if !ok {
   136  			status = &addrStatus{
   137  				nonce: state.GetNonce(sender),
   138  			}
   139  			statuses[sender] = status
   140  		}
   141  
   142  		// The tx pool may be out of sync with current state, so we iterate
   143  		// through the account transactions until we get to one that is
   144  		// executable.
   145  		switch {
   146  		case next.Nonce() < status.nonce:
   147  			stxs.Shift()
   148  			continue
   149  		case next.Nonce() > status.nonce, time.Since(next.FirstSeen()) < regossipFrequency.Duration,
   150  			status.txsAdded >= maxAcctTxs:
   151  			stxs.Pop()
   152  			continue
   153  		}
   154  		queued = append(queued, next)
   155  		status.nonce++
   156  		status.txsAdded++
   157  		stxs.Shift()
   158  	}
   159  	return queued
   160  }
   161  
   162  // queueRegossipTxs finds the best non-priority transactions in the mempool and adds up to
   163  // [RegossipMaxTxs] of them to [txsToGossip].
   164  func (n *pushGossiper) queueRegossipTxs() types.Transactions {
   165  	// Fetch all pending transactions
   166  	pending := n.txPool.Pending(true)
   167  
   168  	// Split the pending transactions into locals and remotes
   169  	localTxs := make(map[common.Address]types.Transactions)
   170  	remoteTxs := pending
   171  	for _, account := range n.txPool.Locals() {
   172  		if txs := remoteTxs[account]; len(txs) > 0 {
   173  			delete(remoteTxs, account)
   174  			localTxs[account] = txs
   175  		}
   176  	}
   177  
   178  	// Add best transactions to be gossiped (preferring local txs)
   179  	tip := n.blockchain.CurrentBlock()
   180  	state, err := n.blockchain.StateAt(tip.Root())
   181  	if err != nil || state == nil {
   182  		log.Debug(
   183  			"could not get state at tip",
   184  			"tip", tip.Hash(),
   185  			"err", err,
   186  		)
   187  		return nil
   188  	}
   189  	rgFrequency := n.config.RegossipFrequency
   190  	rgMaxTxs := n.config.RegossipMaxTxs
   191  	rgTxsPerAddr := n.config.RegossipTxsPerAddress
   192  	localQueued := n.queueExecutableTxs(state, tip.BaseFee(), localTxs, rgFrequency, rgMaxTxs, rgTxsPerAddr)
   193  	localCount := len(localQueued)
   194  	n.stats.IncEthTxsRegossipQueuedLocal(localCount)
   195  	if localCount >= rgMaxTxs {
   196  		n.stats.IncEthTxsRegossipQueued()
   197  		return localQueued
   198  	}
   199  	remoteQueued := n.queueExecutableTxs(state, tip.BaseFee(), remoteTxs, rgFrequency, rgMaxTxs-localCount, rgTxsPerAddr)
   200  	n.stats.IncEthTxsRegossipQueuedRemote(len(remoteQueued))
   201  	if localCount+len(remoteQueued) > 0 {
   202  		// only increment the regossip stat when there are any txs queued
   203  		n.stats.IncEthTxsRegossipQueued()
   204  	}
   205  	return append(localQueued, remoteQueued...)
   206  }
   207  
   208  // queueRegossipTxs finds the best priority transactions in the mempool and adds up to
   209  // [PriorityRegossipMaxTxs] of them to [txsToGossip].
   210  func (n *pushGossiper) queuePriorityRegossipTxs() types.Transactions {
   211  	// Fetch all pending transactions from the priority addresses
   212  	priorityTxs := n.txPool.PendingFrom(n.config.PriorityRegossipAddresses, true)
   213  
   214  	// Add best transactions to be gossiped
   215  	tip := n.blockchain.CurrentBlock()
   216  	state, err := n.blockchain.StateAt(tip.Root())
   217  	if err != nil || state == nil {
   218  		log.Debug(
   219  			"could not get state at tip",
   220  			"tip", tip.Hash(),
   221  			"err", err,
   222  		)
   223  		return nil
   224  	}
   225  	return n.queueExecutableTxs(
   226  		state, tip.BaseFee(), priorityTxs,
   227  		n.config.PriorityRegossipFrequency,
   228  		n.config.PriorityRegossipMaxTxs,
   229  		n.config.PriorityRegossipTxsPerAddress,
   230  	)
   231  }
   232  
   233  // awaitEthTxGossip periodically gossips transactions that have been queued for
   234  // gossip at least once every [txsGossipInterval].
   235  func (n *pushGossiper) awaitEthTxGossip() {
   236  	n.shutdownWg.Add(1)
   237  	go n.ctx.Log.RecoverAndPanic(func() {
   238  		defer n.shutdownWg.Done()
   239  
   240  		var (
   241  			gossipTicker           = time.NewTicker(txsGossipInterval)
   242  			regossipTicker         = time.NewTicker(n.config.RegossipFrequency.Duration)
   243  			priorityRegossipTicker = time.NewTicker(n.config.PriorityRegossipFrequency.Duration)
   244  		)
   245  
   246  		for {
   247  			select {
   248  			case <-gossipTicker.C:
   249  				if attempted, err := n.gossipTxs(false); err != nil {
   250  					log.Warn(
   251  						"failed to send eth transactions",
   252  						"len(txs)", attempted,
   253  						"err", err,
   254  					)
   255  				}
   256  			case <-regossipTicker.C:
   257  				for _, tx := range n.queueRegossipTxs() {
   258  					n.txsToGossip[tx.Hash()] = tx
   259  				}
   260  				if attempted, err := n.gossipTxs(true); err != nil {
   261  					log.Warn(
   262  						"failed to regossip eth transactions",
   263  						"len(txs)", attempted,
   264  						"err", err,
   265  					)
   266  				}
   267  			case <-priorityRegossipTicker.C:
   268  				for _, tx := range n.queuePriorityRegossipTxs() {
   269  					n.txsToGossip[tx.Hash()] = tx
   270  				}
   271  				if attempted, err := n.gossipTxs(true); err != nil {
   272  					log.Warn(
   273  						"failed to regossip priority eth transactions",
   274  						"len(txs)", attempted,
   275  						"err", err,
   276  					)
   277  				}
   278  			case txs := <-n.txsToGossipChan:
   279  				for _, tx := range txs {
   280  					n.txsToGossip[tx.Hash()] = tx
   281  				}
   282  				if attempted, err := n.gossipTxs(false); err != nil {
   283  					log.Warn(
   284  						"failed to send eth transactions",
   285  						"len(txs)", attempted,
   286  						"err", err,
   287  					)
   288  				}
   289  			case <-n.shutdownChan:
   290  				return
   291  			}
   292  		}
   293  	})
   294  }
   295  
   296  func (n *pushGossiper) sendTxs(txs []*types.Transaction) error {
   297  	if len(txs) == 0 {
   298  		return nil
   299  	}
   300  
   301  	txBytes, err := rlp.EncodeToBytes(txs)
   302  	if err != nil {
   303  		return err
   304  	}
   305  	msg := message.TxsGossip{
   306  		Txs: txBytes,
   307  	}
   308  	msgBytes, err := message.BuildGossipMessage(n.codec, msg)
   309  	if err != nil {
   310  		return err
   311  	}
   312  	log.Trace(
   313  		"gossiping eth txs",
   314  		"len(txs)", len(txs),
   315  		"size(txs)", len(msg.Txs),
   316  	)
   317  	n.stats.IncEthTxsGossipSent()
   318  	return n.client.Gossip(msgBytes)
   319  }
   320  
   321  func (n *pushGossiper) gossipTxs(force bool) (int, error) {
   322  	if (!force && time.Since(n.lastGossiped) < txsGossipInterval) || len(n.txsToGossip) == 0 {
   323  		return 0, nil
   324  	}
   325  	n.lastGossiped = time.Now()
   326  	txs := make([]*types.Transaction, 0, len(n.txsToGossip))
   327  	for _, tx := range n.txsToGossip {
   328  		txs = append(txs, tx)
   329  		delete(n.txsToGossip, tx.Hash())
   330  	}
   331  
   332  	selectedTxs := make([]*types.Transaction, 0)
   333  	for _, tx := range txs {
   334  		txHash := tx.Hash()
   335  		txStatus := n.txPool.Status([]common.Hash{txHash})[0]
   336  		if txStatus != core.TxStatusPending {
   337  			continue
   338  		}
   339  
   340  		if n.config.RemoteGossipOnlyEnabled && n.txPool.HasLocal(txHash) {
   341  			continue
   342  		}
   343  
   344  		// We check [force] outside of the if statement to avoid an unnecessary
   345  		// cache lookup.
   346  		if !force {
   347  			if _, has := n.recentTxs.Get(txHash); has {
   348  				continue
   349  			}
   350  		}
   351  		n.recentTxs.Put(txHash, nil)
   352  
   353  		selectedTxs = append(selectedTxs, tx)
   354  	}
   355  
   356  	if len(selectedTxs) == 0 {
   357  		return 0, nil
   358  	}
   359  
   360  	// Attempt to gossip [selectedTxs]
   361  	msgTxs := make([]*types.Transaction, 0)
   362  	msgTxsSize := common.StorageSize(0)
   363  	for _, tx := range selectedTxs {
   364  		size := tx.Size()
   365  		if msgTxsSize+size > message.TxMsgSoftCapSize {
   366  			if err := n.sendTxs(msgTxs); err != nil {
   367  				return len(selectedTxs), err
   368  			}
   369  			msgTxs = msgTxs[:0]
   370  			msgTxsSize = 0
   371  		}
   372  		msgTxs = append(msgTxs, tx)
   373  		msgTxsSize += size
   374  	}
   375  
   376  	// Send any remaining [msgTxs]
   377  	return len(selectedTxs), n.sendTxs(msgTxs)
   378  }
   379  
   380  // GossipTxs enqueues the provided [txs] for gossiping. At some point, the
   381  // [pushGossiper] will attempt to gossip the provided txs to other nodes
   382  // (usually right away if not under load).
   383  //
   384  // NOTE: We never return a non-nil error from this function but retain the
   385  // option to do so in case it becomes useful.
   386  func (n *pushGossiper) GossipTxs(txs []*types.Transaction) error {
   387  	if time.Now().Before(n.gossipActivationTime) {
   388  		log.Trace(
   389  			"not gossiping eth txs before the gossiping activation time",
   390  			"len(txs)", len(txs),
   391  		)
   392  		return nil
   393  	}
   394  
   395  	select {
   396  	case n.txsToGossipChan <- txs:
   397  	case <-n.shutdownChan:
   398  	}
   399  	return nil
   400  }
   401  
   402  // GossipHandler handles incoming gossip messages
   403  type GossipHandler struct {
   404  	vm     *VM
   405  	txPool *core.TxPool
   406  	stats  GossipReceivedStats
   407  }
   408  
   409  func NewGossipHandler(vm *VM, stats GossipReceivedStats) *GossipHandler {
   410  	return &GossipHandler{
   411  		vm:     vm,
   412  		txPool: vm.txPool,
   413  		stats:  stats,
   414  	}
   415  }
   416  
   417  func (h *GossipHandler) HandleTxs(nodeID ids.NodeID, msg message.TxsGossip) error {
   418  	log.Trace(
   419  		"AppGossip called with TxsGossip",
   420  		"peerID", nodeID,
   421  		"size(txs)", len(msg.Txs),
   422  	)
   423  
   424  	if len(msg.Txs) == 0 {
   425  		log.Trace(
   426  			"AppGossip received empty TxsGossip Message",
   427  			"peerID", nodeID,
   428  		)
   429  		return nil
   430  	}
   431  
   432  	// The maximum size of this encoded object is enforced by the codec.
   433  	txs := make([]*types.Transaction, 0)
   434  	if err := rlp.DecodeBytes(msg.Txs, &txs); err != nil {
   435  		log.Trace(
   436  			"AppGossip provided invalid txs",
   437  			"peerID", nodeID,
   438  			"err", err,
   439  		)
   440  		return nil
   441  	}
   442  	h.stats.IncEthTxsGossipReceived()
   443  	errs := h.txPool.AddRemotes(txs)
   444  	for i, err := range errs {
   445  		if err != nil {
   446  			log.Trace(
   447  				"AppGossip failed to add to mempool",
   448  				"err", err,
   449  				"tx", txs[i].Hash(),
   450  			)
   451  			if err == core.ErrAlreadyKnown {
   452  				h.stats.IncEthTxsGossipReceivedKnown()
   453  			}
   454  			continue
   455  		}
   456  		h.stats.IncEthTxsGossipReceivedNew()
   457  	}
   458  	return nil
   459  }
   460  
   461  // noopGossiper should be used when gossip communication is not supported
   462  type noopGossiper struct{}
   463  
   464  func (n *noopGossiper) GossipTxs([]*types.Transaction) error {
   465  	return nil
   466  }