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 }