Add route probing

pull/1/head
rkfg 2 years ago
parent 6a5e0885da
commit 2040013708

@ -24,6 +24,7 @@ var params struct {
Perc int64 `short:"p" long:"perc" description:"use this value as both pfrom and pto from above" default:"0"`
Amount int64 `short:"a" long:"amount" description:"amount to rebalance" default:"0"`
EconRatio float64 `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)" default:"1"`
ProbeSteps int `short:"b" long:"probe" description:"if the payment fails at the last hop try to probe lower amount using binary search" default:"0"`
}
type regolancer struct {
@ -92,10 +93,28 @@ func main() {
log.Printf("Attempt %s, amount: %s (max fee: %s)", hiWhiteColorF("#%d", attempt),
hiWhiteColor(amt), hiWhiteColor(fee/1000))
r.printRoute(route)
err = r.pay(invoice, amt, route)
err = r.pay(invoice, amt, route, params.ProbeSteps)
if err == nil {
return
}
if retryErr, ok := err.(ErrRetry); ok {
amt = retryErr.amount
log.Printf("Trying to rebalance again with %s", hiWhiteColor(amt))
probedInvoice, err := r.createInvoice(from, to, amt)
if err != nil {
log.Fatal("Error creating invoice: ", err)
}
if err != nil {
log.Printf("Error rebuilding the route for probed payment: %s", errColor(err))
} else {
err = r.pay(probedInvoice, amt, retryErr.route, 0)
if err == nil {
return
} else {
log.Printf("Probed rebalance failed with error: %s", errColor(err))
}
}
}
attempt++
}
}

