github.com/nspcc-dev/neo-go@v0.105.2-0.20240517133400-6be757af3eba/pkg/core/mempool/mem_pool.go (about)

     1  package mempool
     2  
     3  import (
     4  	"errors"
     5  	"fmt"
     6  	"math/bits"
     7  	"sort"
     8  	"sync"
     9  	"sync/atomic"
    10  
    11  	"github.com/holiman/uint256"
    12  	"github.com/nspcc-dev/neo-go/pkg/core/mempoolevent"
    13  	"github.com/nspcc-dev/neo-go/pkg/core/transaction"
    14  	"github.com/nspcc-dev/neo-go/pkg/util"
    15  )
    16  
    17  var (
    18  	// ErrInsufficientFunds is returned when the Sender is not able to pay for
    19  	// the transaction being added irrespective of the other contents of the
    20  	// pool.
    21  	ErrInsufficientFunds = errors.New("insufficient funds")
    22  	// ErrConflict is returned when the transaction being added is incompatible
    23  	// with the contents of the memory pool (Sender doesn't have enough GAS
    24  	// to pay for all transactions in the pool).
    25  	ErrConflict = errors.New("conflicts: insufficient funds for all pooled tx")
    26  	// ErrDup is returned when the transaction being added is already present
    27  	// in the memory pool.
    28  	ErrDup = errors.New("already in the memory pool")
    29  	// ErrOOM is returned when the transaction just doesn't fit in the memory
    30  	// pool because of its capacity constraints.
    31  	ErrOOM = errors.New("out of memory")
    32  	// ErrConflictsAttribute is returned when the transaction conflicts with other transactions
    33  	// due to its (or theirs) Conflicts attributes.
    34  	ErrConflictsAttribute = errors.New("conflicts with memory pool due to Conflicts attribute")
    35  	// ErrOracleResponse is returned when the mempool already contains a transaction
    36  	// with the same oracle response ID and higher network fee.
    37  	ErrOracleResponse = errors.New("conflicts with memory pool due to OracleResponse attribute")
    38  )
    39  
    40  // item represents a transaction in the the Memory pool.
    41  type item struct {
    42  	txn        *transaction.Transaction
    43  	blockStamp uint32
    44  	data       any
    45  }
    46  
    47  // items is a slice of an item.
    48  type items []item
    49  
    50  // utilityBalanceAndFees stores the sender's balance and overall fees of
    51  // the sender's transactions which are currently in the mempool.
    52  type utilityBalanceAndFees struct {
    53  	balance uint256.Int
    54  	feeSum  uint256.Int
    55  }
    56  
    57  // Pool stores the unconfirmed transactions.
    58  type Pool struct {
    59  	lock         sync.RWMutex
    60  	verifiedMap  map[util.Uint256]*transaction.Transaction
    61  	verifiedTxes items
    62  	fees         map[util.Uint160]utilityBalanceAndFees
    63  	// conflicts is a map of the hashes of the transactions which are conflicting with the mempooled ones.
    64  	conflicts map[util.Uint256][]util.Uint256
    65  	// oracleResp contains the ids of oracle responses for the tx in the pool.
    66  	oracleResp map[uint64]util.Uint256
    67  
    68  	capacity        int
    69  	feePerByte      int64
    70  	payerIndex      int
    71  	updateMetricsCb func(int)
    72  
    73  	resendThreshold uint32
    74  	resendFunc      func(*transaction.Transaction, any)
    75  
    76  	// subscriptions for mempool events
    77  	subscriptionsEnabled bool
    78  	subscriptionsOn      atomic.Bool
    79  	stopCh               chan struct{}
    80  	events               chan mempoolevent.Event
    81  	subCh                chan chan<- mempoolevent.Event // there are no other events in mempool except Event, so no need in generic subscribers type
    82  	unsubCh              chan chan<- mempoolevent.Event
    83  }
    84  
    85  func (p items) Len() int           { return len(p) }
    86  func (p items) Swap(i, j int)      { p[i], p[j] = p[j], p[i] }
    87  func (p items) Less(i, j int) bool { return p[i].CompareTo(p[j]) < 0 }
    88  
    89  // CompareTo returns the difference between two items.
    90  // difference < 0 implies p < otherP.
    91  // difference = 0 implies p = otherP.
    92  // difference > 0 implies p > otherP.
    93  func (p item) CompareTo(otherP item) int {
    94  	pHigh := p.txn.HasAttribute(transaction.HighPriority)
    95  	otherHigh := otherP.txn.HasAttribute(transaction.HighPriority)
    96  	if pHigh && !otherHigh {
    97  		return 1
    98  	} else if !pHigh && otherHigh {
    99  		return -1
   100  	}
   101  
   102  	// Fees sorted ascending.
   103  	if ret := int(p.txn.FeePerByte() - otherP.txn.FeePerByte()); ret != 0 {
   104  		return ret
   105  	}
   106  
   107  	return int(p.txn.NetworkFee - otherP.txn.NetworkFee)
   108  }
   109  
   110  // Count returns the total number of uncofirmed transactions.
   111  func (mp *Pool) Count() int {
   112  	mp.lock.RLock()
   113  	defer mp.lock.RUnlock()
   114  	return mp.count()
   115  }
   116  
   117  // count is an internal unlocked version of Count.
   118  func (mp *Pool) count() int {
   119  	return len(mp.verifiedTxes)
   120  }
   121  
   122  // ContainsKey checks if the transactions hash is in the Pool.
   123  func (mp *Pool) ContainsKey(hash util.Uint256) bool {
   124  	mp.lock.RLock()
   125  	defer mp.lock.RUnlock()
   126  
   127  	return mp.containsKey(hash)
   128  }
   129  
   130  // containsKey is an internal unlocked version of ContainsKey.
   131  func (mp *Pool) containsKey(hash util.Uint256) bool {
   132  	if _, ok := mp.verifiedMap[hash]; ok {
   133  		return true
   134  	}
   135  
   136  	return false
   137  }
   138  
   139  // HasConflicts returns true if the transaction is already in the pool or in the Conflicts attributes
   140  // of the pooled transactions or has Conflicts attributes against the pooled transactions.
   141  func (mp *Pool) HasConflicts(t *transaction.Transaction, fee Feer) bool {
   142  	mp.lock.RLock()
   143  	defer mp.lock.RUnlock()
   144  
   145  	if mp.containsKey(t.Hash()) {
   146  		return true
   147  	}
   148  	// do not check sender's signature and fee
   149  	if _, ok := mp.conflicts[t.Hash()]; ok {
   150  		return true
   151  	}
   152  	for _, attr := range t.GetAttributes(transaction.ConflictsT) {
   153  		if mp.containsKey(attr.Value.(*transaction.Conflicts).Hash) {
   154  			return true
   155  		}
   156  	}
   157  	return false
   158  }
   159  
   160  // tryAddSendersFee tries to add system fee and network fee to the total sender`s fee in the mempool
   161  // and returns false if both balance check is required and the sender does not have enough GAS to pay.
   162  func (mp *Pool) tryAddSendersFee(tx *transaction.Transaction, feer Feer, needCheck bool) bool {
   163  	payer := tx.Signers[mp.payerIndex].Account
   164  	senderFee, ok := mp.fees[payer]
   165  	if !ok {
   166  		_ = senderFee.balance.SetFromBig(feer.GetUtilityTokenBalance(payer))
   167  		mp.fees[payer] = senderFee
   168  	}
   169  	if needCheck {
   170  		newFeeSum, err := checkBalance(tx, senderFee)
   171  		if err != nil {
   172  			return false
   173  		}
   174  		senderFee.feeSum = newFeeSum
   175  	} else {
   176  		senderFee.feeSum.AddUint64(&senderFee.feeSum, uint64(tx.SystemFee+tx.NetworkFee))
   177  	}
   178  	mp.fees[payer] = senderFee
   179  	return true
   180  }
   181  
   182  // checkBalance returns a new cumulative fee balance for the account or an error in
   183  // case the sender doesn't have enough GAS to pay for the transaction.
   184  func checkBalance(tx *transaction.Transaction, balance utilityBalanceAndFees) (uint256.Int, error) {
   185  	var txFee uint256.Int
   186  
   187  	txFee.SetUint64(uint64(tx.SystemFee + tx.NetworkFee))
   188  	if balance.balance.Cmp(&txFee) < 0 {
   189  		return txFee, ErrInsufficientFunds
   190  	}
   191  	txFee.Add(&txFee, &balance.feeSum)
   192  	if balance.balance.Cmp(&txFee) < 0 {
   193  		return txFee, ErrConflict
   194  	}
   195  	return txFee, nil
   196  }
   197  
   198  // Add tries to add the given transaction to the Pool.
   199  func (mp *Pool) Add(t *transaction.Transaction, fee Feer, data ...any) error {
   200  	var pItem = item{
   201  		txn:        t,
   202  		blockStamp: fee.BlockHeight(),
   203  	}
   204  	if data != nil {
   205  		pItem.data = data[0]
   206  	}
   207  	mp.lock.Lock()
   208  	if mp.containsKey(t.Hash()) {
   209  		mp.lock.Unlock()
   210  		return ErrDup
   211  	}
   212  	conflictsToBeRemoved, err := mp.checkTxConflicts(t, fee)
   213  	if err != nil {
   214  		mp.lock.Unlock()
   215  		return err
   216  	}
   217  	if attrs := t.GetAttributes(transaction.OracleResponseT); len(attrs) != 0 {
   218  		id := attrs[0].Value.(*transaction.OracleResponse).ID
   219  		h, ok := mp.oracleResp[id]
   220  		if ok {
   221  			if mp.verifiedMap[h].NetworkFee >= t.NetworkFee {
   222  				mp.lock.Unlock()
   223  				return ErrOracleResponse
   224  			}
   225  			mp.removeInternal(h, fee)
   226  		}
   227  		mp.oracleResp[id] = t.Hash()
   228  	}
   229  
   230  	// Remove conflicting transactions.
   231  	for _, conflictingTx := range conflictsToBeRemoved {
   232  		mp.removeInternal(conflictingTx.Hash(), fee)
   233  	}
   234  	// Insert into a sorted array (from max to min, that could also be done
   235  	// using sort.Sort(sort.Reverse()), but it incurs more overhead. Notice
   236  	// also that we're searching for a position that is strictly more
   237  	// prioritized than our new item because we do expect a lot of
   238  	// transactions with the same priority and appending to the end of the
   239  	// slice is always more efficient.
   240  	n := sort.Search(len(mp.verifiedTxes), func(n int) bool {
   241  		return pItem.CompareTo(mp.verifiedTxes[n]) > 0
   242  	})
   243  
   244  	// We've reached our capacity already.
   245  	if len(mp.verifiedTxes) == mp.capacity {
   246  		// Less prioritized than the least prioritized we already have, won't fit.
   247  		if n == len(mp.verifiedTxes) {
   248  			mp.lock.Unlock()
   249  			return ErrOOM
   250  		}
   251  		// Ditch the last one.
   252  		unlucky := mp.verifiedTxes[len(mp.verifiedTxes)-1]
   253  		delete(mp.verifiedMap, unlucky.txn.Hash())
   254  		mp.removeConflictsOf(unlucky.txn)
   255  		if attrs := unlucky.txn.GetAttributes(transaction.OracleResponseT); len(attrs) != 0 {
   256  			delete(mp.oracleResp, attrs[0].Value.(*transaction.OracleResponse).ID)
   257  		}
   258  		mp.verifiedTxes[len(mp.verifiedTxes)-1] = pItem
   259  		if mp.subscriptionsOn.Load() {
   260  			mp.events <- mempoolevent.Event{
   261  				Type: mempoolevent.TransactionRemoved,
   262  				Tx:   unlucky.txn,
   263  				Data: unlucky.data,
   264  			}
   265  		}
   266  	} else {
   267  		mp.verifiedTxes = append(mp.verifiedTxes, pItem)
   268  	}
   269  	if n != len(mp.verifiedTxes)-1 {
   270  		copy(mp.verifiedTxes[n+1:], mp.verifiedTxes[n:])
   271  		mp.verifiedTxes[n] = pItem
   272  	}
   273  	mp.verifiedMap[t.Hash()] = t
   274  	// Add conflicting hashes to the mp.conflicts list.
   275  	for _, attr := range t.GetAttributes(transaction.ConflictsT) {
   276  		hash := attr.Value.(*transaction.Conflicts).Hash
   277  		mp.conflicts[hash] = append(mp.conflicts[hash], t.Hash())
   278  	}
   279  	// we already checked balance in checkTxConflicts, so don't need to check again
   280  	mp.tryAddSendersFee(pItem.txn, fee, false)
   281  
   282  	if mp.updateMetricsCb != nil {
   283  		mp.updateMetricsCb(len(mp.verifiedTxes))
   284  	}
   285  	mp.lock.Unlock()
   286  
   287  	if mp.subscriptionsOn.Load() {
   288  		mp.events <- mempoolevent.Event{
   289  			Type: mempoolevent.TransactionAdded,
   290  			Tx:   pItem.txn,
   291  			Data: pItem.data,
   292  		}
   293  	}
   294  	return nil
   295  }
   296  
   297  // Remove removes an item from the mempool if it exists there (and does
   298  // nothing if it doesn't).
   299  func (mp *Pool) Remove(hash util.Uint256, feer Feer) {
   300  	mp.lock.Lock()
   301  	mp.removeInternal(hash, feer)
   302  	mp.lock.Unlock()
   303  }
   304  
   305  // removeInternal is an internal unlocked representation of Remove.
   306  func (mp *Pool) removeInternal(hash util.Uint256, feer Feer) {
   307  	if tx, ok := mp.verifiedMap[hash]; ok {
   308  		var num int
   309  		delete(mp.verifiedMap, hash)
   310  		for num = range mp.verifiedTxes {
   311  			if hash.Equals(mp.verifiedTxes[num].txn.Hash()) {
   312  				break
   313  			}
   314  		}
   315  		itm := mp.verifiedTxes[num]
   316  		if num < len(mp.verifiedTxes)-1 {
   317  			mp.verifiedTxes = append(mp.verifiedTxes[:num], mp.verifiedTxes[num+1:]...)
   318  		} else if num == len(mp.verifiedTxes)-1 {
   319  			mp.verifiedTxes = mp.verifiedTxes[:num]
   320  		}
   321  		payer := itm.txn.Signers[mp.payerIndex].Account
   322  		senderFee := mp.fees[payer]
   323  		senderFee.feeSum.SubUint64(&senderFee.feeSum, uint64(tx.SystemFee+tx.NetworkFee))
   324  		mp.fees[payer] = senderFee
   325  		// remove all conflicting hashes from mp.conflicts list
   326  		mp.removeConflictsOf(tx)
   327  		if attrs := tx.GetAttributes(transaction.OracleResponseT); len(attrs) != 0 {
   328  			delete(mp.oracleResp, attrs[0].Value.(*transaction.OracleResponse).ID)
   329  		}
   330  		if mp.subscriptionsOn.Load() {
   331  			mp.events <- mempoolevent.Event{
   332  				Type: mempoolevent.TransactionRemoved,
   333  				Tx:   itm.txn,
   334  				Data: itm.data,
   335  			}
   336  		}
   337  	}
   338  	if mp.updateMetricsCb != nil {
   339  		mp.updateMetricsCb(len(mp.verifiedTxes))
   340  	}
   341  }
   342  
   343  // RemoveStale filters verified transactions through the given function keeping
   344  // only the transactions for which it returns true result. It's used to quickly
   345  // drop a part of the mempool that is now invalid after the block acceptance.
   346  func (mp *Pool) RemoveStale(isOK func(*transaction.Transaction) bool, feer Feer) {
   347  	mp.lock.Lock()
   348  	policyChanged := mp.loadPolicy(feer)
   349  	// We can reuse already allocated slice
   350  	// because items are iterated one-by-one in increasing order.
   351  	newVerifiedTxes := mp.verifiedTxes[:0]
   352  	mp.fees = make(map[util.Uint160]utilityBalanceAndFees) // it'd be nice to reuse existing map, but we can't easily clear it
   353  	mp.conflicts = make(map[util.Uint256][]util.Uint256)
   354  	height := feer.BlockHeight()
   355  	var (
   356  		staleItems []item
   357  	)
   358  	for _, itm := range mp.verifiedTxes {
   359  		if isOK(itm.txn) && mp.checkPolicy(itm.txn, policyChanged) && mp.tryAddSendersFee(itm.txn, feer, true) {
   360  			newVerifiedTxes = append(newVerifiedTxes, itm)
   361  			for _, attr := range itm.txn.GetAttributes(transaction.ConflictsT) {
   362  				hash := attr.Value.(*transaction.Conflicts).Hash
   363  				mp.conflicts[hash] = append(mp.conflicts[hash], itm.txn.Hash())
   364  			}
   365  			if mp.resendThreshold != 0 {
   366  				// item is resent at resendThreshold, 2*resendThreshold, 4*resendThreshold ...
   367  				// so quotient must be a power of two.
   368  				diff := (height - itm.blockStamp)
   369  				if diff%mp.resendThreshold == 0 && bits.OnesCount32(diff/mp.resendThreshold) == 1 {
   370  					staleItems = append(staleItems, itm)
   371  				}
   372  			}
   373  		} else {
   374  			delete(mp.verifiedMap, itm.txn.Hash())
   375  			if attrs := itm.txn.GetAttributes(transaction.OracleResponseT); len(attrs) != 0 {
   376  				delete(mp.oracleResp, attrs[0].Value.(*transaction.OracleResponse).ID)
   377  			}
   378  			if mp.subscriptionsOn.Load() {
   379  				mp.events <- mempoolevent.Event{
   380  					Type: mempoolevent.TransactionRemoved,
   381  					Tx:   itm.txn,
   382  					Data: itm.data,
   383  				}
   384  			}
   385  		}
   386  	}
   387  	if len(staleItems) != 0 {
   388  		go mp.resendStaleItems(staleItems)
   389  	}
   390  	mp.verifiedTxes = newVerifiedTxes
   391  	mp.lock.Unlock()
   392  }
   393  
   394  // loadPolicy updates feePerByte field and returns whether the policy has been
   395  // changed.
   396  func (mp *Pool) loadPolicy(feer Feer) bool {
   397  	newFeePerByte := feer.FeePerByte()
   398  	if newFeePerByte > mp.feePerByte {
   399  		mp.feePerByte = newFeePerByte
   400  		return true
   401  	}
   402  	return false
   403  }
   404  
   405  // checkPolicy checks whether the transaction fits the policy.
   406  func (mp *Pool) checkPolicy(tx *transaction.Transaction, policyChanged bool) bool {
   407  	if !policyChanged || tx.FeePerByte() >= mp.feePerByte {
   408  		return true
   409  	}
   410  	return false
   411  }
   412  
   413  // New returns a new Pool struct.
   414  func New(capacity int, payerIndex int, enableSubscriptions bool, updateMetricsCb func(int)) *Pool {
   415  	mp := &Pool{
   416  		verifiedMap:          make(map[util.Uint256]*transaction.Transaction, capacity),
   417  		verifiedTxes:         make([]item, 0, capacity),
   418  		capacity:             capacity,
   419  		payerIndex:           payerIndex,
   420  		fees:                 make(map[util.Uint160]utilityBalanceAndFees),
   421  		conflicts:            make(map[util.Uint256][]util.Uint256),
   422  		oracleResp:           make(map[uint64]util.Uint256),
   423  		subscriptionsEnabled: enableSubscriptions,
   424  		stopCh:               make(chan struct{}),
   425  		events:               make(chan mempoolevent.Event),
   426  		subCh:                make(chan chan<- mempoolevent.Event),
   427  		unsubCh:              make(chan chan<- mempoolevent.Event),
   428  		updateMetricsCb:      updateMetricsCb,
   429  	}
   430  	mp.subscriptionsOn.Store(false)
   431  	return mp
   432  }
   433  
   434  // SetResendThreshold sets a threshold after which the transaction will be considered stale
   435  // and returned for retransmission by `GetStaleTransactions`.
   436  func (mp *Pool) SetResendThreshold(h uint32, f func(*transaction.Transaction, any)) {
   437  	mp.lock.Lock()
   438  	defer mp.lock.Unlock()
   439  	mp.resendThreshold = h
   440  	mp.resendFunc = f
   441  }
   442  
   443  func (mp *Pool) resendStaleItems(items []item) {
   444  	for i := range items {
   445  		mp.resendFunc(items[i].txn, items[i].data)
   446  	}
   447  }
   448  
   449  // TryGetValue returns a transaction and its fee if it exists in the memory pool.
   450  func (mp *Pool) TryGetValue(hash util.Uint256) (*transaction.Transaction, bool) {
   451  	mp.lock.RLock()
   452  	defer mp.lock.RUnlock()
   453  	if tx, ok := mp.verifiedMap[hash]; ok {
   454  		return tx, ok
   455  	}
   456  
   457  	return nil, false
   458  }
   459  
   460  // TryGetData returns data associated with the specified transaction if it exists in the memory pool.
   461  func (mp *Pool) TryGetData(hash util.Uint256) (any, bool) {
   462  	mp.lock.RLock()
   463  	defer mp.lock.RUnlock()
   464  	if tx, ok := mp.verifiedMap[hash]; ok {
   465  		itm := item{txn: tx}
   466  		n := sort.Search(len(mp.verifiedTxes), func(n int) bool {
   467  			return itm.CompareTo(mp.verifiedTxes[n]) >= 0
   468  		})
   469  		if n < len(mp.verifiedTxes) {
   470  			for i := n; i < len(mp.verifiedTxes); i++ { // items may have equal priority, so `n` is the left bound of the items which are as prioritized as the desired `itm`.
   471  				if mp.verifiedTxes[i].txn.Hash() == hash {
   472  					return mp.verifiedTxes[i].data, ok
   473  				}
   474  				if itm.CompareTo(mp.verifiedTxes[i]) != 0 {
   475  					break
   476  				}
   477  			}
   478  		}
   479  	}
   480  
   481  	return nil, false
   482  }
   483  
   484  // GetVerifiedTransactions returns a slice of transactions with their fees.
   485  func (mp *Pool) GetVerifiedTransactions() []*transaction.Transaction {
   486  	mp.lock.RLock()
   487  	defer mp.lock.RUnlock()
   488  
   489  	var t = make([]*transaction.Transaction, len(mp.verifiedTxes))
   490  
   491  	for i := range mp.verifiedTxes {
   492  		t[i] = mp.verifiedTxes[i].txn
   493  	}
   494  
   495  	return t
   496  }
   497  
   498  // checkTxConflicts is an internal unprotected version of Verify. It takes into
   499  // consideration conflicting transactions which are about to be removed from mempool.
   500  func (mp *Pool) checkTxConflicts(tx *transaction.Transaction, fee Feer) ([]*transaction.Transaction, error) {
   501  	payer := tx.Signers[mp.payerIndex].Account
   502  	actualSenderFee, ok := mp.fees[payer]
   503  	if !ok {
   504  		actualSenderFee.balance.SetFromBig(fee.GetUtilityTokenBalance(payer))
   505  	}
   506  
   507  	var expectedSenderFee utilityBalanceAndFees
   508  	// Check Conflicts attributes.
   509  	var (
   510  		conflictsToBeRemoved []*transaction.Transaction
   511  		conflictingFee       int64
   512  	)
   513  	// Step 1: check if `tx` was in attributes of mempooled transactions.
   514  	if conflictingHashes, ok := mp.conflicts[tx.Hash()]; ok {
   515  		for _, hash := range conflictingHashes {
   516  			existingTx := mp.verifiedMap[hash]
   517  			if existingTx.HasSigner(payer) {
   518  				conflictingFee += existingTx.NetworkFee
   519  			}
   520  			conflictsToBeRemoved = append(conflictsToBeRemoved, existingTx)
   521  		}
   522  	}
   523  	// Step 2: check if mempooled transactions were in `tx`'s attributes.
   524  	conflictsAttrs := tx.GetAttributes(transaction.ConflictsT)
   525  	if len(conflictsAttrs) != 0 {
   526  		txSigners := make(map[util.Uint160]struct{}, len(tx.Signers))
   527  		for _, s := range tx.Signers {
   528  			txSigners[s.Account] = struct{}{}
   529  		}
   530  		for _, attr := range conflictsAttrs {
   531  			hash := attr.Value.(*transaction.Conflicts).Hash
   532  			existingTx, ok := mp.verifiedMap[hash]
   533  			if !ok {
   534  				continue
   535  			}
   536  			var signerOK bool
   537  			for _, s := range existingTx.Signers {
   538  				if _, ok := txSigners[s.Account]; ok {
   539  					signerOK = true
   540  					break
   541  				}
   542  			}
   543  			if !signerOK {
   544  				return nil, fmt.Errorf("%w: not signed by a signer of conflicting transaction %s", ErrConflictsAttribute, existingTx.Hash().StringBE())
   545  			}
   546  			conflictingFee += existingTx.NetworkFee
   547  			conflictsToBeRemoved = append(conflictsToBeRemoved, existingTx)
   548  		}
   549  	}
   550  	if conflictingFee != 0 && tx.NetworkFee <= conflictingFee {
   551  		return nil, fmt.Errorf("%w: conflicting transactions have bigger or equal network fee: %d vs %d", ErrConflictsAttribute, tx.NetworkFee, conflictingFee)
   552  	}
   553  	// Step 3: take into account sender's conflicting transactions before balance check.
   554  	expectedSenderFee = actualSenderFee
   555  	for _, conflictingTx := range conflictsToBeRemoved {
   556  		if conflictingTx.Signers[mp.payerIndex].Account.Equals(payer) {
   557  			expectedSenderFee.feeSum.SubUint64(&expectedSenderFee.feeSum, uint64(conflictingTx.SystemFee+conflictingTx.NetworkFee))
   558  		}
   559  	}
   560  	_, err := checkBalance(tx, expectedSenderFee)
   561  	return conflictsToBeRemoved, err
   562  }
   563  
   564  // Verify checks if the Sender of the tx is able to pay for it (and all the other
   565  // transactions in the pool). If yes, the transaction tx is a valid
   566  // transaction and the function returns true. If no, the transaction tx is
   567  // considered to be invalid, the function returns false.
   568  func (mp *Pool) Verify(tx *transaction.Transaction, feer Feer) bool {
   569  	mp.lock.RLock()
   570  	defer mp.lock.RUnlock()
   571  	_, err := mp.checkTxConflicts(tx, feer)
   572  	return err == nil
   573  }
   574  
   575  // removeConflictsOf removes the hash of the given transaction from the conflicts list
   576  // for each Conflicts attribute.
   577  func (mp *Pool) removeConflictsOf(tx *transaction.Transaction) {
   578  	// remove all conflicting hashes from mp.conflicts list
   579  	for _, attr := range tx.GetAttributes(transaction.ConflictsT) {
   580  		conflictsHash := attr.Value.(*transaction.Conflicts).Hash
   581  		if len(mp.conflicts[conflictsHash]) == 1 {
   582  			delete(mp.conflicts, conflictsHash)
   583  			continue
   584  		}
   585  		for i, existingHash := range mp.conflicts[conflictsHash] {
   586  			if existingHash == tx.Hash() {
   587  				// tx.Hash can occur in the conflicting hashes array only once, because we can't add the same transaction to the mempol twice
   588  				mp.conflicts[conflictsHash] = append(mp.conflicts[conflictsHash][:i], mp.conflicts[conflictsHash][i+1:]...)
   589  				break
   590  			}
   591  		}
   592  	}
   593  }
   594  
   595  // IterateVerifiedTransactions iterates through verified transactions and invokes
   596  // function `cont`. Iterations continue while the function `cont` returns true.
   597  // Function `cont` is executed within a read-locked memory pool,
   598  // thus IterateVerifiedTransactions will block any write mempool operation,
   599  // use it with care. Do not modify transaction or data via `cont`.
   600  func (mp *Pool) IterateVerifiedTransactions(cont func(tx *transaction.Transaction, data any) bool) {
   601  	mp.lock.RLock()
   602  	defer mp.lock.RUnlock()
   603  
   604  	for i := range mp.verifiedTxes {
   605  		if !cont(mp.verifiedTxes[i].txn, mp.verifiedTxes[i].data) {
   606  			return
   607  		}
   608  	}
   609  }