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 cde3fe59..d84a5834 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 @@ -69,16 +69,16 @@ public class MultiPathPaymentSplitter { List basicRoutes = BasicRoutes.fromFlows(source, intermediateTarget, flows); List extendedBasicRoutes = extendBasicRoutes(basicRoutes, paymentOptions, target); if (extendedBasicRoutes.isEmpty()) { - return MultiPathPayment.FAILURE; + return MultiPathPayment.failure("Unable to extend channel back to own node"); } List routes = getWithLiquidityInformation(extendedBasicRoutes); List fixedRoutes = Routes.getFixedWithTotalAmount(routes, amount); if (fixedRoutes.isEmpty()) { - return MultiPathPayment.FAILURE; + return MultiPathPayment.failure("Not enough liquidity for first hop (due to fees?)"); } if (isTooExpensive(paymentOptions, fixedRoutes)) { - return MultiPathPayment.FAILURE; + return MultiPathPayment.failure("At least one route is too expensive (fee rate limit)"); } return new MultiPathPayment(fixedRoutes); } diff --git a/pickhardt-payments/src/main/java/de/cotto/lndmanagej/pickhardtpayments/PaymentLoop.java b/pickhardt-payments/src/main/java/de/cotto/lndmanagej/pickhardtpayments/PaymentLoop.java index 8d834f24..1c05c253 100644 --- a/pickhardt-payments/src/main/java/de/cotto/lndmanagej/pickhardtpayments/PaymentLoop.java +++ b/pickhardt-payments/src/main/java/de/cotto/lndmanagej/pickhardtpayments/PaymentLoop.java @@ -10,6 +10,7 @@ import de.cotto.lndmanagej.model.Route; import de.cotto.lndmanagej.pickhardtpayments.model.MultiPathPayment; import de.cotto.lndmanagej.pickhardtpayments.model.PaymentOptions; import de.cotto.lndmanagej.pickhardtpayments.model.PaymentStatus; +import org.apache.logging.log4j.util.Strings; import org.springframework.scheduling.annotation.Async; import org.springframework.stereotype.Component; @@ -81,9 +82,18 @@ public class PaymentLoop { MultiPathPayment multiPathPayment = multiPathPaymentSplitter.getMultiPathPaymentTo(destination, residualAmount, paymentOptions); if (multiPathPayment.isFailure()) { - paymentStatus.failed( - "Unable to find route (trying to send %s)".formatted(residualAmount.toStringSat()) - ); + String information = multiPathPayment.getInformation(); + if (Strings.isNotEmpty(information)) { + paymentStatus.failed( + "Unable to find route (trying to send %s): %s" + .formatted(residualAmount.toStringSat(), information) + ); + } else { + paymentStatus.failed( + "Unable to find route (trying to send %s)" + .formatted(residualAmount.toStringSat()) + ); + } return; } List routes = multiPathPayment.routes(); diff --git a/pickhardt-payments/src/main/java/de/cotto/lndmanagej/pickhardtpayments/model/MultiPathPayment.java b/pickhardt-payments/src/main/java/de/cotto/lndmanagej/pickhardtpayments/model/MultiPathPayment.java index b528baaa..f351b370 100644 --- a/pickhardt-payments/src/main/java/de/cotto/lndmanagej/pickhardtpayments/model/MultiPathPayment.java +++ b/pickhardt-payments/src/main/java/de/cotto/lndmanagej/pickhardtpayments/model/MultiPathPayment.java @@ -10,10 +10,11 @@ public record MultiPathPayment( Coins fees, Coins feesWithFirstHop, double probability, - List routes + List routes, + String information ) { public static final MultiPathPayment FAILURE = - new MultiPathPayment(Coins.NONE, Coins.NONE, Coins.NONE, 0.0, List.of()); + new MultiPathPayment(Coins.NONE, Coins.NONE, Coins.NONE, 0.0, List.of(), ""); public MultiPathPayment(List routes) { this( @@ -21,12 +22,17 @@ public record MultiPathPayment( routes.stream().map(Route::getFees).reduce(Coins.NONE, Coins::add), routes.stream().map(Route::getFeesWithFirstHop).reduce(Coins.NONE, Coins::add), routes.stream().mapToDouble(Route::getProbability).reduce(1.0, (a, b) -> a * b), - routes + routes, + "" ); } + public static MultiPathPayment failure(String information) { + return new MultiPathPayment(Coins.NONE, Coins.NONE, Coins.NONE, 0.0, List.of(), information); + } + public boolean isFailure() { - return equals(FAILURE); + return routes.isEmpty() && amount.equals(Coins.NONE); } public long getFeeRate() { @@ -37,6 +43,10 @@ public record MultiPathPayment( return getFeeRateForFees(feesWithFirstHop); } + public String getInformation() { + return information; + } + private long getFeeRateForFees(Coins feesToConsider) { if (amount.isPositive()) { return feesToConsider.milliSatoshis() * 1_000_000 / amount.milliSatoshis(); diff --git a/pickhardt-payments/src/test/java/de/cotto/lndmanagej/pickhardtpayments/PaymentLoopTest.java b/pickhardt-payments/src/test/java/de/cotto/lndmanagej/pickhardtpayments/PaymentLoopTest.java index 30fb3c80..c5ca1a0a 100644 --- a/pickhardt-payments/src/test/java/de/cotto/lndmanagej/pickhardtpayments/PaymentLoopTest.java +++ b/pickhardt-payments/src/test/java/de/cotto/lndmanagej/pickhardtpayments/PaymentLoopTest.java @@ -82,6 +82,18 @@ class PaymentLoopTest { verifyNoInteractions(grpcSendToRoute); } + @Test + void failure_with_information() { + when(multiPathPaymentSplitter.getMultiPathPaymentTo(any(), any(), any())) + .thenReturn(MultiPathPayment.failure("something")); + paymentLoop.start(DECODED_PAYMENT_REQUEST, PAYMENT_OPTIONS, paymentStatus); + + assertThat(paymentStatus.isFailure()).isTrue(); + assertThat(paymentStatus.getMessages().stream().map(InstantWithString::string)) + .contains("Unable to find route (trying to send 123): something"); + verifyNoInteractions(grpcSendToRoute); + } + @Test void requests_route_with_expected_parameters() { mockSuccessOnFirstAttempt(); diff --git a/pickhardt-payments/src/test/java/de/cotto/lndmanagej/pickhardtpayments/model/MultiPathPaymentTest.java b/pickhardt-payments/src/test/java/de/cotto/lndmanagej/pickhardtpayments/model/MultiPathPaymentTest.java index c993ea74..d375373c 100644 --- a/pickhardt-payments/src/test/java/de/cotto/lndmanagej/pickhardtpayments/model/MultiPathPaymentTest.java +++ b/pickhardt-payments/src/test/java/de/cotto/lndmanagej/pickhardtpayments/model/MultiPathPaymentTest.java @@ -1,6 +1,7 @@ package de.cotto.lndmanagej.pickhardtpayments.model; import de.cotto.lndmanagej.model.Coins; +import org.assertj.core.api.SoftAssertions; import org.junit.jupiter.api.Test; import static de.cotto.lndmanagej.model.RouteFixtures.ROUTE; @@ -74,4 +75,18 @@ class MultiPathPaymentTest { void feeRateWithFirstHop() { assertThat(MULTI_PATH_PAYMENT.getFeeRateWithFirstHop()).isEqualTo(466); } + + @Test + void failure_with_information() { + assertThat(MultiPathPayment.failure("something").isFailure()).isTrue(); + } + + @Test + void getInformation() { + SoftAssertions softly = new SoftAssertions(); + softly.assertThat(MultiPathPayment.FAILURE.getInformation()).isEqualTo(""); + softly.assertThat(MultiPathPayment.failure("hello").getInformation()).isEqualTo("hello"); + softly.assertThat(MULTI_PATH_PAYMENT.getInformation()).isEqualTo(""); + softly.assertAll(); + } }