gitlab.com/flarenetwork/coreth@v0.1.1/plugin/evm/mempool.go (about)

     1  // (c) 2019-2020, Ava Labs, Inc. All rights reserved.
     2  // See the file LICENSE for licensing terms.
     3  
     4  package evm
     5  
     6  import (
     7  	"sync"
     8  
     9  	"github.com/ava-labs/avalanchego/cache"
    10  	"github.com/ava-labs/avalanchego/ids"
    11  	"github.com/ethereum/go-ethereum/log"
    12  )
    13  
    14  const (
    15  	discardedTxsCacheSize = 50
    16  )
    17  
    18  // Mempool is a simple mempool for atomic transactions
    19  type Mempool struct {
    20  	lock sync.RWMutex
    21  	// maxSize is the maximum number of transactions allowed to be kept in mempool
    22  	maxSize int
    23  	// currentTx is the transaction about to be added to a block.
    24  	currentTx *Tx
    25  	// txs is the set of transactions that need to be issued into new blocks
    26  	txs map[ids.ID]*Tx
    27  	// issuedTxs is the set of transactions that have been issued into a new block
    28  	issuedTxs map[ids.ID]*Tx
    29  	// discardedTxs is an LRU Cache of transactions that have been discarded after failing
    30  	// verification.
    31  	discardedTxs *cache.LRU
    32  	// Pending is a channel of length one, which the mempool ensures has an item on
    33  	// it as long as there is an unissued transaction remaining in [txs]
    34  	Pending chan struct{}
    35  }
    36  
    37  // NewMempool returns a Mempool with [maxSize]
    38  func NewMempool(maxSize int) *Mempool {
    39  	return &Mempool{
    40  		txs:          make(map[ids.ID]*Tx),
    41  		issuedTxs:    make(map[ids.ID]*Tx),
    42  		discardedTxs: &cache.LRU{Size: discardedTxsCacheSize},
    43  		Pending:      make(chan struct{}, 1),
    44  		maxSize:      maxSize,
    45  	}
    46  }
    47  
    48  // Len returns the number of transactions in the mempool
    49  func (m *Mempool) Len() int {
    50  	m.lock.RLock()
    51  	defer m.lock.RUnlock()
    52  
    53  	return m.length()
    54  }
    55  
    56  // assumes the lock is held
    57  func (m *Mempool) length() int {
    58  	return len(m.txs) + len(m.issuedTxs)
    59  }
    60  
    61  // Add attempts to add [tx] to the mempool and returns an error if
    62  // it could not be addeed to the mempool.
    63  func (m *Mempool) AddTx(tx *Tx) error {
    64  	m.lock.Lock()
    65  	defer m.lock.Unlock()
    66  
    67  	txID := tx.ID()
    68  	// If [txID] has already been issued or is the currentTx
    69  	// there's no need to add it.
    70  	if _, exists := m.issuedTxs[txID]; exists {
    71  		return nil
    72  	}
    73  	if m.currentTx != nil && m.currentTx.ID() == txID {
    74  		return nil
    75  	}
    76  
    77  	if m.length() >= m.maxSize {
    78  		return errTooManyAtomicTx
    79  	}
    80  
    81  	if _, exists := m.txs[txID]; exists {
    82  		return nil
    83  	}
    84  
    85  	// If the transaction was recently discarded, log the event and evict from
    86  	// discarded transactions so it's not in two places within the mempool.
    87  	// We allow the transaction to be re-issued since it may have been invalid due
    88  	// to an atomic UTXO not being present yet.
    89  	if _, has := m.discardedTxs.Get(txID); has {
    90  		log.Debug("Adding recently discarded transaction %s back to the mempool", txID)
    91  		m.discardedTxs.Evict(txID)
    92  	}
    93  
    94  	m.txs[txID] = tx
    95  	// When adding [tx] to the mempool make sure that there is an item in Pending
    96  	// to signal the VM to produce a block. Note: if the VM's buildStatus has already
    97  	// been set to something other than [dontBuild], this will be ignored and won't be
    98  	// reset until the engine calls BuildBlock. This case is handled in IssueCurrentTx
    99  	// and CancelCurrentTx.
   100  	m.addPending()
   101  	return nil
   102  }
   103  
   104  // NextTx returns a transaction to be issued from the mempool.
   105  func (m *Mempool) NextTx() (*Tx, bool) {
   106  	m.lock.Lock()
   107  	defer m.lock.Unlock()
   108  
   109  	for txID, tx := range m.txs {
   110  		delete(m.txs, txID)
   111  		m.currentTx = tx
   112  		return tx, true
   113  	}
   114  
   115  	return nil, false
   116  }
   117  
   118  // GetTx returns the transaction [txID] if it was issued
   119  // by this node and returns whether it was dropped and whether
   120  // it exists.
   121  func (m *Mempool) GetTx(txID ids.ID) (*Tx, bool, bool) {
   122  	m.lock.RLock()
   123  	defer m.lock.RUnlock()
   124  
   125  	if tx, ok := m.txs[txID]; ok {
   126  		return tx, false, true
   127  	}
   128  	if tx, ok := m.issuedTxs[txID]; ok {
   129  		return tx, false, true
   130  	}
   131  	if m.currentTx != nil && m.currentTx.ID() == txID {
   132  		return m.currentTx, false, true
   133  	}
   134  	if tx, exists := m.discardedTxs.Get(txID); exists {
   135  		return tx.(*Tx), true, true
   136  	}
   137  
   138  	return nil, false, false
   139  }
   140  
   141  // IssueCurrentTx marks [currentTx] as issued if there is one
   142  func (m *Mempool) IssueCurrentTx() {
   143  	m.lock.Lock()
   144  	defer m.lock.Unlock()
   145  
   146  	if m.currentTx != nil {
   147  		m.issuedTxs[m.currentTx.ID()] = m.currentTx
   148  		m.currentTx = nil
   149  	}
   150  
   151  	// If there are more transactions to be issued, add an item
   152  	// to Pending.
   153  	if len(m.txs) > 0 {
   154  		m.addPending()
   155  	}
   156  }
   157  
   158  // CancelCurrentTx marks the attempt to issue [currentTx]
   159  // as being aborted. If this is called after a buildBlock error
   160  // caused by the atomic transaction, then DiscardCurrentTx should have been called
   161  // such that this call will have no effect and should not re-issue the invalid tx.
   162  func (m *Mempool) CancelCurrentTx() {
   163  	m.lock.Lock()
   164  	defer m.lock.Unlock()
   165  
   166  	// If building a block failed, put the currentTx back in [txs]
   167  	// if it exists.
   168  	if m.currentTx != nil {
   169  		m.txs[m.currentTx.ID()] = m.currentTx
   170  		m.currentTx = nil
   171  	}
   172  
   173  	// If there are more transactions to be issued, add an item
   174  	// to Pending.
   175  	if len(m.txs) > 0 {
   176  		m.addPending()
   177  	}
   178  }
   179  
   180  // DiscardCurrentTx marks [currentTx] as invalid and aborts the attempt
   181  // to issue it since it failed verification.
   182  // Adding to Pending should be handled by CancelCurrentTx in this case.
   183  func (m *Mempool) DiscardCurrentTx() {
   184  	m.lock.Lock()
   185  	defer m.lock.Unlock()
   186  
   187  	if m.currentTx == nil {
   188  		return
   189  	}
   190  
   191  	m.discardedTxs.Put(m.currentTx.ID(), m.currentTx)
   192  	m.currentTx = nil
   193  }
   194  
   195  // RemoveTx removes [txID] from the mempool completely.
   196  func (m *Mempool) RemoveTx(txID ids.ID) {
   197  	m.lock.Lock()
   198  	defer m.lock.Unlock()
   199  
   200  	if m.currentTx != nil && m.currentTx.ID() == txID {
   201  		m.currentTx = nil
   202  	}
   203  	delete(m.txs, txID)
   204  	delete(m.issuedTxs, txID)
   205  	m.discardedTxs.Evict(txID)
   206  }
   207  
   208  // RejectTx marks [txID] as being rejected and attempts to re-issue
   209  // it if it was previously in the mempool.
   210  func (m *Mempool) RejectTx(txID ids.ID) {
   211  	m.lock.Lock()
   212  	defer m.lock.Unlock()
   213  
   214  	tx, ok := m.issuedTxs[txID]
   215  	if !ok {
   216  		return
   217  	}
   218  	// If the transaction was issued by the mempool, add it back
   219  	// to transactions pending issuance.
   220  	delete(m.issuedTxs, txID)
   221  	m.txs[txID] = tx
   222  	// Add an item to Pending to ensure the VM attempts to reissue
   223  	// [tx].
   224  	m.addPending()
   225  }
   226  
   227  // addPending makes sure that an item is in the Pending channel.
   228  func (m *Mempool) addPending() {
   229  	select {
   230  	case m.Pending <- struct{}{}:
   231  	default:
   232  	}
   233  }