@ -10,13 +10,24 @@ import (
"github.com/lightningnetwork/lnd/lnrpc/routerrpc"
)
type ErrRetry struct {
amount int64
route *lnrpc.Route
}
func (e ErrRetry) Error() string {
return fmt.Sprintf("retry payment with %d sats", e.amount)
}
var ErrProbeFailed = fmt.Errorf("probe failed")
func (r *regolancer) createInvoice(from, to uint64, amount int64) (*lnrpc.AddInvoiceResponse, error) {
return r.lnClient.AddInvoice(context.Background(), &lnrpc.Invoice{Value: amount,
Memo: fmt.Sprintf("Rebalance %d ⇒ %d", from, to),
Expiry: int64(time.Hour.Seconds() * 24)})
}
func (r *regolancer) pay(invoice *lnrpc.AddInvoiceResponse, amount int64, route *lnrpc.Route) error {
func (r *regolancer) pay(invoice *lnrpc.AddInvoiceResponse, amount int64, route *lnrpc.Route, probeSteps int) error {
lastHop := route.Hops[len(route.Hops)-1]
lastHop.MppRecord = &lnrpc.MPPRecord{
PaymentAddr: invoice.PaymentAddr,
@ -26,7 +37,6 @@ func (r *regolancer) pay(invoice *lnrpc.AddInvoiceResponse, amount int64, route
&routerrpc.SendToRouteRequest{
PaymentHash: invoice.RHash,
Route: route,
SkipTempErr: true,
})
if err != nil {
return err
@ -48,6 +58,17 @@ func (r *regolancer) pay(invoice *lnrpc.AddInvoiceResponse, amount int64, route
}
fmt.Printf("\n%s %s ⇒ %s\n\n", faintWhiteColor(result.Failure.Code.String()),
cyanColor(node1name), cyanColor(node2name))
if int(result.Failure.FailureSourceIndex) == len(route.Hops)-2 && probeSteps > 0 {
fmt.Println("Probing route...")
maxAmount, goodRoute, err := r.probeRoute(route, 0, amount, amount/2, probeSteps)
if err != nil {
return err
}
if maxAmount == 0 {
return ErrProbeFailed
}
return ErrRetry{amount: maxAmount, route: goodRoute}
}
return fmt.Errorf("error: %s @ %d", result.Failure.Code.String(), result.Failure.FailureSourceIndex)
} else {
log.Printf("Success! Paid %s in fees", hiWhiteColor(result.Route.TotalFeesMsat/1000))

@ -4,9 +4,12 @@ import (
"context"
"encoding/hex"
"fmt"
"log"
"math/rand"
"time"
"github.com/lightningnetwork/lnd/lnrpc"
"github.com/lightningnetwork/lnd/lnrpc/routerrpc"
)
func calcFeeMsat(amtMsat int64, policy *lnrpc.RoutingPolicy) float64 {
@ -73,16 +76,16 @@ func (r *regolancer) printRoute(route *lnrpc.Route) {
return
}
errs := ""
fmt.Printf("%s %s\n", faintWhiteColor("Total fee:"), hiWhiteColor((route.TotalFeesMsat-route.Hops[0].FeeMsat)/1000))
fmt.Printf("%s %s\n", faintWhiteColor("Total fee:"), hiWhiteColor(route.TotalFeesMsat/1000))
for i, hop := range route.Hops {
nodeInfo, err := r.getNodeInfo(hop.PubKey)
if err != nil {
errs = errs + err.Error() + "\n"
continue
}
fee := hiWhiteColorF("%-6d", hop.FeeMsat)
if i == 0 {
fee = hiWhiteColorF("%-6s", "")
fee := hiWhiteColorF("%-6s", "")
if i > 0 {
hiWhiteColorF("%-6d", route.Hops[i-1].FeeMsat)
}
fmt.Printf("%s %s %s\n", faintWhiteColor(hop.ChanId), fee, cyanColor(nodeInfo.Node.Alias))
}
@ -90,3 +93,70 @@ func (r *regolancer) printRoute(route *lnrpc.Route) {
fmt.Println(errColor(errs))
}
}
func (r *regolancer) rebuildRoute(route *lnrpc.Route, amount int64) (*lnrpc.Route, error) {
pks := [][]byte{}
for _, h := range route.Hops {
pk, _ := hex.DecodeString(h.PubKey)
pks = append(pks, pk)
}
resultRoute, err := r.routerClient.BuildRoute(context.Background(), &routerrpc.BuildRouteRequest{
AmtMsat: amount * 1000,
OutgoingChanId: route.Hops[0].ChanId,
HopPubkeys: pks,
FinalCltvDelta: 144,
})
return resultRoute.Route, err
}
func (r *regolancer) probeRoute(route *lnrpc.Route, goodAmount, badAmount, amount int64, steps int) (maxAmount int64,
goodRoute *lnrpc.Route, err error) {
goodRoute, err = r.rebuildRoute(route, amount)
if err != nil {
return 0, nil, err
}
fakeHash := make([]byte, 32)
rand.Read(fakeHash)
result, err := r.routerClient.SendToRouteV2(context.Background(),
&routerrpc.SendToRouteRequest{
PaymentHash: fakeHash,
Route: goodRoute,
})
if err != nil {
return
}
if result.Status == lnrpc.HTLCAttempt_SUCCEEDED {
return 0, nil, fmt.Errorf("this should never happen")
}
if result.Status == lnrpc.HTLCAttempt_FAILED {
if result.Failure.Code == lnrpc.Failure_INCORRECT_OR_UNKNOWN_PAYMENT_DETAILS { // payment can succeed
if steps == 1 || amount == badAmount {
log.Printf("%s is the best amount", hiWhiteColor(amount))
return amount, goodRoute, nil
}
nextAmount := amount + (badAmount-amount)/2
log.Printf("%s is good enough, trying amount %s, %s steps left",
hiWhiteColor(amount), hiWhiteColor(nextAmount), hiWhiteColor(steps-1))
return r.probeRoute(route, amount, badAmount, nextAmount, steps-1)
}
if result.Failure.Code == lnrpc.Failure_TEMPORARY_CHANNEL_FAILURE {
if steps == 1 {
bestAmount := hiWhiteColor(goodAmount)
if goodAmount == 0 {
bestAmount = hiWhiteColor("unknown")
}
log.Printf("%s is too much, best amount is %s", hiWhiteColor(amount), bestAmount)
return goodAmount, goodRoute, nil
}
nextAmount := amount + (goodAmount-amount)/2
log.Printf("%s is too much, lowering amount to %s, %s steps left",
hiWhiteColor(amount), hiWhiteColor(nextAmount), hiWhiteColor(steps-1))
return r.probeRoute(route, goodAmount, amount, nextAmount, steps-1)
}
if result.Failure.Code == lnrpc.Failure_FEE_INSUFFICIENT {
log.Printf("Fee insufficient, retrying...")
return r.probeRoute(route, goodAmount, badAmount, amount, steps)
}
}
return 0, nil, fmt.Errorf("unknown error: %+v", result)
}

Loading…
Cancel
Save