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 }