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  }