github.com/piotrnar/gocoin@v0.0.0-20240512203912-faa0448c5e96/client/network/txpool_sort.go (about) 1 package network 2 3 import ( 4 "fmt" 5 "sort" 6 "time" 7 8 "github.com/piotrnar/gocoin/client/common" 9 "github.com/piotrnar/gocoin/lib/btc" 10 ) 11 12 var ( 13 expireTxsNow bool = true 14 lastTxsExpire time.Time 15 ) 16 17 // GetSortedMempool returns txs sorted by SPB, but with parents first. 18 func GetSortedMempool() (result []*OneTxToSend) { 19 all_txs := make([]BIDX, len(TransactionsToSend)) 20 var idx int 21 const MIN_PKB = 200 22 for k := range TransactionsToSend { 23 all_txs[idx] = k 24 idx++ 25 } 26 sort.Slice(all_txs, func(i, j int) bool { 27 rec_i := TransactionsToSend[all_txs[i]] 28 rec_j := TransactionsToSend[all_txs[j]] 29 rate_i := rec_i.Fee * uint64(rec_j.Weight()) 30 rate_j := rec_j.Fee * uint64(rec_i.Weight()) 31 if rate_i != rate_j { 32 return rate_i > rate_j 33 } 34 if rec_i.MemInputCnt != rec_j.MemInputCnt { 35 return rec_i.MemInputCnt < rec_j.MemInputCnt 36 } 37 for x := 0; x < 32; x++ { 38 if rec_i.Hash.Hash[x] != rec_i.Hash.Hash[x] { 39 return rec_i.Hash.Hash[x] < rec_i.Hash.Hash[x] 40 } 41 } 42 return false 43 }) 44 45 // now put the childrer after the parents 46 result = make([]*OneTxToSend, len(all_txs)) 47 already_in := make(map[BIDX]bool, len(all_txs)) 48 parent_of := make(map[BIDX][]BIDX) 49 50 idx = 0 51 52 var missing_parents = func(txkey BIDX, is_any bool) (res []BIDX, yes bool) { 53 tx := TransactionsToSend[txkey] 54 if tx.MemInputs == nil { 55 return 56 } 57 var cnt_ok int 58 for idx, inp := range tx.TxIn { 59 if tx.MemInputs[idx] { 60 txk := btc.BIdx(inp.Input.Hash[:]) 61 if _, ok := already_in[txk]; ok { 62 } else { 63 yes = true 64 if is_any { 65 return 66 } 67 res = append(res, txk) 68 } 69 70 cnt_ok++ 71 if cnt_ok == tx.MemInputCnt { 72 return 73 } 74 } 75 } 76 return 77 } 78 79 var append_txs func(txkey BIDX) 80 append_txs = func(txkey BIDX) { 81 result[idx] = TransactionsToSend[txkey] 82 idx++ 83 already_in[txkey] = true 84 85 if toretry, ok := parent_of[txkey]; ok { 86 for _, kv := range toretry { 87 if _, in := already_in[kv]; in { 88 continue 89 } 90 if _, yes := missing_parents(kv, true); !yes { 91 append_txs(kv) 92 } 93 } 94 delete(parent_of, txkey) 95 } 96 } 97 98 for _, txkey := range all_txs { 99 if missing, yes := missing_parents(txkey, false); yes { 100 for _, kv := range missing { 101 parent_of[kv] = append(parent_of[kv], txkey) 102 } 103 continue 104 } 105 append_txs(txkey) 106 } 107 108 if idx != len(result) || idx != len(already_in) || len(parent_of) != 0 { 109 fmt.Println("Get sorted mempool idx:", idx, " result:", len(result), " alreadyin:", len(already_in), " parents:", len(parent_of)) 110 fmt.Println("DUPA!!!!!!!!!!") 111 result = result[:idx] 112 } 113 114 return 115 } 116 117 // LimitPoolSize must be called with TxMutex locked. 118 func LimitPoolSize(maxlen uint64) { 119 ticklen := maxlen >> 5 // 1/32th of the max size = X 120 121 if TransactionsToSendSize < maxlen { 122 if TransactionsToSendSize < maxlen-2*ticklen { 123 if common.SetMinFeePerKB(0) { 124 var cnt uint64 125 for k, v := range TransactionsRejected { 126 if v.Reason == TX_REJECTED_LOW_FEE { 127 deleteRejected(k) 128 cnt++ 129 } 130 } 131 common.CounterMutex.Lock() 132 common.Count("TxPoolSizeLow") 133 common.CountAdd("TxRejectedFeeUndone", cnt) 134 common.CounterMutex.Unlock() 135 //fmt.Println("Mempool size low:", TransactionsToSendSize, maxlen, maxlen-2*ticklen, "-", cnt, "rejected purged") 136 } 137 } else { 138 common.CountSafe("TxPoolSizeOK") 139 //fmt.Println("Mempool size OK:", TransactionsToSendSize, maxlen, maxlen-2*ticklen) 140 } 141 return 142 } 143 144 //sta := time.Now() 145 146 sorted := GetSortedMempoolNew() 147 idx := len(sorted) 148 149 old_size := TransactionsToSendSize 150 151 maxlen -= ticklen 152 153 for idx > 0 && TransactionsToSendSize > maxlen { 154 idx-- 155 tx := sorted[idx] 156 if _, ok := TransactionsToSend[tx.Hash.BIdx()]; !ok { 157 // this has already been rmoved 158 continue 159 } 160 tx.Delete(true, TX_REJECTED_LOW_FEE) 161 } 162 163 if cnt := len(sorted) - idx; cnt > 0 { 164 newspkb := uint64(float64(1000*sorted[idx].Fee) / float64(sorted[idx].VSize())) 165 common.SetMinFeePerKB(newspkb) 166 167 /*fmt.Println("Mempool purged in", time.Now().Sub(sta).String(), "-", 168 old_size-TransactionsToSendSize, "/", old_size, "bytes and", cnt, "/", len(sorted), "txs removed. SPKB:", newspkb)*/ 169 common.CounterMutex.Lock() 170 common.Count("TxPoolSizeHigh") 171 common.CountAdd("TxPurgedSizCnt", uint64(cnt)) 172 common.CountAdd("TxPurgedSizBts", old_size-TransactionsToSendSize) 173 common.CounterMutex.Unlock() 174 } 175 } 176 177 func GetSortedRejected() (sorted []*OneTxRejected) { 178 var idx int 179 sorted = make([]*OneTxRejected, len(TransactionsRejected)) 180 for _, t := range TransactionsRejected { 181 sorted[idx] = t 182 idx++ 183 } 184 var now = time.Now() 185 sort.Slice(sorted, func(i, j int) bool { 186 return int64(sorted[i].Size)*int64(now.Sub(sorted[i].Time)) < int64(sorted[j].Size)*int64(now.Sub(sorted[j].Time)) 187 }) 188 return 189 } 190 191 // LimitRejectedSize must be called with TxMutex locked. 192 func LimitRejectedSize() { 193 //ticklen := maxlen >> 5 // 1/32th of the max size = X 194 var idx int 195 var sorted []*OneTxRejected 196 197 old_cnt := len(TransactionsRejected) 198 old_size := TransactionsRejectedSize 199 200 maxlen, maxcnt := common.RejectedTxsLimits() 201 202 if maxcnt > 0 && len(TransactionsRejected) > maxcnt { 203 common.CountSafe("TxRejectedCntHigh") 204 sorted = GetSortedRejected() 205 maxcnt -= maxcnt >> 5 206 for idx = maxcnt; idx < len(sorted); idx++ { 207 deleteRejected(sorted[idx].Id.BIdx()) 208 } 209 sorted = sorted[:maxcnt] 210 } 211 212 if maxlen > 0 && TransactionsRejectedSize > maxlen { 213 common.CountSafe("TxRejectedBtsHigh") 214 if sorted == nil { 215 sorted = GetSortedRejected() 216 } 217 maxlen -= maxlen >> 5 218 for idx = len(sorted) - 1; idx >= 0; idx-- { 219 deleteRejected(sorted[idx].Hash.BIdx()) 220 if TransactionsRejectedSize <= maxlen { 221 break 222 } 223 } 224 } 225 226 if old_cnt > len(TransactionsRejected) { 227 common.CounterMutex.Lock() 228 common.CountAdd("TxRejectedSizCnt", uint64(old_cnt-len(TransactionsRejected))) 229 common.CountAdd("TxRejectedSizBts", old_size-TransactionsRejectedSize) 230 if common.GetBool(&common.CFG.TXPool.Debug) { 231 println("Removed", uint64(old_cnt-len(TransactionsRejected)), "txs and", old_size-TransactionsRejectedSize, 232 "bytes from the rejected poool") 233 } 234 common.CounterMutex.Unlock() 235 } 236 } 237 238 /* --== Let's keep it here for now as it sometimes comes handy for debuging 239 240 var first_ = true 241 242 // call this one when TxMutex is locked 243 func MPC_locked() bool { 244 if first_ && MempoolCheck() { 245 first_ = false 246 _, file, line, _ := runtime.Caller(1) 247 println("=====================================================") 248 println("Mempool first iime seen broken from", file, line) 249 return true 250 } 251 return false 252 } 253 254 func MPC() (res bool) { 255 TxMutex.Lock() 256 res = MPC_locked() 257 TxMutex.Unlock() 258 return 259 } 260 */ 261 262 // MempoolCheck verifies the Mempool for consistency. 263 // Make sure to call it with TxMutex Locked. 264 func MempoolCheck() (dupa bool) { 265 var spent_cnt int 266 var totsize uint64 267 268 // First check if t2s.MemInputs fields are properly set 269 for _, t2s := range TransactionsToSend { 270 var micnt int 271 272 totsize += uint64(len(t2s.Raw)) 273 274 for i, inp := range t2s.TxIn { 275 spent_cnt++ 276 277 outk, ok := SpentOutputs[inp.Input.UIdx()] 278 if ok { 279 if outk != t2s.Hash.BIdx() { 280 fmt.Println("Tx", t2s.Hash.String(), "input", i, "has a mismatch in SpentOutputs record", outk) 281 dupa = true 282 } 283 } else { 284 fmt.Println("Tx", t2s.Hash.String(), "input", i, "is not in SpentOutputs") 285 dupa = true 286 } 287 288 _, ok = TransactionsToSend[btc.BIdx(inp.Input.Hash[:])] 289 290 if t2s.MemInputs == nil { 291 if ok { 292 fmt.Println("Tx", t2s.Hash.String(), "MemInputs==nil but input", i, "is in mempool", inp.Input.String()) 293 dupa = true 294 } 295 } else { 296 if t2s.MemInputs[i] { 297 micnt++ 298 if !ok { 299 fmt.Println("Tx", t2s.Hash.String(), "MemInput set but input", i, "NOT in mempool", inp.Input.String()) 300 dupa = true 301 } 302 } else { 303 if ok { 304 fmt.Println("Tx", t2s.Hash.String(), "MemInput NOT set but input", i, "IS in mempool", inp.Input.String()) 305 dupa = true 306 } 307 } 308 } 309 310 if _, ok := TransactionsToSend[btc.BIdx(inp.Input.Hash[:])]; !ok { 311 if unsp := common.BlockChain.Unspent.UnspentGet(&inp.Input); unsp == nil { 312 fmt.Println("Mempool tx", t2s.Hash.String(), "has no input", i) 313 dupa = true 314 } 315 } 316 } 317 if t2s.MemInputs != nil && micnt == 0 { 318 fmt.Println("Tx", t2s.Hash.String(), "has MemInputs array with all false values") 319 dupa = true 320 } 321 if t2s.MemInputCnt != micnt { 322 fmt.Println("Tx", t2s.Hash.String(), "has incorrect MemInputCnt", t2s.MemInputCnt, micnt) 323 dupa = true 324 } 325 } 326 327 if spent_cnt != len(SpentOutputs) { 328 fmt.Println("SpentOutputs length mismatch", spent_cnt, len(SpentOutputs)) 329 dupa = true 330 } 331 332 if totsize != TransactionsToSendSize { 333 fmt.Println("TransactionsToSendSize mismatch", totsize, TransactionsToSendSize) 334 dupa = true 335 } 336 337 totsize = 0 338 for _, tr := range TransactionsRejected { 339 totsize += uint64(tr.Size) 340 } 341 if totsize != TransactionsRejectedSize { 342 fmt.Println("TransactionsRejectedSize mismatch", totsize, TransactionsRejectedSize) 343 dupa = true 344 } 345 346 return 347 } 348 349 // GetChildren gets all first level children of the tx. 350 func (tx *OneTxToSend) GetChildren() (result []*OneTxToSend) { 351 var po btc.TxPrevOut 352 po.Hash = tx.Hash.Hash 353 354 res := make(map[*OneTxToSend]bool) 355 356 for po.Vout = 0; po.Vout < uint32(len(tx.TxOut)); po.Vout++ { 357 uidx := po.UIdx() 358 if val, ok := SpentOutputs[uidx]; ok { 359 res[TransactionsToSend[val]] = true 360 } 361 } 362 363 result = make([]*OneTxToSend, len(res)) 364 var idx int 365 for ttx := range res { 366 result[idx] = ttx 367 idx++ 368 } 369 return 370 } 371 372 // GetItWithAllChildren gets all the children (and all of their children...) of the tx. 373 // If any of the children has other unconfirmed parents, they are also included in the result. 374 // The result is sorted with the input parent first and always with parents before their children. 375 func (tx *OneTxToSend) GetItWithAllChildren() (result []*OneTxToSend) { 376 already_included := make(map[*OneTxToSend]bool) 377 378 result = []*OneTxToSend{tx} // out starting (parent) tx shall be the first element of the result 379 already_included[tx] = true 380 381 for idx := 0; idx < len(result); idx++ { 382 par := result[idx] 383 for _, ch := range par.GetChildren() { 384 // do it for each returned child, 385 386 // but only if it has not been included yet ... 387 if _, ok := already_included[ch]; !ok { 388 389 // first make sure we have all of its parents... 390 for _, prnt := range ch.GetAllParentsExcept(par) { 391 if _, ok := already_included[prnt]; !ok { 392 // if we dont have a parent, just insert it here into the result 393 result = append(result, prnt) 394 // ... and mark it as included, for later 395 already_included[prnt] = true 396 } 397 } 398 399 // now we can safely insert the child, as all its parent shall be already included 400 result = append(result, ch) 401 // ... and mark it as included, for later 402 already_included[ch] = true 403 } 404 } 405 } 406 return 407 } 408 409 // GetAllChildren gets all the children (and all of their children...) of the tx. 410 // The result is sorted by the oldest parent. 411 func (tx *OneTxToSend) GetAllChildren() (result []*OneTxToSend) { 412 already_included := make(map[*OneTxToSend]bool) 413 var idx int 414 par := tx 415 for { 416 chlds := par.GetChildren() 417 for _, ch := range chlds { 418 if _, ok := already_included[ch]; !ok { 419 already_included[ch] = true 420 result = append(result, ch) 421 } 422 } 423 if idx == len(result) { 424 break 425 } 426 427 par = result[idx] 428 already_included[par] = true 429 idx++ 430 } 431 return 432 } 433 434 // GetAllParents gets all the unconfirmed parents of the given tx. 435 // The result is sorted by the oldest parent. 436 func (tx *OneTxToSend) GetAllParents() (result []*OneTxToSend) { 437 already_in := make(map[*OneTxToSend]bool) 438 already_in[tx] = true 439 var do_one func(*OneTxToSend) 440 do_one = func(tx *OneTxToSend) { 441 if tx.MemInputCnt > 0 { 442 for idx := range tx.TxIn { 443 if tx.MemInputs[idx] { 444 par_tx := TransactionsToSend[btc.BIdx(tx.TxIn[idx].Input.Hash[:])] 445 if _, ok := already_in[par_tx]; !ok { 446 do_one(par_tx) 447 } 448 } 449 } 450 } 451 if _, ok := already_in[tx]; !ok { 452 result = append(result, tx) 453 already_in[tx] = true 454 } 455 } 456 do_one(tx) 457 return 458 } 459 460 // GetAllParents gets all the unconfirmed parents of the given tx, except for the input tx (and its parents). 461 // The result is sorted by the oldest parent. 462 func (tx *OneTxToSend) GetAllParentsExcept(except *OneTxToSend) (result []*OneTxToSend) { 463 already_in := make(map[*OneTxToSend]bool) 464 already_in[tx] = true 465 var do_one func(*OneTxToSend) 466 do_one = func(tx *OneTxToSend) { 467 if tx.MemInputCnt > 0 { 468 for idx := range tx.TxIn { 469 if tx.MemInputs[idx] { 470 if par_tx := TransactionsToSend[btc.BIdx(tx.TxIn[idx].Input.Hash[:])]; par_tx != except { 471 if _, ok := already_in[par_tx]; !ok { 472 do_one(par_tx) 473 } 474 } 475 } 476 } 477 } 478 if _, ok := already_in[tx]; !ok { 479 result = append(result, tx) 480 already_in[tx] = true 481 } 482 } 483 do_one(tx) 484 return 485 } 486 487 func (tx *OneTxToSend) SPW() float64 { 488 return float64(tx.Fee) / float64(tx.Weight()) 489 } 490 491 func (tx *OneTxToSend) SPB() float64 { 492 return tx.SPW() * 4.0 493 } 494 495 type OneTxsPackage struct { 496 Txs []*OneTxToSend 497 Weight int 498 Fee uint64 499 } 500 501 func (pk *OneTxsPackage) AnyIn(list map[*OneTxToSend]bool) (ok bool) { 502 for _, par := range pk.Txs { 503 if _, ok = list[par]; ok { 504 return 505 } 506 } 507 return 508 } 509 510 func LookForPackages(txs []*OneTxToSend) (result []*OneTxsPackage) { 511 for _, tx := range txs { 512 if tx.MemInputCnt > 0 { 513 continue 514 } 515 var pkg OneTxsPackage 516 pandch := tx.GetItWithAllChildren() 517 if len(pandch) > 1 { 518 pkg.Txs = pandch 519 for _, t := range pkg.Txs { 520 pkg.Weight += t.Weight() 521 pkg.Fee += t.Fee 522 } 523 result = append(result, &pkg) 524 } 525 } 526 sort.Slice(result, func(i, j int) bool { 527 return result[i].Fee*uint64(result[j].Weight) > result[j].Fee*uint64(result[i].Weight) 528 }) 529 return 530 } 531 532 /* This one uses the old method, which turned out to be very slow sometimes 533 func LookForPackages(txs []*OneTxToSend) (result []*OneTxsPackage) { 534 for _, tx := range txs { 535 if tx.MemInputCnt == 0 { 536 continue 537 } 538 var pkg OneTxsPackage 539 childs := tx.GetAllParents() 540 if len(childs) > 0 { 541 pkg.Txs = append(childs, tx) 542 for _, t := range pkg.Txs { 543 pkg.Weight += t.Weight() 544 pkg.Fee += t.Fee 545 } 546 result = append(result, &pkg) 547 } 548 } 549 sort.Slice(result, func(i, j int) bool { 550 return result[i].Fee*uint64(result[j].Weight) > result[j].Fee*uint64(result[i].Weight) 551 }) 552 return 553 } 554 */ 555 556 // GetSortedMempoolNew is like GetSortedMempool(), but one uses Child-Pays-For-Parent algo. 557 func GetSortedMempoolNew() (result []*OneTxToSend) { 558 txs := GetSortedMempool() 559 pkgs := LookForPackages(txs) 560 //println(len(pkgs), "pkgs from", len(txs), "txs") 561 562 result = make([]*OneTxToSend, len(txs)) 563 var txs_idx, pks_idx, res_idx int 564 already_in := make(map[*OneTxToSend]bool, len(txs)) 565 for txs_idx < len(txs) { 566 tx := txs[txs_idx] 567 568 if pks_idx < len(pkgs) { 569 pk := pkgs[pks_idx] 570 if pk.Fee*uint64(tx.Weight()) > tx.Fee*uint64(pk.Weight) { 571 pks_idx++ 572 if pk.AnyIn(already_in) { 573 continue 574 } 575 // all package's txs new: incude them all 576 copy(result[res_idx:], pk.Txs) 577 res_idx += len(pk.Txs) 578 for _, _t := range pk.Txs { 579 already_in[_t] = true 580 } 581 continue 582 } 583 } 584 585 txs_idx++ 586 if _, ok := already_in[tx]; ok { 587 continue 588 } 589 result[res_idx] = tx 590 already_in[tx] = true 591 res_idx++ 592 } 593 //println("All sorted. res_idx:", res_idx, " txs:", len(txs)) 594 return 595 } 596 597 // GetMempoolFees only takes tx/package weight and the fee. 598 func GetMempoolFees(maxweight uint64) (result [][2]uint64) { 599 txs := GetSortedMempool() 600 pkgs := LookForPackages(txs) 601 602 var txs_idx, pks_idx, res_idx int 603 var weightsofar uint64 604 result = make([][2]uint64, len(txs)) 605 already_in := make(map[*OneTxToSend]bool, len(txs)) 606 for txs_idx < len(txs) && weightsofar < maxweight { 607 tx := txs[txs_idx] 608 609 if pks_idx < len(pkgs) { 610 pk := pkgs[pks_idx] 611 if pk.Fee*uint64(tx.Weight()) > tx.Fee*uint64(pk.Weight) { 612 pks_idx++ 613 if pk.AnyIn(already_in) { 614 continue 615 } 616 617 result[res_idx] = [2]uint64{uint64(pk.Weight), pk.Fee} 618 res_idx++ 619 weightsofar += uint64(pk.Weight) 620 621 for _, _t := range pk.Txs { 622 already_in[_t] = true 623 } 624 continue 625 } 626 } 627 628 txs_idx++ 629 if _, ok := already_in[tx]; ok { 630 continue 631 } 632 result[res_idx] = [2]uint64{uint64(tx.Weight()), tx.Fee} 633 res_idx++ 634 weightsofar += uint64(tx.Weight()) 635 636 already_in[tx] = true 637 } 638 result = result[:res_idx] 639 return 640 } 641 642 func ExpireTxs() { 643 lastTxsExpire = time.Now() 644 expireTxsNow = false 645 646 TxMutex.Lock() 647 648 if maxpoolsize := common.MaxMempoolSize(); maxpoolsize != 0 { 649 LimitPoolSize(maxpoolsize) 650 } 651 652 LimitRejectedSize() 653 654 TxMutex.Unlock() 655 656 common.CountSafe("TxPurgedTicks") 657 }