git.gammaspectra.live/P2Pool/consensus/v3@v3.8.0/p2pool/mempool/mempool.go (about) 1 package mempool 2 3 import ( 4 "git.gammaspectra.live/P2Pool/consensus/v3/types" 5 "git.gammaspectra.live/P2Pool/consensus/v3/utils" 6 "lukechampine.com/uint128" 7 "math" 8 "math/bits" 9 "slices" 10 "time" 11 ) 12 13 type Entry struct { 14 Id types.Hash `json:"id"` 15 BlobSize uint64 `json:"blob_size"` 16 Weight uint64 `json:"weight"` 17 Fee uint64 `json:"fee"` 18 TimeReceived time.Time `json:"-"` 19 } 20 21 type Mempool []*Entry 22 23 func (m Mempool) Sort() { 24 // Sort all transactions by fee per byte (highest to lowest) 25 26 slices.SortFunc(m, func(a, b *Entry) int { 27 return a.Compare(b) 28 }) 29 } 30 31 func (m Mempool) WeightAndFees() (weight, fees uint64) { 32 for _, e := range m { 33 weight += e.Weight 34 fees += e.Fee 35 } 36 return 37 } 38 39 func (m Mempool) Fees() (r uint64) { 40 for _, e := range m { 41 r += e.Fee 42 } 43 return r 44 } 45 46 func (m Mempool) Weight() (r uint64) { 47 for _, e := range m { 48 r += e.Weight 49 } 50 return r 51 } 52 53 // Pick Selects transactions semi-optimally 54 // 55 // Picking all transactions will result in the base reward penalty 56 // Use a heuristic algorithm to pick transactions and get the maximum possible reward 57 // Testing has shown that this algorithm is very close to the optimal selection 58 // Usually no more than 0.5 micronero away from the optimal discrete knapsack solution 59 // Sometimes it even finds the optimal solution 60 func (m Mempool) Pick(baseReward, minerTxWeight, medianWeight uint64) Mempool { 61 // Sort all transactions by fee per byte (highest to lowest) 62 m.Sort() 63 64 finalReward := baseReward 65 finalFees := uint64(0) 66 finalWeight := minerTxWeight 67 68 mempoolTxsOrder2 := make(Mempool, 0, len(m)) 69 70 for i, tx := range m { 71 k := -1 72 73 reward := GetBlockReward(baseReward, medianWeight, finalFees+tx.Fee, finalWeight+tx.Weight) 74 if reward > finalReward { 75 // If simply adding this transaction increases the reward, remember it 76 finalReward = reward 77 k = i 78 } 79 80 // Try replacing other transactions when we are above the limit 81 if finalWeight+tx.Weight > medianWeight { 82 // Don't check more than 100 transactions deep because they have higher and higher fee/byte 83 n := len(mempoolTxsOrder2) 84 for j, j1 := n-1, max(0, n-100); j >= j1; j-- { 85 prevTx := mempoolTxsOrder2[j] 86 reward2 := GetBlockReward(baseReward, medianWeight, finalFees+tx.Fee-prevTx.Fee, finalWeight+tx.Weight-prevTx.Weight) 87 if reward2 > finalReward { 88 // If replacing some other transaction increases the reward even more, remember it 89 // And keep trying to replace other transactions 90 finalReward = reward2 91 k = j 92 } 93 } 94 } 95 96 if k == i { 97 // Simply adding this tx improves the reward 98 mempoolTxsOrder2 = append(mempoolTxsOrder2, tx) 99 finalFees += tx.Fee 100 finalWeight += tx.Weight 101 } else if k >= 0 { 102 // Replacing another tx with this tx improves the reward 103 prevTx := mempoolTxsOrder2[k] 104 mempoolTxsOrder2[k] = tx 105 finalFees += tx.Fee - prevTx.Fee 106 finalWeight += tx.Weight - prevTx.Weight 107 } 108 } 109 110 return mempoolTxsOrder2 111 } 112 113 func (m Mempool) perfectSumRecursion(c chan Mempool, targetFee uint64, i int, currentSum uint64, top *int, m2 Mempool) { 114 if currentSum == targetFee { 115 c <- slices.Clone(m2) 116 return 117 } 118 119 if currentSum < targetFee && i < len(m) { 120 if top != nil && *top < i { 121 *top = i 122 utils.Logf("Mempool", "index %d/%d", i, len(m)) 123 } 124 m3 := append(m2, m[i]) 125 m.perfectSumRecursion(c, targetFee, i+1, currentSum+m[i].Fee, nil, m3) 126 m.perfectSumRecursion(c, targetFee, i+1, currentSum, top, m2) 127 } 128 } 129 130 func (m Mempool) PerfectSum(targetFee uint64) chan Mempool { 131 mempoolTxsOrder2 := make(Mempool, 0, len(m)) 132 c := make(chan Mempool) 133 go func() { 134 defer close(c) 135 var i int 136 m.perfectSumRecursion(c, targetFee, 0, 0, &i, mempoolTxsOrder2) 137 }() 138 return c 139 } 140 141 // Compare returns -1 if self is preferred over o, 0 if equal, 1 if o is preferred over self 142 func (t *Entry) Compare(o *Entry) int { 143 a := t.Fee * o.Weight 144 b := o.Fee * t.Weight 145 146 // Prefer transactions with higher fee/byte 147 if a > b { 148 return -1 149 } 150 if a < b { 151 return 1 152 } 153 154 // If fee/byte is the same, prefer smaller transactions (they give smaller penalty when going above the median block size limit) 155 if t.Weight < o.Weight { 156 return -1 157 } 158 if t.Weight > o.Weight { 159 return 1 160 } 161 162 // If two transactions have exactly the same fee and weight, just order them by id 163 return t.Id.Compare(o.Id) 164 } 165 166 // GetBlockReward Faster and limited version of block.GetBlockReward 167 func GetBlockReward(baseReward, medianWeight, fees, weight uint64) uint64 { 168 if weight <= medianWeight { 169 return baseReward + fees 170 } 171 if weight > medianWeight*2 { 172 return 0 173 } 174 175 hi, lo := bits.Mul64(baseReward, (medianWeight*2-weight)*weight) 176 177 if medianWeight >= math.MaxUint32 { 178 // slow path for medianWeight overflow 179 //panic("overflow") 180 return uint128.New(lo, hi).Div64(medianWeight).Div64(medianWeight).Lo 181 } 182 183 // This will overflow if medianWeight >= 2^32 184 // Performance of this code is more important 185 reward, _ := bits.Div64(hi, lo, medianWeight*medianWeight) 186 187 return reward + fees 188 }