2022-08-20 14:11:45 +00:00
|
|
|
package main
|
|
|
|
|
|
|
|
import (
|
|
|
|
"context"
|
|
|
|
"fmt"
|
|
|
|
"log"
|
|
|
|
"time"
|
|
|
|
|
|
|
|
"github.com/lightningnetwork/lnd/lnrpc"
|
|
|
|
"github.com/lightningnetwork/lnd/lnrpc/routerrpc"
|
|
|
|
)
|
|
|
|
|
2022-08-20 17:02:25 +00:00
|
|
|
type ErrRetry struct {
|
|
|
|
amount int64
|
|
|
|
}
|
|
|
|
|
|
|
|
func (e ErrRetry) Error() string {
|
|
|
|
return fmt.Sprintf("retry payment with %d sats", e.amount)
|
|
|
|
}
|
|
|
|
|
|
|
|
var ErrProbeFailed = fmt.Errorf("probe failed")
|
|
|
|
|
2022-08-20 19:19:48 +00:00
|
|
|
func (r *regolancer) createInvoice(ctx context.Context, from, to uint64, amount int64) (*lnrpc.AddInvoiceResponse, error) {
|
|
|
|
return r.lnClient.AddInvoice(ctx, &lnrpc.Invoice{Value: amount,
|
2022-08-20 14:11:45 +00:00
|
|
|
Memo: fmt.Sprintf("Rebalance %d ⇒ %d", from, to),
|
|
|
|
Expiry: int64(time.Hour.Seconds() * 24)})
|
|
|
|
}
|
|
|
|
|
2022-08-20 19:19:48 +00:00
|
|
|
func (r *regolancer) pay(ctx context.Context, invoice *lnrpc.AddInvoiceResponse, amount int64, route *lnrpc.Route, probeSteps int) error {
|
2022-08-20 14:11:45 +00:00
|
|
|
lastHop := route.Hops[len(route.Hops)-1]
|
|
|
|
lastHop.MppRecord = &lnrpc.MPPRecord{
|
|
|
|
PaymentAddr: invoice.PaymentAddr,
|
|
|
|
TotalAmtMsat: amount * 1000,
|
|
|
|
}
|
2022-08-20 19:19:48 +00:00
|
|
|
result, err := r.routerClient.SendToRouteV2(ctx,
|
2022-08-20 14:11:45 +00:00
|
|
|
&routerrpc.SendToRouteRequest{
|
|
|
|
PaymentHash: invoice.RHash,
|
|
|
|
Route: route,
|
|
|
|
})
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
if result.Status == lnrpc.HTLCAttempt_FAILED {
|
2022-08-20 19:19:48 +00:00
|
|
|
nodeCtx, cancel := context.WithTimeout(ctx, time.Minute)
|
|
|
|
defer cancel()
|
|
|
|
node1, err := r.getNodeInfo(nodeCtx, route.Hops[result.Failure.FailureSourceIndex-1].PubKey)
|
2022-08-20 14:11:45 +00:00
|
|
|
node1name := ""
|
|
|
|
node2name := ""
|
|
|
|
if err != nil {
|
|
|
|
node1name = fmt.Sprintf("node%d", result.Failure.FailureSourceIndex-1)
|
|
|
|
} else {
|
|
|
|
node1name = node1.Node.Alias
|
|
|
|
}
|
2022-08-20 19:19:48 +00:00
|
|
|
node2, err := r.getNodeInfo(nodeCtx, route.Hops[result.Failure.FailureSourceIndex].PubKey)
|
2022-08-20 14:11:45 +00:00
|
|
|
if err != nil {
|
|
|
|
node2name = fmt.Sprintf("node%d", result.Failure.FailureSourceIndex)
|
|
|
|
} else {
|
|
|
|
node2name = node2.Node.Alias
|
|
|
|
}
|
|
|
|
fmt.Printf("\n%s %s ⇒ %s\n\n", faintWhiteColor(result.Failure.Code.String()),
|
|
|
|
cyanColor(node1name), cyanColor(node2name))
|
2022-08-20 17:02:25 +00:00
|
|
|
if int(result.Failure.FailureSourceIndex) == len(route.Hops)-2 && probeSteps > 0 {
|
|
|
|
fmt.Println("Probing route...")
|
2022-08-20 22:15:39 +00:00
|
|
|
maxAmount, err := r.probeRoute(ctx, route, 0, amount, amount/2, probeSteps)
|
2022-08-20 17:02:25 +00:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
if maxAmount == 0 {
|
|
|
|
return ErrProbeFailed
|
|
|
|
}
|
2022-08-20 22:15:39 +00:00
|
|
|
return ErrRetry{amount: maxAmount}
|
2022-08-20 17:02:25 +00:00
|
|
|
}
|
2022-08-20 14:11:45 +00:00
|
|
|
return fmt.Errorf("error: %s @ %d", result.Failure.Code.String(), result.Failure.FailureSourceIndex)
|
|
|
|
} else {
|
2022-08-20 15:04:49 +00:00
|
|
|
log.Printf("Success! Paid %s in fees", hiWhiteColor(result.Route.TotalFeesMsat/1000))
|
2022-08-20 14:11:45 +00:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
}
|