mempool: remove unverified transactions pool

Our mempool only contains valid verified transactions all the time, it never
has any unverified ones. Unverified pool made some sense for quick unverifying
after the new block acceptance (and gradual background reverification), but
reverification needs some non-trivial locking between blockchain and mempool
and internal mempool state locking (reverifying tx and moving it between
unverified and verified pools must be atomic). But our current reverification
is fast enough (and has all the appropriate locks), so bothering with
unverified pool makes little sense.
This commit is contained in:
Roman Khimov 2020-02-06 15:33:49 +03:00
parent b675903f52
commit 1133bbe584
3 changed files with 27 additions and 95 deletions

View file

@ -37,11 +37,9 @@ type items []*item
// Pool stores the unconfirms transactions. // Pool stores the unconfirms transactions.
type Pool struct { type Pool struct {
lock sync.RWMutex lock sync.RWMutex
verifiedMap map[util.Uint256]*item verifiedMap map[util.Uint256]*item
unverifiedMap map[util.Uint256]*item verifiedTxes items
verifiedTxes items
unverifiedTxes items
capacity int capacity int
} }
@ -103,7 +101,7 @@ func (mp *Pool) Count() int {
// count is an internal unlocked version of Count. // count is an internal unlocked version of Count.
func (mp *Pool) count() int { func (mp *Pool) count() int {
return len(mp.verifiedTxes) + len(mp.unverifiedTxes) return len(mp.verifiedTxes)
} }
// ContainsKey checks if a transactions hash is in the Pool. // ContainsKey checks if a transactions hash is in the Pool.
@ -120,10 +118,6 @@ func (mp *Pool) containsKey(hash util.Uint256) bool {
return true return true
} }
if _, ok := mp.unverifiedMap[hash]; ok {
return true
}
return false return false
} }
@ -168,7 +162,7 @@ func (mp *Pool) Add(t *transaction.Transaction, fee Feer) error {
} }
mp.lock.RLock() mp.lock.RLock()
_, ok := mp.verifiedMap[t.Hash()] _, ok := mp.verifiedMap[t.Hash()]
updateMempoolMetrics(len(mp.verifiedTxes), len(mp.unverifiedTxes)) updateMempoolMetrics(len(mp.verifiedTxes))
mp.lock.RUnlock() mp.lock.RUnlock()
if !ok { if !ok {
return ErrOOM return ErrOOM
@ -179,31 +173,22 @@ func (mp *Pool) Add(t *transaction.Transaction, fee Feer) error {
// Remove removes an item from the mempool, if it exists there (and does // Remove removes an item from the mempool, if it exists there (and does
// nothing if it doesn't). // nothing if it doesn't).
func (mp *Pool) Remove(hash util.Uint256) { func (mp *Pool) Remove(hash util.Uint256) {
var mapAndPools = []struct {
txMap map[util.Uint256]*item
txPool *items
}{
{txMap: mp.verifiedMap, txPool: &mp.verifiedTxes},
{txMap: mp.unverifiedMap, txPool: &mp.unverifiedTxes},
}
mp.lock.Lock() mp.lock.Lock()
for _, mapAndPool := range mapAndPools { if _, ok := mp.verifiedMap[hash]; ok {
if _, ok := mapAndPool.txMap[hash]; ok { var num int
var num int delete(mp.verifiedMap, hash)
delete(mapAndPool.txMap, hash) for num := range mp.verifiedTxes {
for num := range *mapAndPool.txPool { if hash.Equals(mp.verifiedTxes[num].txn.Hash()) {
if hash.Equals((*mapAndPool.txPool)[num].txn.Hash()) { break
break
}
}
if num < len(*mapAndPool.txPool)-1 {
*mapAndPool.txPool = append((*mapAndPool.txPool)[:num], (*mapAndPool.txPool)[num+1:]...)
} else if num == len(*mapAndPool.txPool)-1 {
*mapAndPool.txPool = (*mapAndPool.txPool)[:num]
} }
} }
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), len(mp.unverifiedTxes)) updateMempoolMetrics(len(mp.verifiedTxes))
mp.lock.Unlock() mp.lock.Unlock()
} }
@ -230,32 +215,22 @@ func (mp *Pool) RemoveStale(isOK func(*transaction.Transaction) bool) {
// in the Pool is within the capacity of the Pool. // in the Pool is within the capacity of the Pool.
func (mp *Pool) RemoveOverCapacity() { func (mp *Pool) RemoveOverCapacity() {
mp.lock.Lock() mp.lock.Lock()
for mp.count()-mp.capacity > 0 { num := mp.count() - mp.capacity
minItem, argPosition := getLowestFeeTransaction(mp.verifiedTxes, mp.unverifiedTxes) for i := 1; i <= num; i++ {
if argPosition == 1 { txToDrop := mp.verifiedTxes[len(mp.verifiedTxes)-num].txn
// minItem belongs to the mp.sortedLowPrioTxn slice. delete(mp.verifiedMap, txToDrop.Hash())
// The corresponding unsorted pool is is mp.unsortedTxn.
delete(mp.verifiedMap, minItem.txn.Hash())
mp.verifiedTxes = mp.verifiedTxes[:len(mp.verifiedTxes)-1]
} else {
// minItem belongs to the mp.unverifiedSortedLowPrioTxn slice.
// The corresponding unsorted pool is is mp.unverifiedTxn.
delete(mp.unverifiedMap, minItem.txn.Hash())
mp.unverifiedTxes = mp.unverifiedTxes[:len(mp.unverifiedTxes)-1]
}
updateMempoolMetrics(len(mp.verifiedTxes), len(mp.unverifiedTxes))
} }
mp.verifiedTxes = mp.verifiedTxes[:len(mp.verifiedTxes)-num]
updateMempoolMetrics(mp.count())
mp.lock.Unlock() mp.lock.Unlock()
} }
// NewMemPool returns a new Pool struct. // NewMemPool returns a new Pool struct.
func NewMemPool(capacity int) Pool { func NewMemPool(capacity int) Pool {
return Pool{ return Pool{
verifiedMap: make(map[util.Uint256]*item), verifiedMap: make(map[util.Uint256]*item),
unverifiedMap: make(map[util.Uint256]*item), verifiedTxes: make([]*item, 0, capacity),
verifiedTxes: make([]*item, 0, capacity), capacity: capacity,
unverifiedTxes: make([]*item, 0, capacity),
capacity: capacity,
} }
} }
@ -267,40 +242,9 @@ func (mp *Pool) TryGetValue(hash util.Uint256) (*transaction.Transaction, bool)
return pItem.txn, ok return pItem.txn, ok
} }
if pItem, ok := mp.unverifiedMap[hash]; ok {
return pItem.txn, ok
}
return nil, false return nil, false
} }
// getLowestFeeTransaction returns the item with the lowest fee amongst the "verifiedTxnSorted"
// and "unverifiedTxnSorted" items along with a integer. The integer can assume two values, 1 and 2 which indicate
// that the item with the lowest fee was found in "verifiedTxnSorted" respectively in "unverifiedTxnSorted".
// "verifiedTxnSorted" and "unverifiedTxnSorted" are sorted slice order by transaction fee ascending. This means that
// the transaction with lowest fee start at index 0.
// Reference: GetLowestFeeTransaction method in C# (https://github.com/neo-project/neo/blob/master/neo/Ledger/MemoryPool.cs)
func getLowestFeeTransaction(verifiedTxnSorted items, unverifiedTxnSorted items) (*item, int) {
minItem := min(unverifiedTxnSorted)
verifiedMin := min(verifiedTxnSorted)
if verifiedMin == nil || (minItem != nil && verifiedMin.CompareTo(minItem) >= 0) {
return minItem, 2
}
minItem = verifiedMin
return minItem, 1
}
// min returns the minimum item in a ascending sorted slice of pool items.
// The function can't be applied to unsorted slice!
func min(sortedPool items) *item {
if len(sortedPool) == 0 {
return nil
}
return sortedPool[len(sortedPool)-1]
}
// GetVerifiedTransactions returns a slice of Input from all the transactions in the memory pool // GetVerifiedTransactions returns a slice of Input from all the transactions in the memory pool
// whose hash is not included in excludedHashes. // whose hash is not included in excludedHashes.
func (mp *Pool) GetVerifiedTransactions() []*transaction.Transaction { func (mp *Pool) GetVerifiedTransactions() []*transaction.Transaction {

View file

@ -49,9 +49,7 @@ func testMemPoolAddRemoveWithFeer(t *testing.T, fs Feer) {
require.Equal(t, false, ok) require.Equal(t, false, ok)
// Make sure nothing left in the mempool after removal. // Make sure nothing left in the mempool after removal.
assert.Equal(t, 0, len(mp.verifiedMap)) assert.Equal(t, 0, len(mp.verifiedMap))
assert.Equal(t, 0, len(mp.unverifiedMap))
assert.Equal(t, 0, len(mp.verifiedTxes)) assert.Equal(t, 0, len(mp.verifiedTxes))
assert.Equal(t, 0, len(mp.unverifiedTxes))
} }
func TestMemPoolAddRemove(t *testing.T) { func TestMemPoolAddRemove(t *testing.T) {

View file

@ -11,24 +11,14 @@ var (
Namespace: "neogo", Namespace: "neogo",
}, },
) )
//mempoolUnverifiedTx prometheus metric.
mempoolUnverifiedTx = prometheus.NewGauge(
prometheus.GaugeOpts{
Help: "Mempool Unverified TXs",
Name: "mempool_unverified_tx",
Namespace: "neogo",
},
)
) )
func init() { func init() {
prometheus.MustRegister( prometheus.MustRegister(
mempoolUnsortedTx, mempoolUnsortedTx,
mempoolUnverifiedTx,
) )
} }
func updateMempoolMetrics(unsortedTxnLen int, unverifiedTxnLen int) { func updateMempoolMetrics(unsortedTxnLen int) {
mempoolUnsortedTx.Set(float64(unsortedTxnLen)) mempoolUnsortedTx.Set(float64(unsortedTxnLen))
mempoolUnverifiedTx.Set(float64(unverifiedTxnLen))
} }