@ -3,6 +3,7 @@ package main
import (
import (
"context"
"context"
"encoding/hex"
"encoding/hex"
"errors"
"log"
"log"
"time"
"time"
@ -11,10 +12,16 @@ import (
type rebalanceResult struct {
type rebalanceResult struct {
successfulAttempts int
successfulAttempts int
failedAttempts int
successfulAmt int64
successfulAmt int64
paidFeeMsat int64
paidFeeMsat int64
}
}
const (
increaseAmtRapidRebalance string = "increase"
decreaseAmtRapidRebalance string = "decrease"
)
func ( r * regolancer ) tryRebalance ( ctx context . Context , attempt * int ) ( err error ,
func ( r * regolancer ) tryRebalance ( ctx context . Context , attempt * int ) ( err error ,
repeat bool ) {
repeat bool ) {
attemptCtx , attemptCancel := context . WithTimeout ( ctx , time . Minute * time . Duration ( params . TimeoutAttempt ) )
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 {
if params . AllowRapidRebalance {
rebalanceResult , _ := r . tryRapidRebalance ( ctx , route )
rebalanceResult , _ := r . tryRapidRebalance ( ctx , route )
if rebalanceResult . successfulAttempts > 0 {
if rebalanceResult . successfulAttempts > 0 || rebalanceResult . failedAttempts > 0 {
log . Printf ( "%s rapid rebalances were successful, total amount: %s (fee: %s sat | %s ppm) \n",
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 ) ,
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" )
log . Printf ( "Finished rapid rebalancing" )
}
}
@ -99,11 +107,15 @@ func (r *regolancer) tryRapidRebalance(ctx context.Context, route *lnrpc.Route)
amtLocal int64 = amt
amtLocal int64 = amt
accelerator int64 = 1
accelerator int64 = 1
hittingTheWall bool
hittingTheWall bool
exitEarly bool
capReached bool
capReached bool
maxAmountOnRouteMsat uint64
maxAmountOnRouteMsat uint64
minAmount uint64
rebalanceStrategy string = increaseAmtRapidRebalance
)
)
result . successfulAttempts = 0
result . successfulAttempts = 0
result . failedAttempts = 0
// Include Initial Rebalance
// Include Initial Rebalance
result . successfulAmt = amt
result . successfulAmt = amt
result . paidFeeMsat = route . TotalFeesMsat
result . paidFeeMsat = route . TotalFeesMsat
@ -113,14 +125,23 @@ func (r *regolancer) tryRapidRebalance(ctx context.Context, route *lnrpc.Route)
return result , err
return result , err
}
}
if params . MinAmount > 0 {
minAmount = uint64 ( params . MinAmount )
} else {
minAmount = 10000
}
Loop :
for {
for {
switch rebalanceStrategy {
case increaseAmtRapidRebalance :
if hittingTheWall {
if hittingTheWall {
accelerator >>= 1
accelerator >>= 1
// In case we encounter that we are already constrained
// In case we encounter that we are already constrained
// by the liquidity on the channels we are waiting for
// by the liquidity on the channels we are waiting for
// the accelerator to go below this amount to save
// the accelerator to go below this amount to save
// already failed rebalances
// already failed rebalances
if amtLocal < accelerator * amt && amtLocal > 0 {
if amtLocal < accelerator * amt && amtLocal > int64 ( minAmount ) {
continue
continue
}
}
} else if ! capReached {
} else if ! capReached {
@ -136,9 +157,32 @@ func (r *regolancer) tryRapidRebalance(ctx context.Context, route *lnrpc.Route)
log . Printf ( "Max amount on route reached capping amount at %s sats " +
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 ) )
"| 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 {
if accelerator < 1 {
break
accelerator = 2
rebalanceStrategy = decreaseAmtRapidRebalance
amtLocal = amt / accelerator
if amtLocal < int64 ( minAmount ) {
break Loop
}
}
case decreaseAmtRapidRebalance :
accelerator <<= 1
if amtLocal < amt / accelerator {
continue
}
amtLocal = amt / accelerator
if amtLocal < int64 ( minAmount ) {
break Loop
}
}
if exitEarly {
break Loop
}
}
log . Printf ( "Rapid rebalance attempt %s, amount: %s\n" , hiWhiteColor ( result . successfulAttempts + 1 ) , hiWhiteColor ( amtLocal ) )
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
return result , err
}
}
amtLocalTemp := amtLocal
_ , _ , amtLocal , err = r . pickChannelPair ( amtLocal , params . MinAmount , params . RelAmountFrom , params . RelAmountTo )
_ , _ , amtLocal , err = r . pickChannelPair ( amtLocal , params . MinAmount , params . RelAmountFrom , params . RelAmountTo )
if err != nil {
if err != nil {
log . Printf ( errColor ( "Error during picking channel: %s" ) , err )
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
}
}
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 ) )
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 )
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 )
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 ( )
attemptCancel ( )
if attemptCtx . Err ( ) == context . DeadlineExceeded {
if attemptCtx . Err ( ) == context . DeadlineExceeded {
@ -250,6 +315,8 @@ func (r *regolancer) tryRapidRebalance(ctx context.Context, route *lnrpc.Route)
if err != nil {
if err != nil {
log . Printf ( "Rebalance failed with %s" , err )
log . Printf ( "Rebalance failed with %s" , err )
log . Println ( )
result . failedAttempts ++
hittingTheWall = true
hittingTheWall = true
} else {
} else {
result . successfulAttempts ++
result . successfulAttempts ++