diff --git a/payment.go b/payment.go index 9e86aaf..6e057fa 100644 --- a/payment.go +++ b/payment.go @@ -38,10 +38,16 @@ func (r *regolancer) invalidateInvoice(amount int64) { delete(r.invoiceCache, amount) } -func (r *regolancer) pay(ctx context.Context, amount int64, minAmount int64, +func (r *regolancer) pay(ctx context.Context, amount int64, minAmount int64, maxFeeMsat int64, route *lnrpc.Route, probeSteps int) error { fmt.Println() defer fmt.Println() + + if route.TotalFeesMsat > maxFeeMsat { + log.Printf("fee on the route exceeds our limits: %s ppm (max fee %s ppm)", formatFeePPM(amount*1000, route.TotalFeesMsat), formatFeePPM(amount*1000, maxFeeMsat)) + return fmt.Errorf("fee-limit exceeded") + } + invoice, err := r.createInvoice(ctx, amount) if err != nil { log.Printf("Error creating invoice: %s", err) @@ -64,6 +70,7 @@ func (r *regolancer) pay(ctx context.Context, amount int64, minAmount int64, Route: route, }) if err != nil { + logErrorF("error sending payment %s", err) return err } if result.Status == lnrpc.HTLCAttempt_FAILED { @@ -79,6 +86,7 @@ func (r *regolancer) pay(ctx context.Context, amount int64, minAmount int64, return fmt.Errorf("error: %s @ %d", result.Failure.Code.String(), result.Failure.FailureSourceIndex) } + prevHop := route.Hops[result.Failure.FailureSourceIndex-1] failedHop := route.Hops[result.Failure.FailureSourceIndex] nodeCtx, cancel := context.WithTimeout(ctx, time.Second*time.Duration(params.TimeoutInfo)) @@ -98,6 +106,20 @@ func (r *regolancer) pay(ctx context.Context, amount int64, minAmount int64, node2name = node2.Node.Alias } log.Printf("%s %s ⇒ %s", faintWhiteColor(result.Failure.Code.String()), cyanColor(node1name), cyanColor(node2name)) + + if result.Failure.Code == lnrpc.Failure_FEE_INSUFFICIENT || result.Failure.Code == lnrpc.Failure_INCORRECT_CLTV_EXPIRY { + failedHop := route.Hops[result.Failure.FailureSourceIndex-1] + route, err = r.rebuildRoute(ctx, route, amount) + updatedHop := route.Hops[result.Failure.FailureSourceIndex-1] + if err == nil { + // compare hops to make sure we do not loop endlessly + if !compareHops(failedHop, updatedHop) { + log.Printf("received channelupdate after failure, trying again with amt %s and fee %s ppm", + hiWhiteColor(amount), formatFeePPM(amount*1000, route.TotalFeesMsat)) + return r.pay(ctx, amount, minAmount, maxFeeMsat, route, probeSteps) + } + } + } if result.Failure.Code == lnrpc.Failure_TEMPORARY_CHANNEL_FAILURE { r.addFailedChan(node1.Node.PubKey, node2.Node.PubKey, prevHop. AmtToForwardMsat) diff --git a/rebalancer.go b/rebalancer.go index 9a862c5..02aff15 100644 --- a/rebalancer.go +++ b/rebalancer.go @@ -28,7 +28,7 @@ func (r *regolancer) tryRebalance(ctx context.Context, attempt *int) (err error, } routeCtx, routeCtxCancel := context.WithTimeout(attemptCtx, time.Second*time.Duration(params.TimeoutRoute)) defer routeCtxCancel() - routes, feeMsat, err := r.getRoutes(routeCtx, from, to, amt*1000) + routes, maxFeeMsat, err := r.getRoutes(routeCtx, from, to, amt*1000) if err != nil { if routeCtx.Err() == context.DeadlineExceeded { log.Print(errColor("Timed out looking for a route")) @@ -40,9 +40,9 @@ func (r *regolancer) tryRebalance(ctx context.Context, attempt *int) (err error, routeCtxCancel() for _, route := range routes { log.Printf("Attempt %s, amount: %s (max fee: %s sat | %s ppm )", - hiWhiteColorF("#%d", *attempt), hiWhiteColor(amt), formatFee(feeMsat), formatFeePPM(amt*1000, feeMsat)) + hiWhiteColorF("#%d", *attempt), hiWhiteColor(amt), formatFee(maxFeeMsat), formatFeePPM(amt*1000, maxFeeMsat)) r.printRoute(attemptCtx, route) - err = r.pay(attemptCtx, amt, params.MinAmount, route, params.ProbeSteps) + err = r.pay(attemptCtx, amt, params.MinAmount, maxFeeMsat, route, params.ProbeSteps) if err == nil { if params.AllowRapidRebalance { @@ -65,7 +65,7 @@ func (r *regolancer) tryRebalance(ctx context.Context, attempt *int) (err error, if err != nil { log.Printf("Error rebuilding the route for probed payment: %s", errColor(err)) } else { - err = r.pay(ctx, amt, 0, probedRoute, 0) + err = r.pay(ctx, amt, 0, maxFeeMsat, probedRoute, 0) if err == nil { return nil, false } else { @@ -74,6 +74,7 @@ func (r *regolancer) tryRebalance(ctx context.Context, attempt *int) (err error, } } } + *attempt++ } attemptCancel() @@ -206,7 +207,15 @@ func (r *regolancer) tryRapidRebalance(ctx context.Context, route *lnrpc.Route) defer attemptCancel() - err = r.pay(attemptCtx, amtLocal, params.MinAmount, routeLocal, 0) + // make sure we account for fees when increasing the rebalance amount + maxFeeMsat, _, err := r.calcFeeMsat(ctx, from, to, amtLocal*1000) + + if err != nil { + log.Printf(errColor("Error calculating fee: %s"), err) + return result, err + } + + err = r.pay(attemptCtx, amtLocal, params.MinAmount, maxFeeMsat, routeLocal, 0) attemptCancel() diff --git a/routes.go b/routes.go index 4719b38..a2df0b0 100644 --- a/routes.go +++ b/routes.go @@ -320,3 +320,9 @@ func getTarget(route *lnrpc.Route) uint64 { return 0 } + +func compareHops(hop1 *lnrpc.Hop, hop2 *lnrpc.Hop) bool { + return hop1.ChanId == hop2.ChanId && + hop1.FeeMsat == hop2.FeeMsat && + hop1.Expiry == hop2.Expiry +}