package mempool import ( "sort" "sync" "time" "github.com/tendermint/tendermint/internal/libs/clist" "github.com/tendermint/tendermint/types" ) // TxInfo are parameters that get passed when attempting to add a tx to the // mempool. type TxInfo struct { // SenderID is the internal peer ID used in the mempool to identify the // sender, storing two bytes with each transaction instead of 20 bytes for // the types.NodeID. SenderID uint16 // SenderNodeID is the actual types.NodeID of the sender. SenderNodeID types.NodeID } // WrappedTx defines a wrapper around a raw transaction with additional metadata // that is used for indexing. type WrappedTx struct { // tx represents the raw binary transaction data tx types.Tx // hash defines the transaction hash and the primary key used in the mempool hash types.TxKey // height defines the height at which the transaction was validated at height int64 // gasWanted defines the amount of gas the transaction sender requires gasWanted int64 // priority defines the transaction's priority as specified by the application // in the ResponseCheckTx response. priority int64 // sender defines the transaction's sender as specified by the application in // the ResponseCheckTx response. sender string // timestamp is the time at which the node first received the transaction from // a peer. It is used as a second dimension is prioritizing transactions when // two transactions have the same priority. timestamp time.Time // peers records a mapping of all peers that sent a given transaction peers map[uint16]struct{} // heapIndex defines the index of the item in the heap heapIndex int // gossipEl references the linked-list element in the gossip index gossipEl *clist.CElement // removed marks the transaction as removed from the mempool. This is set // during RemoveTx and is needed due to the fact that a given existing // transaction in the mempool can be evicted when it is simultaneously having // a reCheckTx callback executed. removed bool } func (wtx *WrappedTx) Size() int { return len(wtx.tx) } // TxStore implements a thread-safe mapping of valid transaction(s). // // NOTE: // - Concurrent read-only access to a *WrappedTx object is OK. However, mutative // access is not allowed. Regardless, it is not expected for the mempool to // need mutative access. type TxStore struct { mtx sync.RWMutex hashTxs map[types.TxKey]*WrappedTx // primary index senderTxs map[string]*WrappedTx // sender is defined by the ABCI application } func NewTxStore() *TxStore { return &TxStore{ senderTxs: make(map[string]*WrappedTx), hashTxs: make(map[types.TxKey]*WrappedTx), } } // Size returns the total number of transactions in the store. func (txs *TxStore) Size() int { txs.mtx.RLock() defer txs.mtx.RUnlock() return len(txs.hashTxs) } // GetAllTxs returns all the transactions currently in the store. func (txs *TxStore) GetAllTxs() []*WrappedTx { txs.mtx.RLock() defer txs.mtx.RUnlock() wTxs := make([]*WrappedTx, len(txs.hashTxs)) i := 0 for _, wtx := range txs.hashTxs { wTxs[i] = wtx i++ } return wTxs } // GetTxBySender returns a *WrappedTx by the transaction's sender property // defined by the ABCI application. func (txs *TxStore) GetTxBySender(sender string) *WrappedTx { txs.mtx.RLock() defer txs.mtx.RUnlock() return txs.senderTxs[sender] } // GetTxByHash returns a *WrappedTx by the transaction's hash. func (txs *TxStore) GetTxByHash(hash types.TxKey) *WrappedTx { txs.mtx.RLock() defer txs.mtx.RUnlock() return txs.hashTxs[hash] } // IsTxRemoved returns true if a transaction by hash is marked as removed and // false otherwise. func (txs *TxStore) IsTxRemoved(hash types.TxKey) bool { txs.mtx.RLock() defer txs.mtx.RUnlock() wtx, ok := txs.hashTxs[hash] if ok { return wtx.removed } return false } // SetTx stores a *WrappedTx by it's hash. If the transaction also contains a // non-empty sender, we additionally store the transaction by the sender as // defined by the ABCI application. func (txs *TxStore) SetTx(wtx *WrappedTx) { txs.mtx.Lock() defer txs.mtx.Unlock() if len(wtx.sender) > 0 { txs.senderTxs[wtx.sender] = wtx } txs.hashTxs[wtx.tx.Key()] = wtx } // RemoveTx removes a *WrappedTx from the transaction store. It deletes all // indexes of the transaction. func (txs *TxStore) RemoveTx(wtx *WrappedTx) { txs.mtx.Lock() defer txs.mtx.Unlock() if len(wtx.sender) > 0 { delete(txs.senderTxs, wtx.sender) } delete(txs.hashTxs, wtx.tx.Key()) wtx.removed = true } // TxHasPeer returns true if a transaction by hash has a given peer ID and false // otherwise. If the transaction does not exist, false is returned. func (txs *TxStore) TxHasPeer(hash types.TxKey, peerID uint16) bool { txs.mtx.RLock() defer txs.mtx.RUnlock() wtx := txs.hashTxs[hash] if wtx == nil { return false } _, ok := wtx.peers[peerID] return ok } // GetOrSetPeerByTxHash looks up a WrappedTx by transaction hash and adds the // given peerID to the WrappedTx's set of peers that sent us this transaction. // We return true if we've already recorded the given peer for this transaction // and false otherwise. If the transaction does not exist by hash, we return // (nil, false). func (txs *TxStore) GetOrSetPeerByTxHash(hash types.TxKey, peerID uint16) (*WrappedTx, bool) { txs.mtx.Lock() defer txs.mtx.Unlock() wtx := txs.hashTxs[hash] if wtx == nil { return nil, false } if wtx.peers == nil { wtx.peers = make(map[uint16]struct{}) } if _, ok := wtx.peers[peerID]; ok { return wtx, true } wtx.peers[peerID] = struct{}{} return wtx, false } // WrappedTxList implements a thread-safe list of *WrappedTx objects that can be // used to build generic transaction indexes in the mempool. It accepts a // comparator function, less(a, b *WrappedTx) bool, that compares two WrappedTx // references which is used during Insert in order to determine sorted order. If // less returns true, a <= b. type WrappedTxList struct { mtx sync.RWMutex txs []*WrappedTx less func(*WrappedTx, *WrappedTx) bool } func NewWrappedTxList(less func(*WrappedTx, *WrappedTx) bool) *WrappedTxList { return &WrappedTxList{ txs: make([]*WrappedTx, 0), less: less, } } // Size returns the number of WrappedTx objects in the list. func (wtl *WrappedTxList) Size() int { wtl.mtx.RLock() defer wtl.mtx.RUnlock() return len(wtl.txs) } // Reset resets the list of transactions to an empty list. func (wtl *WrappedTxList) Reset() { wtl.mtx.Lock() defer wtl.mtx.Unlock() wtl.txs = make([]*WrappedTx, 0) } // Insert inserts a WrappedTx reference into the sorted list based on the list's // comparator function. func (wtl *WrappedTxList) Insert(wtx *WrappedTx) { wtl.mtx.Lock() defer wtl.mtx.Unlock() i := sort.Search(len(wtl.txs), func(i int) bool { return wtl.less(wtl.txs[i], wtx) }) if i == len(wtl.txs) { // insert at the end wtl.txs = append(wtl.txs, wtx) return } // Make space for the inserted element by shifting values at the insertion // index up one index. // // NOTE: The call to append does not allocate memory when cap(wtl.txs) > len(wtl.txs). wtl.txs = append(wtl.txs[:i+1], wtl.txs[i:]...) wtl.txs[i] = wtx } // Remove attempts to remove a WrappedTx from the sorted list. func (wtl *WrappedTxList) Remove(wtx *WrappedTx) { wtl.mtx.Lock() defer wtl.mtx.Unlock() i := sort.Search(len(wtl.txs), func(i int) bool { return wtl.less(wtl.txs[i], wtx) }) // Since the list is sorted, we evaluate all elements starting at i. Note, if // the element does not exist, we may potentially evaluate the entire remainder // of the list. However, a caller should not be expected to call Remove with a // non-existing element. for i < len(wtl.txs) { if wtl.txs[i] == wtx { wtl.txs = append(wtl.txs[:i], wtl.txs[i+1:]...) return } i++ } }