git.gammaspectra.live/P2Pool/consensus@v0.0.0-20240403173234-a039820b20c9/p2pool/mempool/mempool.go (about)

     1  package mempool
     2  
     3  import (
     4  	"git.gammaspectra.live/P2Pool/consensus/types"
     5  	"git.gammaspectra.live/P2Pool/consensus/utils"
     6  	"git.gammaspectra.live/P2Pool/go-monero/pkg/rpc/daemon"
     7  	"lukechampine.com/uint128"
     8  	"slices"
     9  )
    10  
    11  type MempoolEntry struct {
    12  	Id       types.Hash
    13  	BlobSize uint64
    14  	Weight   uint64
    15  	Fee      uint64
    16  }
    17  
    18  type Mempool []*MempoolEntry
    19  
    20  func (m Mempool) Sort() {
    21  	// Sort all transactions by fee per byte (highest to lowest)
    22  
    23  	slices.SortFunc(m, func(a, b *MempoolEntry) int {
    24  		return a.Compare(b)
    25  	})
    26  }
    27  
    28  func (m Mempool) WeightAndFees() (weight, fees uint64) {
    29  	for _, e := range m {
    30  		weight += e.Weight
    31  		fees += e.Fee
    32  	}
    33  	return
    34  }
    35  
    36  func (m Mempool) Fees() (r uint64) {
    37  	for _, e := range m {
    38  		r += e.Fee
    39  	}
    40  	return r
    41  }
    42  
    43  func (m Mempool) Weight() (r uint64) {
    44  	for _, e := range m {
    45  		r += e.Weight
    46  	}
    47  	return r
    48  }
    49  
    50  func (m Mempool) Pick(baseReward, minerTxWeight, medianWeight uint64) Mempool {
    51  	// Sort all transactions by fee per byte (highest to lowest)
    52  	m.Sort()
    53  
    54  	finalReward := baseReward
    55  	finalFees := uint64(0)
    56  	finalWeight := minerTxWeight
    57  
    58  	m2 := make(Mempool, 0, len(m))
    59  
    60  	for i, tx := range m {
    61  		k := -1
    62  
    63  		reward := GetBlockReward(baseReward, medianWeight, finalFees+tx.Fee, finalWeight+tx.Weight)
    64  		if reward > finalReward {
    65  			// If simply adding this transaction increases the reward, remember it
    66  			finalReward = reward
    67  			k = 1
    68  		}
    69  
    70  		// Try replacing other transactions when we are above the limit
    71  		if finalWeight+tx.Weight > medianWeight {
    72  			// Don't check more than 100 transactions deep because they have higher and higher fee/byte
    73  			n := len(m2)
    74  			for j, j1 := n-1, max(0, n-100); j >= j1; j-- {
    75  				prevTx := m2[j]
    76  				reward2 := GetBlockReward(baseReward, medianWeight, finalFees+prevTx.Fee, finalWeight+prevTx.Weight)
    77  				if reward2 > finalReward {
    78  					// If replacing some other transaction increases the reward even more, remember it
    79  					// And keep trying to replace other transactions
    80  					finalReward = reward2
    81  					k = j
    82  				}
    83  			}
    84  		}
    85  
    86  		if k == i {
    87  			// Simply adding this tx improves the reward
    88  			m2 = append(m2, tx)
    89  			finalFees += tx.Fee
    90  			finalWeight += tx.Weight
    91  		} else if k >= 0 {
    92  			// Replacing another tx with this tx improves the reward
    93  			prevTx := m2[k]
    94  			m2[k] = tx
    95  			finalFees += tx.Fee - prevTx.Fee
    96  			finalWeight += tx.Weight - prevTx.Weight
    97  		}
    98  	}
    99  
   100  	return m2
   101  }
   102  
   103  func (m Mempool) perfectSumRecursion(c chan Mempool, targetFee uint64, i int, currentSum uint64, top *int, m2 Mempool) {
   104  	if currentSum == targetFee {
   105  		c <- slices.Clone(m2)
   106  		return
   107  	}
   108  
   109  	if currentSum < targetFee && i < len(m) {
   110  		if top != nil && *top < i {
   111  			*top = i
   112  			utils.Logf("Mempool", "index %d/%d", i, len(m))
   113  		}
   114  		m3 := append(m2, m[i])
   115  		m.perfectSumRecursion(c, targetFee, i+1, currentSum+m[i].Fee, nil, m3)
   116  		m.perfectSumRecursion(c, targetFee, i+1, currentSum, top, m2)
   117  	}
   118  }
   119  
   120  func (m Mempool) PerfectSum(targetFee uint64) chan Mempool {
   121  	m2 := make(Mempool, 0, len(m))
   122  	c := make(chan Mempool)
   123  	go func() {
   124  		defer close(c)
   125  		var i int
   126  		m.perfectSumRecursion(c, targetFee, 0, 0, &i, m2)
   127  	}()
   128  	return c
   129  }
   130  
   131  // Compare returns -1 if self is preferred over o, 0 if equal, 1 if o is preferred over self
   132  func (t *MempoolEntry) Compare(o *MempoolEntry) int {
   133  	a := t.Fee * o.Weight
   134  	b := o.Fee * t.Weight
   135  
   136  	// Prefer transactions with higher fee/byte
   137  	if a > b {
   138  		return -1
   139  	}
   140  	if a < b {
   141  		return 1
   142  	}
   143  
   144  	// If fee/byte is the same, prefer smaller transactions (they give smaller penalty when going above the median block size limit)
   145  	if t.Weight < o.Weight {
   146  		return -1
   147  	}
   148  	if t.Weight > o.Weight {
   149  		return 1
   150  	}
   151  
   152  	// If two transactions have exactly the same fee and weight, just order them by id
   153  	return t.Id.Compare(o.Id)
   154  }
   155  
   156  func GetBlockReward(baseReward, medianWeight, fees, weight uint64) uint64 {
   157  	if weight <= medianWeight {
   158  		return baseReward + fees
   159  	}
   160  	if weight > medianWeight*2 {
   161  		return 0
   162  	}
   163  
   164  	reward := uint128.From64(baseReward).Mul64(medianWeight*2 - weight).Div64(medianWeight).Div64(medianWeight)
   165  	return reward.Lo + fees
   166  }
   167  
   168  func isRctBulletproof(t int) bool {
   169  	switch t {
   170  	case 3, 4, 5: // RCTTypeBulletproof, RCTTypeBulletproof2, RCTTypeCLSAG:
   171  		return true
   172  	default:
   173  		return false
   174  	}
   175  }
   176  
   177  func isRctBulletproofPlus(t int) bool {
   178  	switch t {
   179  	case 6: // RCTTypeBulletproofPlus:
   180  		return true
   181  	default:
   182  		return false
   183  	}
   184  }
   185  
   186  func NewEntryFromRPCData(id types.Hash, buf []byte, json *daemon.TransactionJSON) *MempoolEntry {
   187  	isBulletproof := isRctBulletproof(json.RctSignatures.Type)
   188  	isBulletproofPlus := isRctBulletproofPlus(json.RctSignatures.Type)
   189  
   190  	var weight, paddedOutputs, bpBase, bpSize, bpClawback uint64
   191  	if !isBulletproof && !isBulletproofPlus {
   192  		weight = uint64(len(buf))
   193  	} else if isBulletproofPlus {
   194  		for _, proof := range json.RctsigPrunable.Bpp {
   195  			LSize := len(proof.L) / 2
   196  			n2 := uint64(1 << (LSize - 6))
   197  			if n2 == 0 {
   198  				paddedOutputs = 0
   199  				break
   200  			}
   201  			paddedOutputs += n2
   202  		}
   203  		{
   204  
   205  			bpBase = uint64(32*6+7*2) / 2
   206  
   207  			//get_transaction_weight_clawback
   208  			if len(json.RctSignatures.Outpk) <= 2 {
   209  				bpClawback = 0
   210  			} else {
   211  				nlr := 0
   212  				for (1 << nlr) < paddedOutputs {
   213  					nlr++
   214  				}
   215  				nlr += 6
   216  
   217  				bpSize = uint64(32*6 + 2*nlr)
   218  
   219  				bpClawback = (bpBase*paddedOutputs - bpSize) * 4 / 5
   220  			}
   221  		}
   222  
   223  		weight = uint64(len(buf)) + bpClawback
   224  	} else {
   225  		for _, proof := range json.RctsigPrunable.Bp {
   226  			LSize := len(proof.L) / 2
   227  			n2 := uint64(1 << (LSize - 6))
   228  			if n2 == 0 {
   229  				paddedOutputs = 0
   230  				break
   231  			}
   232  			paddedOutputs += n2
   233  		}
   234  		{
   235  
   236  			bpBase = uint64(32*9+7*2) / 2
   237  
   238  			//get_transaction_weight_clawback
   239  			if len(json.RctSignatures.Outpk) <= 2 {
   240  				bpClawback = 0
   241  			} else {
   242  				nlr := 0
   243  				for (1 << nlr) < paddedOutputs {
   244  					nlr++
   245  				}
   246  				nlr += 6
   247  
   248  				bpSize = uint64(32*9 + 2*nlr)
   249  
   250  				bpClawback = (bpBase*paddedOutputs - bpSize) * 4 / 5
   251  			}
   252  		}
   253  
   254  		weight = uint64(len(buf)) + bpClawback
   255  	}
   256  
   257  	return &MempoolEntry{
   258  		Id:       id,
   259  		BlobSize: uint64(len(buf)),
   260  		Weight:   weight,
   261  		Fee:      json.RctSignatures.Txnfee,
   262  	}
   263  }