Add lost profits accounting, fixes #2

pull/11/head
rkfg 2 years ago
parent 974e938313
commit 4a45b7a23c

@ -11,6 +11,7 @@
"pfrom": 10, "pfrom": 10,
"pto": 30, "pto": 30,
"stat": "stats.csv", "stat": "stats.csv",
"lost_profit": true,
"exclude_channels_in": [ "exclude_channels_in": [
821913529170526209, 821913529170526209,
821280210377179136 821280210377179136

@ -10,6 +10,7 @@ macaroon_filename = "admin.macaroon"
"pfrom"= 40 "pfrom"= 40
"pto"= 40 "pto"= 40
"stat"= "stats.csv" "stat"= "stats.csv"
lost_profit = true
"exclude_channels_in"= [ "exclude_channels_in"= [
# River Financial # River Financial
821913529170526209, 821913529170526209,

@ -32,6 +32,7 @@ var params struct {
Perc int64 `short:"p" long:"perc" description:"use this value as both pfrom and pto from above" json:"perc"` Perc int64 `short:"p" long:"perc" description:"use this value as both pfrom and pto from above" json:"perc"`
Amount int64 `short:"a" long:"amount" description:"amount to rebalance" json:"amount"` Amount int64 `short:"a" long:"amount" description:"amount to rebalance" json:"amount"`
EconRatio float64 `short:"r" long:"econ-ratio" description:"economical ratio for fee limit calculation as a multiple of target channel fee (for example, 0.5 means you want to pay at max half the fee you might earn for routing out of the target channel)" json:"econ_ratio" toml:"econ_ratio"` EconRatio float64 `short:"r" long:"econ-ratio" description:"economical ratio for fee limit calculation as a multiple of target channel fee (for example, 0.5 means you want to pay at max half the fee you might earn for routing out of the target channel)" json:"econ_ratio" toml:"econ_ratio"`
LostProfit bool `short:"l" long:"lost-profit" description:"also consider the outbound channel fees when looking for profitable routes so that outbound_fee+inbound_fee < route_fee" json:"lost_profit" toml:"lost_profit"`
ProbeSteps int `short:"b" long:"probe-steps" description:"if the payment fails at the last hop try to probe lower amount using this many steps" json:"probe_steps" toml:"probe_steps"` ProbeSteps int `short:"b" long:"probe-steps" description:"if the payment fails at the last hop try to probe lower amount using this many steps" json:"probe_steps" toml:"probe_steps"`
MinAmount int64 `long:"min-amount" description:"if probing is enabled this will be the minimum amount to try" json:"min_amount" toml:"min_amount"` MinAmount int64 `long:"min-amount" description:"if probing is enabled this will be the minimum amount to try" json:"min_amount" toml:"min_amount"`
ExcludeChannelsIn []uint64 `short:"i" long:"exclude-channel-in" description:"don't use this channel as incoming (can be specified multiple times)" json:"exclude_channels_in" toml:"exclude_channels_in"` ExcludeChannelsIn []uint64 `short:"i" long:"exclude-channel-in" description:"don't use this channel as incoming (can be specified multiple times)" json:"exclude_channels_in" toml:"exclude_channels_in"`
@ -104,7 +105,7 @@ func tryRebalance(ctx context.Context, r *regolancer, invoice **lnrpc.AddInvoice
} }
routeCtx, routeCtxCancel := context.WithTimeout(ctx, time.Second*30) routeCtx, routeCtxCancel := context.WithTimeout(ctx, time.Second*30)
defer routeCtxCancel() defer routeCtxCancel()
routes, fee, err := r.getRoutes(routeCtx, from, to, amt*1000, params.EconRatio) routes, fee, err := r.getRoutes(routeCtx, from, to, amt*1000)
if err != nil { if err != nil {
if routeCtx.Err() == context.DeadlineExceeded { if routeCtx.Err() == context.DeadlineExceeded {
log.Print(errColor("Timed out looking for a route")) log.Print(errColor("Timed out looking for a route"))

@ -27,8 +27,8 @@ func (r *regolancer) createInvoice(ctx context.Context, from, to uint64, amount
Expiry: int64(time.Hour.Seconds() * 24)}) Expiry: int64(time.Hour.Seconds() * 24)})
} }
func (r *regolancer) pay(ctx context.Context, invoice *lnrpc.AddInvoiceResponse, amount int64, func (r *regolancer) pay(ctx context.Context, invoice *lnrpc.AddInvoiceResponse,
minAmount int64, route *lnrpc.Route, probeSteps int) error { amount int64, minAmount int64, route *lnrpc.Route, probeSteps int) error {
fmt.Println() fmt.Println()
defer fmt.Println() defer fmt.Println()
lastHop := route.Hops[len(route.Hops)-1] lastHop := route.Hops[len(route.Hops)-1]
@ -84,7 +84,8 @@ func (r *regolancer) pay(ctx context.Context, invoice *lnrpc.AddInvoiceResponse,
min = -minAmount - 1 min = -minAmount - 1
start = minAmount start = minAmount
} }
maxAmount, err := r.probeRoute(ctx, route, min, amount, start, probeSteps, params.EconRatio) maxAmount, err := r.probeRoute(ctx, route, min, amount, start,
probeSteps)
if err != nil { if err != nil {
return err return err
} }

@ -28,26 +28,41 @@ func (r *regolancer) getChanInfo(ctx context.Context, chanId uint64) (*lnrpc.Cha
return c, nil return c, nil
} }
func (r *regolancer) calcFeeMsat(ctx context.Context, to uint64, amtMsat int64, ratio float64) (feeMsat int64, func (r *regolancer) calcFeeMsat(ctx context.Context, from, to uint64, amtMsat int64, ratio float64) (feeMsat int64,
lastPKstr string, err error) { lastPKstr string, err error) {
c, err := r.getChanInfo(ctx, to) cTo, err := r.getChanInfo(ctx, to)
if err != nil { if err != nil {
return 0, "", err return 0, "", err
} }
lastPKstr = c.Node1Pub lastPKstr = cTo.Node1Pub
policy := c.Node2Policy policyTo := cTo.Node2Policy
if lastPKstr == r.myPK { if lastPKstr == r.myPK {
lastPKstr = c.Node2Pub lastPKstr = cTo.Node2Pub
policy = c.Node1Policy policyTo = cTo.Node1Policy
} }
feeMsat = int64(float64(policy.FeeBaseMsat+amtMsat*policy.FeeRateMilliMsat) * ratio / 1e6) lostProfitMsat := int64(0)
if params.LostProfit {
cFrom, err := r.getChanInfo(ctx, from)
if err != nil {
return 0, "", err
}
policyFrom := cFrom.Node1Policy
if cFrom.Node2Pub == r.myPK {
policyFrom = cFrom.Node2Policy
}
lostProfitMsat = int64(float64(policyFrom.FeeBaseMsat+
amtMsat*policyFrom.FeeRateMilliMsat) / 1e6)
}
feeMsat = int64(float64(policyTo.FeeBaseMsat+amtMsat*
policyTo.FeeRateMilliMsat)*ratio/1e6) - lostProfitMsat
return return
} }
func (r *regolancer) getRoutes(ctx context.Context, from, to uint64, amtMsat int64, ratio float64) ([]*lnrpc.Route, int64, error) { func (r *regolancer) getRoutes(ctx context.Context, from, to uint64, amtMsat int64) ([]*lnrpc.Route, int64, error) {
routeCtx, cancel := context.WithTimeout(ctx, time.Second*30) routeCtx, cancel := context.WithTimeout(ctx, time.Second*30)
defer cancel() defer cancel()
feeMsat, lastPKstr, err := r.calcFeeMsat(routeCtx, to, amtMsat, ratio) feeMsat, lastPKstr, err := r.calcFeeMsat(routeCtx, from, to, amtMsat,
params.EconRatio)
if err != nil { if err != nil {
return nil, 0, err return nil, 0, err
} }
@ -145,8 +160,8 @@ func (r *regolancer) rebuildRoute(ctx context.Context, route *lnrpc.Route, amoun
return resultRoute.Route, err return resultRoute.Route, err
} }
func (r *regolancer) probeRoute(ctx context.Context, route *lnrpc.Route, goodAmount, badAmount, amount int64, func (r *regolancer) probeRoute(ctx context.Context, route *lnrpc.Route,
steps int, ratio float64) (maxAmount int64, err error) { goodAmount, badAmount, amount int64, steps int) (maxAmount int64, err error) {
if amount == badAmount || amount == goodAmount || amount == -goodAmount { if amount == badAmount || amount == goodAmount || amount == -goodAmount {
bestAmount := hiWhiteColor(goodAmount) bestAmount := hiWhiteColor(goodAmount)
if goodAmount <= 0 { if goodAmount <= 0 {
@ -160,18 +175,21 @@ func (r *regolancer) probeRoute(ctx context.Context, route *lnrpc.Route, goodAmo
if err != nil { if err != nil {
return 0, err return 0, err
} }
maxFeeMsat, _, err := r.calcFeeMsat(ctx, probedRoute.Hops[len(probedRoute.Hops)-1].ChanId, amount*1000, ratio) maxFeeMsat, _, err := r.calcFeeMsat(ctx, probedRoute.Hops[0].ChanId,
probedRoute.Hops[len(probedRoute.Hops)-1].ChanId, amount*1000,
params.EconRatio)
if err != nil { if err != nil {
return 0, err return 0, err
} }
if probedRoute.TotalFeesMsat > maxFeeMsat { if probedRoute.TotalFeesMsat > maxFeeMsat {
nextAmount := amount + (badAmount-amount)/2 nextAmount := amount + (badAmount-amount)/2
log.Printf("%s requires too high fee %s (max allowed is %s), increasing amount to %s", log.Printf("%s requires too high fee %s (max allowed is %s), increasing amount to %s",
hiWhiteColor(amount), hiWhiteColor(probedRoute.TotalFeesMsat/1000), hiWhiteColor(maxFeeMsat/1000), hiWhiteColor(amount), hiWhiteColor(probedRoute.TotalFeesMsat/1000),
hiWhiteColor(nextAmount)) hiWhiteColor(maxFeeMsat/1000), hiWhiteColor(nextAmount))
// returning negative amount as "good", it's a special case which means this is // returning negative amount as "good", it's a special case which means
// rather the lower bound and the actual good amount is still unknown // this is rather the lower bound and the actual good amount is still
return r.probeRoute(ctx, route, -amount, badAmount, nextAmount, steps, ratio) // unknown
return r.probeRoute(ctx, route, -amount, badAmount, nextAmount, steps)
} }
fakeHash := make([]byte, 32) fakeHash := make([]byte, 32)
rand.Read(fakeHash) rand.Read(fakeHash)
@ -194,8 +212,10 @@ func (r *regolancer) probeRoute(ctx context.Context, route *lnrpc.Route, goodAmo
} }
nextAmount := amount + (badAmount-amount)/2 nextAmount := amount + (badAmount-amount)/2
log.Printf("%s is good enough, trying amount %s, %s steps left", log.Printf("%s is good enough, trying amount %s, %s steps left",
hiWhiteColor(amount), hiWhiteColor(nextAmount), hiWhiteColor(steps-1)) hiWhiteColor(amount), hiWhiteColor(nextAmount),
return r.probeRoute(ctx, route, amount, badAmount, nextAmount, steps-1, ratio) hiWhiteColor(steps-1))
return r.probeRoute(ctx, route, amount, badAmount, nextAmount,
steps-1)
} }
if result.Failure.Code == lnrpc.Failure_TEMPORARY_CHANNEL_FAILURE { if result.Failure.Code == lnrpc.Failure_TEMPORARY_CHANNEL_FAILURE {
if steps == 1 { if steps == 1 {
@ -204,7 +224,8 @@ func (r *regolancer) probeRoute(ctx context.Context, route *lnrpc.Route, goodAmo
bestAmount = hiWhiteColor("unknown") bestAmount = hiWhiteColor("unknown")
goodAmount = 0 goodAmount = 0
} }
log.Printf("%s is too much, best amount is %s", hiWhiteColor(amount), bestAmount) log.Printf("%s is too much, best amount is %s",
hiWhiteColor(amount), bestAmount)
return goodAmount, nil return goodAmount, nil
} }
var nextAmount int64 var nextAmount int64
@ -214,12 +235,15 @@ func (r *regolancer) probeRoute(ctx context.Context, route *lnrpc.Route, goodAmo
nextAmount = amount - (goodAmount+amount)/2 nextAmount = amount - (goodAmount+amount)/2
} }
log.Printf("%s is too much, lowering amount to %s, %s steps left", log.Printf("%s is too much, lowering amount to %s, %s steps left",
hiWhiteColor(amount), hiWhiteColor(nextAmount), hiWhiteColor(steps-1)) hiWhiteColor(amount), hiWhiteColor(nextAmount),
return r.probeRoute(ctx, route, goodAmount, amount, nextAmount, steps-1, ratio) hiWhiteColor(steps-1))
return r.probeRoute(ctx, route, goodAmount, amount, nextAmount,
steps-1)
} }
if result.Failure.Code == lnrpc.Failure_FEE_INSUFFICIENT { if result.Failure.Code == lnrpc.Failure_FEE_INSUFFICIENT {
log.Printf("Fee insufficient, retrying...") log.Printf("Fee insufficient, retrying...")
return r.probeRoute(ctx, route, goodAmount, badAmount, amount, steps, ratio) return r.probeRoute(ctx, route, goodAmount, badAmount, amount,
steps)
} }
} }
return 0, fmt.Errorf("unknown error: %+v", result) return 0, fmt.Errorf("unknown error: %+v", result)

Loading…
Cancel
Save