diff --git a/pickhardt-payments/src/main/java/de/cotto/lndmanagej/pickhardtpayments/MultiPathPaymentSplitter.java b/pickhardt-payments/src/main/java/de/cotto/lndmanagej/pickhardtpayments/MultiPathPaymentSplitter.java index 424ce59e..cde3fe59 100644 --- a/pickhardt-payments/src/main/java/de/cotto/lndmanagej/pickhardtpayments/MultiPathPaymentSplitter.java +++ b/pickhardt-payments/src/main/java/de/cotto/lndmanagej/pickhardtpayments/MultiPathPaymentSplitter.java @@ -73,6 +73,9 @@ public class MultiPathPaymentSplitter { } List routes = getWithLiquidityInformation(extendedBasicRoutes); List fixedRoutes = Routes.getFixedWithTotalAmount(routes, amount); + if (fixedRoutes.isEmpty()) { + return MultiPathPayment.FAILURE; + } if (isTooExpensive(paymentOptions, fixedRoutes)) { return MultiPathPayment.FAILURE; diff --git a/pickhardt-payments/src/main/java/de/cotto/lndmanagej/pickhardtpayments/model/Routes.java b/pickhardt-payments/src/main/java/de/cotto/lndmanagej/pickhardtpayments/model/Routes.java index a51fa393..28f05fe8 100644 --- a/pickhardt-payments/src/main/java/de/cotto/lndmanagej/pickhardtpayments/model/Routes.java +++ b/pickhardt-payments/src/main/java/de/cotto/lndmanagej/pickhardtpayments/model/Routes.java @@ -1,6 +1,7 @@ package de.cotto.lndmanagej.pickhardtpayments.model; import de.cotto.lndmanagej.model.Coins; +import de.cotto.lndmanagej.model.EdgeWithLiquidityInformation; import de.cotto.lndmanagej.model.Route; import java.util.ArrayList; @@ -21,6 +22,24 @@ public final class Routes { routesCopy.remove(highProbabilityRoute); Route fixedRoute = highProbabilityRoute.getForAmount(highProbabilityRoute.getAmount().add(remainder)); routesCopy.add(fixedRoute); + if (isAboveAvailableLiquidity(routesCopy)) { + return List.of(); + } return routesCopy; } + + private static boolean isAboveAvailableLiquidity(List routes) { + for (Route route : routes) { + List edgesWithLiquidityInformation = route.getEdgesWithLiquidityInformation(); + for (int index = 0; index < edgesWithLiquidityInformation.size(); index++) { + EdgeWithLiquidityInformation edge = edgesWithLiquidityInformation.get(index); + Coins requiredAmount = route.getForwardAmountForHop(index); + Coins availableAmountUpperBound = edge.availableLiquidityUpperBound(); + if (availableAmountUpperBound.compareTo(requiredAmount) < 0) { + return true; + } + } + } + return false; + } } diff --git a/pickhardt-payments/src/test/java/de/cotto/lndmanagej/pickhardtpayments/MultiPathPaymentSplitterTest.java b/pickhardt-payments/src/test/java/de/cotto/lndmanagej/pickhardtpayments/MultiPathPaymentSplitterTest.java index 3e0087d2..41a2a73e 100644 --- a/pickhardt-payments/src/test/java/de/cotto/lndmanagej/pickhardtpayments/MultiPathPaymentSplitterTest.java +++ b/pickhardt-payments/src/test/java/de/cotto/lndmanagej/pickhardtpayments/MultiPathPaymentSplitterTest.java @@ -325,6 +325,20 @@ class MultiPathPaymentSplitterTest { assertThat(multiPathPayment.routes().iterator().next().getAmount()).isEqualTo(AMOUNT); } + @Test + void unable_to_add_remainder() { + Coins largeAmount = Coins.ofSatoshis(100_000_000_000L); + when(flowComputation.getOptimalFlows(PUBKEY, PUBKEY_2, largeAmount, DEFAULT_PAYMENT_OPTIONS)) + .thenReturn(new Flows(FLOW)); + MultiPathPayment multiPathPayment = multiPathPaymentSplitter.getMultiPathPayment( + PUBKEY, + PUBKEY_2, + largeAmount, + DEFAULT_PAYMENT_OPTIONS + ); + assertThat(multiPathPayment.isFailure()).isTrue(); + } + @Test void adds_hop_if_peer_is_specified_in_payment_options() { Edge extensionEdge = mockExtensionEdge(PUBKEY_3, 0); @@ -372,6 +386,7 @@ class MultiPathPaymentSplitterTest { @Test void adds_remainder_to_most_probable_route_due_to_liquidity_information() { Coins oneSat = Coins.ofSatoshis(1); + Coins amount = Coins.ofSatoshis(3); Coins largeCapacity = Coins.ofSatoshis(10); Coins smallCapacity = Coins.ofSatoshis(5); @@ -386,20 +401,20 @@ class MultiPathPaymentSplitterTest { when(edgeComputation.getEdgeWithLiquidityInformation(edgeSmallCapacity)) .thenReturn(liquidityInformationSmall); - when(flowComputation.getOptimalFlows(PUBKEY, PUBKEY_2, AMOUNT, DEFAULT_PAYMENT_OPTIONS)) + when(flowComputation.getOptimalFlows(PUBKEY, PUBKEY_2, amount, DEFAULT_PAYMENT_OPTIONS)) .thenReturn(new Flows( new Flow(edgeLargeCapacity, oneSat), new Flow(edgeSmallCapacity, oneSat) )); - assumeThat(FLOW.amount()).isLessThan(AMOUNT); + assumeThat(FLOW.amount()).isLessThan(amount); MultiPathPayment multiPathPayment = multiPathPaymentSplitter.getMultiPathPayment( PUBKEY, PUBKEY_2, - AMOUNT, + amount, DEFAULT_PAYMENT_OPTIONS ); Route route1 = new Route(List.of(liquidityInformationLarge), oneSat); - Route route2 = new Route(List.of(liquidityInformationSmall), AMOUNT.subtract(oneSat)); + Route route2 = new Route(List.of(liquidityInformationSmall), amount.subtract(oneSat)); assertThat(multiPathPayment.routes()).containsExactlyInAnyOrder(route1, route2); } diff --git a/pickhardt-payments/src/test/java/de/cotto/lndmanagej/pickhardtpayments/model/RoutesTest.java b/pickhardt-payments/src/test/java/de/cotto/lndmanagej/pickhardtpayments/model/RoutesTest.java index 82cf175e..caaacc49 100644 --- a/pickhardt-payments/src/test/java/de/cotto/lndmanagej/pickhardtpayments/model/RoutesTest.java +++ b/pickhardt-payments/src/test/java/de/cotto/lndmanagej/pickhardtpayments/model/RoutesTest.java @@ -3,6 +3,7 @@ package de.cotto.lndmanagej.pickhardtpayments.model; import de.cotto.lndmanagej.model.BasicRoute; import de.cotto.lndmanagej.model.Coins; import de.cotto.lndmanagej.model.Edge; +import de.cotto.lndmanagej.model.EdgeWithLiquidityInformation; import de.cotto.lndmanagej.model.Route; import org.junit.jupiter.api.Test; @@ -53,4 +54,17 @@ class RoutesTest { new Route(basicRoute3) ); } + + @Test + void getFixedWithTotalAmount_does_not_add_if_amount_exceeds_available_balance() { + Edge edge = new Edge(CHANNEL_ID, PUBKEY, PUBKEY_2, CAPACITY, POLICY_1); + + EdgeWithLiquidityInformation withLiquidityInformation = + EdgeWithLiquidityInformation.forKnownLiquidity(edge, Coins.ofSatoshis(199)); + List routes = List.of(new Route(List.of(withLiquidityInformation), Coins.ofSatoshis(150))); + + List fixedRoutes = Routes.getFixedWithTotalAmount(routes, Coins.ofSatoshis(200)); + + assertThat(fixedRoutes).isEmpty(); + } }