provide better failure reporting

This commit is contained in:
Carsten Otto
2022-05-22 23:11:40 +02:00
parent 35ba9d9aeb
commit 116dd4a508
5 changed files with 57 additions and 10 deletions

View File

@@ -69,16 +69,16 @@ public class MultiPathPaymentSplitter {
List<BasicRoute> basicRoutes = BasicRoutes.fromFlows(source, intermediateTarget, flows);
List<BasicRoute> extendedBasicRoutes = extendBasicRoutes(basicRoutes, paymentOptions, target);
if (extendedBasicRoutes.isEmpty()) {
return MultiPathPayment.FAILURE;
return MultiPathPayment.failure("Unable to extend channel back to own node");
}
List<Route> routes = getWithLiquidityInformation(extendedBasicRoutes);
List<Route> 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);
}

View File

@@ -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<Route> routes = multiPathPayment.routes();

View File

@@ -10,10 +10,11 @@ public record MultiPathPayment(
Coins fees,
Coins feesWithFirstHop,
double probability,
List<Route> routes
List<Route> 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<Route> 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();

View File

@@ -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();

View File

@@ -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();
}
}