diff --git a/liquidator.go b/liquidator.go index e69850f..8088088 100644 --- a/liquidator.go +++ b/liquidator.go @@ -132,7 +132,6 @@ func startLiquidator() { log.Fatal("failed to generate context with macaroon") } - //Get the local node info nodeInfo, err := getLocalNodeInfo(lightningClient, nodeContext) if err != nil { @@ -349,22 +348,18 @@ func monitorChannel(info MonitorChannelInfo) { channelRules := info.liquidationRules[info.channel.GetChanId()] - if len(info.channel.PendingHtlcs) == 0 { - //Manage liquidity if there are no pending htlcs - err = manageChannelLiquidity(ManageChannelLiquidityInfo{ - channel: info.channel, - channelBalanceRatio: channelBalanceRatio, - channelRules: &channelRules, - swapClientClient: info.swapClient, - nodeguardClient: info.nodeguardClient, - loopProvider: info.loopProvider, - loopdMacaroon: info.loopdMacaroon, - nodeInfo: info.nodeInfo, - ctx: spanCtx, - }) - } else { - log.WithField("span", span).Debugf("channel: %v has pending htlcs, skipping liquidity management", info.channel.GetChanId()) - } + //Manage liquidity if there are no pending htlcs + err = manageChannelLiquidity(ManageChannelLiquidityInfo{ + channel: info.channel, + channelBalanceRatio: channelBalanceRatio, + channelRules: &channelRules, + swapClientClient: info.swapClient, + nodeguardClient: info.nodeguardClient, + loopProvider: info.loopProvider, + loopdMacaroon: info.loopdMacaroon, + nodeInfo: info.nodeInfo, + ctx: spanCtx, + }) if err != nil { @@ -448,6 +443,13 @@ func manageChannelLiquidity(info ManageChannelLiquidityInfo) error { //Calculate the swap amount swapAmount := helper.AbsInt64((channel.RemoteBalance - swapAmountTarget)) + // if the swapAmount is bigger than max pending htlc amount, set it to max pending htlc amount + maxPendingLocalSats := int64(channel.GetLocalConstraints().GetMaxPendingAmtMsat() / 1000) + if swapAmount > maxPendingLocalSats && maxPendingLocalSats > 0 { + swapAmount = maxPendingLocalSats + log.WithField("span",span).Infof("swap amount is bigger than max pending htlc amount, setting it to max pending htlc amount: %v", maxPendingLocalSats) + } + //Request nodeguard a new destination address for the reverse swap walletRequest := &nodeguard.GetNewWalletAddressRequest{ WalletId: rule.WalletId, diff --git a/liquidator_test.go b/liquidator_test.go index cddb978..14f6341 100644 --- a/liquidator_test.go +++ b/liquidator_test.go @@ -113,6 +113,14 @@ func Test_manageChannelLiquidity(t *testing.T) { LocalBalance: 100, RemoteBalance: 900, RemotePubkey: "03485d8dcdd149c87553eeb80586eb2bece874d412e9f117304446ce189955d375", + LocalConstraints: &lnrpc.ChannelConstraints{ + CsvDelay: 144, + ChanReserveSat: 250, + DustLimitSat: 300, + MaxPendingAmtMsat: 300*1000, + MinHtlcMsat: 350 * 1000, + MaxAcceptedHtlcs: 30, + }, } nodeInfo := lnrpc.GetInfoResponse{ @@ -137,6 +145,20 @@ func Test_manageChannelLiquidity(t *testing.T) { ctx: context.TODO(), }, wantErr: false, + }, + { + name: "Manage channel liquidity test valid reverse swap bypassing max pending amt", + args: ManageChannelLiquidityInfo{ + channel: channelActive, + channelBalanceRatio: 0.1, + channelRules: &[]nodeguard.LiquidityRule{{ChannelId: 123, NodePubkey: "", WalletId: 1, MinimumLocalBalance: 20, MinimumRemoteBalance: 80, RebalanceTarget: 40}}, + nodeguardClient: mockNodeGuardClient, + loopProvider: mockProvider, + loopdMacaroon: "0201036c6e6402f801030a10dc64226b045d25f090b114baebcbf04c1201301a160a0761646472657373120472656164120577726974651a130a04696e666f120472656164120577726974651a170a08696e766f69636573120472656164120577726974651a210a086d616361726f6f6e120867656e6572617465120472656164120577726974651a160a076d657373616765120472656164120577726974651a170a086f6666636861696e120472656164120577726974651a160a076f6e636861696e120472656164120577726974651a140a057065657273120472656164120577726974651a180a067369676e6572120867656e657261746512047265616400000620a21b8cc8c071aa5104b706b751aede972f642537c05da31450fb4b02c6da776e", + nodeInfo: nodeInfo, + ctx: context.TODO(), + }, + wantErr: false, }, { name: "Manage channel liquidity test valid swap", diff --git a/loop b/loop index 28421af..fcfa4b1 160000 --- a/loop +++ b/loop @@ -1 +1 @@ -Subproject commit 28421affb856f521153fa633d542b4f08d31fbd2 +Subproject commit fcfa4b180d8590fbc5581ad1239ce70ffeec17ad diff --git a/provider/loop_provider.go b/provider/loop_provider.go index bf243e3..8873768 100644 --- a/provider/loop_provider.go +++ b/provider/loop_provider.go @@ -234,16 +234,16 @@ func (l *LoopProvider) RequestReverseSubmarineSwap(ctx context.Context, request //Use the client to request the swap resp, err := client.LoopOut(ctx, &looprpc.LoopOutRequest{ - Amt: request.SatsAmount, - Dest: request.ReceiverBTCAddress, - MaxMinerFee: int64(limits.maxMinerFee), - MaxPrepayAmt: int64(limits.maxPrepayAmt), - MaxSwapFee: int64(limits.maxSwapFee), - MaxPrepayRoutingFee: int64(limits.maxPrepayRoutingFee), - MaxSwapRoutingFee: int64(limits.maxSwapRoutingFee), - OutgoingChanSet: request.ChannelSet, - SweepConfTarget: 2, //TODO Make this configurable - HtlcConfirmations: 2, + Amt: request.SatsAmount, + Dest: request.ReceiverBTCAddress, + MaxMinerFee: int64(limits.maxMinerFee), + MaxPrepayAmt: int64(limits.maxPrepayAmt), + MaxSwapFee: int64(limits.maxSwapFee), + MaxPrepayRoutingFee: int64(limits.maxPrepayRoutingFee), + MaxSwapRoutingFee: int64(limits.maxSwapRoutingFee), + OutgoingChanSet: request.ChannelSet, + SweepConfTarget: 3, //TODO Make this configurable + HtlcConfirmations: 3, //The publication deadline is maximum the offset of the swap deadline conf plus the current time SwapPublicationDeadline: uint64(time.Now().Add(viper.GetDuration("swapPublicationOffset") * time.Minute).Unix()), Label: fmt.Sprintf("Reverse submarine swap %d sats on date %s", request.SatsAmount, time.Now().Format(time.RFC3339)), diff --git a/provider/loop_provider_test.go b/provider/loop_provider_test.go index e526b7d..6c786b7 100644 --- a/provider/loop_provider_test.go +++ b/provider/loop_provider_test.go @@ -366,12 +366,12 @@ func Test_checkSubmarineSwapNotInProgress(t *testing.T) { swapClientWithOngoingSwaps.EXPECT().ListSwaps(gomock.Any(), gomock.Any()).Return(&looprpc.ListSwapsResponse{ Swaps: []*looprpc.SwapStatus{ { - Amt: 0, - Id: "", - IdBytes: idBytes, - Type: looprpc.SwapType_LOOP_IN, - State: looprpc.SwapState_INITIATED, - FailureReason: 0, + Amt: 0, + Id: "", + IdBytes: idBytes, + Type: looprpc.SwapType_LOOP_IN, + State: looprpc.SwapState_INITIATED, + FailureReason: 0, //InitiationTime 4 hours ago InitiationTime: time.Now().Add(-4 * time.Hour).UnixNano(), LastUpdateTime: time.Now().Add(-4 * time.Hour).UnixNano(), @@ -393,12 +393,12 @@ func Test_checkSubmarineSwapNotInProgress(t *testing.T) { swapClientWithNoOngoingSwaps.EXPECT().ListSwaps(gomock.Any(), gomock.Any()).Return(&looprpc.ListSwapsResponse{ Swaps: []*looprpc.SwapStatus{ { - Amt: 0, - Id: "", - IdBytes: idBytes, - Type: looprpc.SwapType_LOOP_IN, - State: looprpc.SwapState_INITIATED, - FailureReason: 0, + Amt: 0, + Id: "", + IdBytes: idBytes, + Type: looprpc.SwapType_LOOP_IN, + State: looprpc.SwapState_INITIATED, + FailureReason: 0, //InitiationTime is more than 24 hours ago, stuck but ignored InitiationTime: time.Now().Add(-25 * time.Hour).UnixNano(), LastUpdateTime: time.Now().Add(-25 * time.Hour).UnixNano(), diff --git a/rpc/rpc_test.go b/rpc/rpc_test.go index aff55cf..3c520b7 100644 --- a/rpc/rpc_test.go +++ b/rpc/rpc_test.go @@ -113,7 +113,6 @@ func TestCreateSwapClientClient(t *testing.T) { Cert: tlsCertEncoded, Macaroon: "0201036c6e6402f801030a101ec5b6370c166f6c8e2853164109145a1201301a160a0761646472657373120472656164120577726974651a130a04696e666f120472656164120577726974651a170a08696e766f69636573120472656164120577726974651a210a086d616361726f6f6e120867656e6572617465120472656164120577726974651a160a076d657373616765120472656164120577726974651a170a086f6666636861696e120472656164120577726974651a160a076f6e636861696e120472656164120577726974651a140a057065657273120472656164120577726974651a180a067369676e6572120867656e6572617465120472656164000006208e957e78ec39e7810fad25cfc43850b8e9e7c079843b8ec7bb5522bba12230d6", }, - }, wantErr: false, },