Fix accelerator revenue impact always showing zero
The old approach compared coinbasevalue from two getblocktemplate calls, but new mempool arrivals between calls masked the displacement loss. Now uses a single template snapshot to find the marginal (lowest fee-rate) transaction that would be displaced and reports its fee as the revenue cost.
This commit is contained in:
@@ -43,6 +43,54 @@ type AccelerateResult struct {
|
||||
FeeLostError string `json:"fee_lost_error,omitempty"`
|
||||
}
|
||||
|
||||
// marginalFeeLost estimates the fee revenue lost by inserting a boosted
|
||||
// transaction into the template. It finds the lowest fee-rate transaction
|
||||
// in the current template (the one that would be displaced) and returns
|
||||
// the difference: displaced_fee - boosted_tx_real_fee. If the boosted tx
|
||||
// is already in the template or the template has room, returns 0.
|
||||
func marginalFeeLost(tpl *bitcoind.BlockTemplate, txid string, txVsize int64) int64 {
|
||||
if len(tpl.Transactions) == 0 {
|
||||
return 0
|
||||
}
|
||||
|
||||
// Check if the tx is already in the template (nothing displaced).
|
||||
for _, tx := range tpl.Transactions {
|
||||
if tx.Txid == txid {
|
||||
return 0
|
||||
}
|
||||
}
|
||||
|
||||
// Find the marginal transaction: lowest fee-rate in the template.
|
||||
var marginal *bitcoind.TemplateTx
|
||||
var marginalRate float64 = math.MaxFloat64
|
||||
for i := range tpl.Transactions {
|
||||
tx := &tpl.Transactions[i]
|
||||
// Weight to vsize: ceil(weight/4)
|
||||
vsize := (tx.Weight + 3) / 4
|
||||
if vsize <= 0 {
|
||||
continue
|
||||
}
|
||||
rate := float64(tx.Fee) / float64(vsize)
|
||||
if rate < marginalRate {
|
||||
marginalRate = rate
|
||||
marginal = tx
|
||||
}
|
||||
}
|
||||
|
||||
if marginal == nil {
|
||||
return 0
|
||||
}
|
||||
|
||||
// The displaced tx's fee is the revenue we lose. We don't add the
|
||||
// boosted tx's real fee back because the pool never had it — the tx
|
||||
// wasn't in the template before the boost.
|
||||
lost := marginal.Fee
|
||||
if lost < 0 {
|
||||
lost = 0
|
||||
}
|
||||
return lost
|
||||
}
|
||||
|
||||
// Accelerate boosts a transaction to the target feerate (sat/vB).
|
||||
func (s *Service) Accelerate(ctx context.Context, txid string, targetFeerateVB float64) (*AccelerateResult, error) {
|
||||
if targetFeerateVB > MaxFeerateVB {
|
||||
@@ -68,38 +116,26 @@ func (s *Service) Accelerate(ctx context.Context, txid string, targetFeerateVB f
|
||||
targetFeerateVB, modifiedFeeSats/float64(entry.Vsize))
|
||||
}
|
||||
|
||||
// Snapshot coinbasevalue BEFORE the boost to measure actual impact.
|
||||
var coinbaseBefore int64
|
||||
// Estimate revenue impact from the pre-boost template. The boosted tx
|
||||
// will displace the marginal (lowest fee-rate) transaction in the
|
||||
// template. We compute this from the single template snapshot to avoid
|
||||
// a race with new mempool arrivals between two getblocktemplate calls.
|
||||
var feeLost int64
|
||||
var tplErr string
|
||||
tplBefore, err := s.RPC.GetBlockTemplate(ctx)
|
||||
tpl, err := s.RPC.GetBlockTemplate(ctx)
|
||||
if err != nil {
|
||||
tplErr = fmt.Sprintf("getblocktemplate (before): %s", bitcoind.RPCErrorMessage(err))
|
||||
s.Log.Warn("accelerate: getblocktemplate failed (before)", "err", err)
|
||||
tplErr = fmt.Sprintf("getblocktemplate: %s", bitcoind.RPCErrorMessage(err))
|
||||
s.Log.Warn("accelerate: getblocktemplate failed", "err", err)
|
||||
} else {
|
||||
coinbaseBefore = tplBefore.CoinbaseValue
|
||||
feeLost = marginalFeeLost(tpl, txid, entry.Vsize)
|
||||
s.Log.Info("accelerate: fee impact estimated",
|
||||
"marginal_fee_lost", feeLost, "boosted_tx_fee", int64(baseFeeSats))
|
||||
}
|
||||
|
||||
if err := s.RPC.PrioritiseTransaction(ctx, txid, delta); err != nil {
|
||||
return nil, fmt.Errorf("prioritisetransaction: %w", err)
|
||||
}
|
||||
|
||||
// Measure coinbasevalue AFTER — the difference is the real fee lost.
|
||||
var feeLost int64
|
||||
if coinbaseBefore > 0 {
|
||||
tplAfter, err := s.RPC.GetBlockTemplate(ctx)
|
||||
if err != nil {
|
||||
tplErr = fmt.Sprintf("getblocktemplate (after): %s", bitcoind.RPCErrorMessage(err))
|
||||
s.Log.Warn("accelerate: getblocktemplate failed (after)", "err", err)
|
||||
} else {
|
||||
feeLost = coinbaseBefore - tplAfter.CoinbaseValue
|
||||
if feeLost < 0 {
|
||||
feeLost = 0 // template improved (new tx arrived between calls)
|
||||
}
|
||||
s.Log.Info("accelerate: fee impact measured",
|
||||
"before", coinbaseBefore, "after", tplAfter.CoinbaseValue, "lost", feeLost)
|
||||
}
|
||||
}
|
||||
|
||||
rec := store.BoostedTx{
|
||||
Txid: txid,
|
||||
OriginalFeerate: currentFeerate,
|
||||
|
||||
Reference in New Issue
Block a user