Merge pull request #56 from ziggie1984/rapid-rebalance-strategy

master
rkfg 12 months ago committed by GitHub
commit 3467c7edbe
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -256,6 +256,7 @@ func preflightChecks(params *configParams) error {
if params.TimeoutRoute == 0 {
params.TimeoutRoute = 30
}
return nil
}

@ -19,7 +19,10 @@ func (e ErrRetry) Error() string {
return fmt.Sprintf("retry payment with %d sats", e.amount)
}
var ErrProbeFailed = fmt.Errorf("probe failed")
var (
ErrProbeFailed = fmt.Errorf("probe failed")
ErrFeeExceeded = fmt.Errorf("fee-limit exceeded")
)
func (r *regolancer) createInvoice(ctx context.Context, amount int64) (result *lnrpc.AddInvoiceResponse, err error) {
var ok bool
@ -45,7 +48,7 @@ func (r *regolancer) pay(ctx context.Context, amount int64, minAmount int64, max
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")
return ErrFeeExceeded
}
invoice, err := r.createInvoice(ctx, amount)

@ -3,6 +3,7 @@ package main
import (
"context"
"encoding/hex"
"errors"
"log"
"time"
@ -11,10 +12,16 @@ import (
type rebalanceResult struct {
successfulAttempts int
failedAttempts int
successfulAmt int64
paidFeeMsat int64
}
const (
increaseAmtRapidRebalance string = "increase"
decreaseAmtRapidRebalance string = "decrease"
)
func (r *regolancer) tryRebalance(ctx context.Context, attempt *int) (err error,
repeat bool) {
attemptCtx, attemptCancel := context.WithTimeout(ctx, time.Minute*time.Duration(params.TimeoutAttempt))
@ -48,10 +55,11 @@ func (r *regolancer) tryRebalance(ctx context.Context, attempt *int) (err error,
if params.AllowRapidRebalance {
rebalanceResult, _ := r.tryRapidRebalance(ctx, route)
if rebalanceResult.successfulAttempts > 0 {
log.Printf("%s rapid rebalances were successful, total amount: %s (fee: %s sat | %s ppm)\n",
if rebalanceResult.successfulAttempts > 0 || rebalanceResult.failedAttempts > 0 {
log.Printf("%s rapid rebalances were successful, total amount: %s (fee: %s sat | %s ppm) - Failed Attempts: %s\n",
hiWhiteColor(rebalanceResult.successfulAttempts), hiWhiteColor(rebalanceResult.successfulAmt),
formatFee(rebalanceResult.paidFeeMsat), formatFeePPM(rebalanceResult.successfulAmt*1000, rebalanceResult.paidFeeMsat))
formatFee(rebalanceResult.paidFeeMsat), formatFeePPM(rebalanceResult.successfulAmt*1000, rebalanceResult.paidFeeMsat),
hiWhiteColor(rebalanceResult.failedAttempts))
}
log.Printf("Finished rapid rebalancing")
}
@ -99,11 +107,15 @@ func (r *regolancer) tryRapidRebalance(ctx context.Context, route *lnrpc.Route)
amtLocal int64 = amt
accelerator int64 = 1
hittingTheWall bool
exitEarly bool
capReached bool
maxAmountOnRouteMsat uint64
minAmount uint64
rebalanceStrategy string = increaseAmtRapidRebalance
)
result.successfulAttempts = 0
result.failedAttempts = 0
// Include Initial Rebalance
result.successfulAmt = amt
result.paidFeeMsat = route.TotalFeesMsat
@ -113,32 +125,64 @@ func (r *regolancer) tryRapidRebalance(ctx context.Context, route *lnrpc.Route)
return result, err
}
if params.MinAmount > 0 {
minAmount = uint64(params.MinAmount)
} else {
minAmount = 10000
}
Loop:
for {
if hittingTheWall {
accelerator >>= 1
// In case we encounter that we are already constrained
// by the liquidity on the channels we are waiting for
// the accelerator to go below this amount to save
// already failed rebalances
if amtLocal < accelerator*amt && amtLocal > 0 {
continue
switch rebalanceStrategy {
case increaseAmtRapidRebalance:
if hittingTheWall {
accelerator >>= 1
// In case we encounter that we are already constrained
// by the liquidity on the channels we are waiting for
// the accelerator to go below this amount to save
// already failed rebalances
if amtLocal < accelerator*amt && amtLocal > int64(minAmount) {
continue
}
} else if !capReached {
// we only increase the amount if the max Amount on the
// route is still not reached
accelerator <<= 1
}
} else if !capReached {
// we only increase the amount if the max Amount on the
// route is still not reached
if uint64(accelerator*amt) < maxAmountOnRouteMsat/1000 {
amtLocal = accelerator * amt
} else if !capReached {
capReached = true
log.Printf("Max amount on route reached capping amount at %s sats "+
"| max amount on route (max htlc size) %s sats\n", infoColor(amtLocal), infoColor(maxAmountOnRouteMsat/1000))
}
// We reached the initial amount again.
// now we switch to the decreasing strategy.
// We half the amount on every step we go down.
if accelerator < 1 {
accelerator = 2
rebalanceStrategy = decreaseAmtRapidRebalance
amtLocal = amt / accelerator
if amtLocal < int64(minAmount) {
break Loop
}
}
case decreaseAmtRapidRebalance:
accelerator <<= 1
}
if amtLocal < amt/accelerator {
continue
}
if uint64(accelerator*amt) < maxAmountOnRouteMsat/1000 {
amtLocal = accelerator * amt
} else if !capReached {
capReached = true
log.Printf("Max amount on route reached capping amount at %s sats "+
"| max amount on route (max htlc size) %s sats\n", infoColor(amtLocal), infoColor(maxAmountOnRouteMsat/1000))
amtLocal = amt / accelerator
if amtLocal < int64(minAmount) {
break Loop
}
}
if accelerator < 1 {
break
if exitEarly {
break Loop
}
log.Printf("Rapid rebalance attempt %s, amount: %s\n", hiWhiteColor(result.successfulAttempts+1), hiWhiteColor(amtLocal))
@ -211,14 +255,29 @@ func (r *regolancer) tryRapidRebalance(ctx context.Context, route *lnrpc.Route)
return result, err
}
amtLocalTemp := amtLocal
_, _, amtLocal, err = r.pickChannelPair(amtLocal, params.MinAmount, params.RelAmountFrom, params.RelAmountTo)
if err != nil {
log.Printf(errColor("Error during picking channel: %s"), err)
return result, err
hittingTheWall = true
// We are not returning an error here because
// in we still could rebalance an amount in the
// decreasing strategy.
// return result, err
continue
}
log.Printf("Rapid fire starting with actual amount: %s (could be lower than the attempted amount in case there is less liquidity available on the channel)", hiWhiteColor(amtLocal))
if amtLocalTemp > amtLocal {
log.Printf("Rapid fire starting with actual amount: %s (could be lower than the attempted amount in case there is less liquidity available on the channel)", hiWhiteColor(amtLocal))
// We are already using maximum available liquidity so we can begin decreasing amounts again.
hittingTheWall = true
// This is needed so we do not test further amounts
// when in the decreasing strategy.
if rebalanceStrategy == decreaseAmtRapidRebalance {
exitEarly = true
}
}
routeLocal, err = r.rebuildRoute(ctx, route, amtLocal)
@ -241,6 +300,12 @@ func (r *regolancer) tryRapidRebalance(ctx context.Context, route *lnrpc.Route)
err = r.pay(attemptCtx, amtLocal, params.MinAmount, maxFeeMsat, routeLocal, 0)
// In case we are already decreasing the amount we can exit early because
// for even smaller amounts the fee will be higher (reason is the basefee).
if rebalanceStrategy == decreaseAmtRapidRebalance && errors.Is(err, ErrFeeExceeded) {
exitEarly = true
}
attemptCancel()
if attemptCtx.Err() == context.DeadlineExceeded {
@ -250,6 +315,8 @@ func (r *regolancer) tryRapidRebalance(ctx context.Context, route *lnrpc.Route)
if err != nil {
log.Printf("Rebalance failed with %s", err)
log.Println()
result.failedAttempts++
hittingTheWall = true
} else {
result.successfulAttempts++

Loading…
Cancel
Save