package mempool import ( "errors" "sort" "sync" "time" "github.com/CityOfZion/neo-go/pkg/core/transaction" "github.com/CityOfZion/neo-go/pkg/util" ) var ( // ErrConflict is returned when transaction being added is incompatible // with the contents of the memory pool (using the same inputs as some // other transaction in the pool) ErrConflict = errors.New("conflicts with the memory pool") // ErrDup is returned when transaction being added is already present // in the memory pool. ErrDup = errors.New("already in the memory pool") // ErrOOM is returned when transaction just doesn't fit in the memory // pool because of its capacity constraints. ErrOOM = errors.New("out of memory") ) // item represents a transaction in the the Memory pool. type item struct { txn *transaction.Transaction timeStamp time.Time perByteFee util.Fixed8 netFee util.Fixed8 isLowPrio bool } // items is a slice of item. type items []*item // Pool stores the unconfirms transactions. type Pool struct { lock sync.RWMutex verifiedMap map[util.Uint256]*item verifiedTxes items capacity int } func (p items) Len() int { return len(p) } func (p items) Swap(i, j int) { p[i], p[j] = p[j], p[i] } func (p items) Less(i, j int) bool { return p[i].CompareTo(p[j]) < 0 } // CompareTo returns the difference between two items. // difference < 0 implies p < otherP. // difference = 0 implies p = otherP. // difference > 0 implies p > otherP. func (p *item) CompareTo(otherP *item) int { if otherP == nil { return 1 } if !p.isLowPrio && otherP.isLowPrio { return 1 } if p.isLowPrio && !otherP.isLowPrio { return -1 } if p.isLowPrio && otherP.isLowPrio { thisIsClaimTx := p.txn.Type == transaction.ClaimType otherIsClaimTx := otherP.txn.Type == transaction.ClaimType if thisIsClaimTx != otherIsClaimTx { // This is a claim Tx and other isn't. if thisIsClaimTx { return 1 } // The other is claim Tx and this isn't. return -1 } } // Fees sorted ascending. if ret := p.perByteFee.CompareTo(otherP.perByteFee); ret != 0 { return ret } if ret := p.netFee.CompareTo(otherP.netFee); ret != 0 { return ret } // Transaction hash sorted descending. return otherP.txn.Hash().CompareTo(p.txn.Hash()) } // Count returns the total number of uncofirm transactions. func (mp *Pool) Count() int { mp.lock.RLock() defer mp.lock.RUnlock() return mp.count() } // count is an internal unlocked version of Count. func (mp *Pool) count() int { return len(mp.verifiedTxes) } // ContainsKey checks if a transactions hash is in the Pool. func (mp *Pool) ContainsKey(hash util.Uint256) bool { mp.lock.RLock() defer mp.lock.RUnlock() return mp.containsKey(hash) } // containsKey is an internal unlocked version of ContainsKey. func (mp *Pool) containsKey(hash util.Uint256) bool { if _, ok := mp.verifiedMap[hash]; ok { return true } return false } // Add tries to add given transaction to the Pool. func (mp *Pool) Add(t *transaction.Transaction, fee Feer) error { var pItem = &item{ txn: t, timeStamp: time.Now().UTC(), perByteFee: fee.FeePerByte(t), netFee: fee.NetworkFee(t), isLowPrio: fee.IsLowPriority(t), } mp.lock.Lock() if !mp.verifyInputs(t) { mp.lock.Unlock() return ErrConflict } if mp.containsKey(t.Hash()) { mp.lock.Unlock() return ErrDup } mp.verifiedMap[t.Hash()] = pItem // Insert into sorted array (from max to min, that could also be done // using sort.Sort(sort.Reverse()), but it incurs more overhead. Notice // also that we're searching for position that is strictly more // prioritized than our new item because we do expect a lot of // transactions with the same priority and appending to the end of the // slice is always more efficient. n := sort.Search(len(mp.verifiedTxes), func(n int) bool { return pItem.CompareTo(mp.verifiedTxes[n]) > 0 }) mp.verifiedTxes = append(mp.verifiedTxes, pItem) if n != len(mp.verifiedTxes) { copy(mp.verifiedTxes[n+1:], mp.verifiedTxes[n:]) mp.verifiedTxes[n] = pItem } mp.lock.Unlock() if mp.Count() > mp.capacity { mp.RemoveOverCapacity() } mp.lock.RLock() _, ok := mp.verifiedMap[t.Hash()] updateMempoolMetrics(len(mp.verifiedTxes)) mp.lock.RUnlock() if !ok { return ErrOOM } return nil } // Remove removes an item from the mempool, if it exists there (and does // nothing if it doesn't). func (mp *Pool) Remove(hash util.Uint256) { mp.lock.Lock() if _, ok := mp.verifiedMap[hash]; ok { var num int delete(mp.verifiedMap, hash) for num := range mp.verifiedTxes { if hash.Equals(mp.verifiedTxes[num].txn.Hash()) { break } } if num < len(mp.verifiedTxes)-1 { mp.verifiedTxes = append(mp.verifiedTxes[:num], mp.verifiedTxes[num+1:]...) } else if num == len(mp.verifiedTxes)-1 { mp.verifiedTxes = mp.verifiedTxes[:num] } } updateMempoolMetrics(len(mp.verifiedTxes)) mp.lock.Unlock() } // RemoveStale filters verified transactions through the given function keeping // only the transactions for which it returns a true result. It's used to quickly // drop part of the mempool that is now invalid after the block acceptance. func (mp *Pool) RemoveStale(isOK func(*transaction.Transaction) bool) { mp.lock.Lock() // We expect a lot of changes, so it's easier to allocate a new slice // rather than move things in an old one. newVerifiedTxes := make([]*item, 0, mp.capacity) for _, itm := range mp.verifiedTxes { if isOK(itm.txn) { newVerifiedTxes = append(newVerifiedTxes, itm) } else { delete(mp.verifiedMap, itm.txn.Hash()) } } mp.verifiedTxes = newVerifiedTxes mp.lock.Unlock() } // RemoveOverCapacity removes transactions with lowest fees until the total number of transactions // in the Pool is within the capacity of the Pool. func (mp *Pool) RemoveOverCapacity() { mp.lock.Lock() num := mp.count() - mp.capacity for i := 1; i <= num; i++ { txToDrop := mp.verifiedTxes[len(mp.verifiedTxes)-num].txn delete(mp.verifiedMap, txToDrop.Hash()) } mp.verifiedTxes = mp.verifiedTxes[:len(mp.verifiedTxes)-num] updateMempoolMetrics(mp.count()) mp.lock.Unlock() } // NewMemPool returns a new Pool struct. func NewMemPool(capacity int) Pool { return Pool{ verifiedMap: make(map[util.Uint256]*item), verifiedTxes: make([]*item, 0, capacity), capacity: capacity, } } // TryGetValue returns a transaction if it exists in the memory pool. func (mp *Pool) TryGetValue(hash util.Uint256) (*transaction.Transaction, bool) { mp.lock.RLock() defer mp.lock.RUnlock() if pItem, ok := mp.verifiedMap[hash]; ok { return pItem.txn, ok } return nil, false } // GetVerifiedTransactions returns a slice of Input from all the transactions in the memory pool // whose hash is not included in excludedHashes. func (mp *Pool) GetVerifiedTransactions() []*transaction.Transaction { mp.lock.RLock() defer mp.lock.RUnlock() var t = make([]*transaction.Transaction, len(mp.verifiedTxes)) var i int for _, p := range mp.verifiedTxes { t[i] = p.txn i++ } return t } // verifyInputs is an internal unprotected version of Verify. func (mp *Pool) verifyInputs(tx *transaction.Transaction) bool { if len(tx.Inputs) == 0 { return true } for num := range mp.verifiedTxes { txn := mp.verifiedTxes[num].txn for i := range txn.Inputs { for j := 0; j < len(tx.Inputs); j++ { if txn.Inputs[i] == tx.Inputs[j] { return false } } } } return true } // Verify verifies if the inputs of a transaction tx are already used in any other transaction in the memory pool. // If yes, the transaction tx is not a valid transaction and the function return false. // If no, the transaction tx is a valid transaction and the function return true. func (mp *Pool) Verify(tx *transaction.Transaction) bool { mp.lock.RLock() defer mp.lock.RUnlock() return mp.verifyInputs(tx) }