From 7bfcbf7333f0a47800afee29bdff7b9240861ea1 Mon Sep 17 00:00:00 2001 From: Carsten Otto Date: Sat, 14 May 2022 11:20:12 +0200 Subject: [PATCH] use PaymentInformation, provide isFailure/isSettled --- .../MultiPathPaymentObserver.java | 37 ++++++- .../MultiPathPaymentObserverTest.java | 100 +++++++++++++----- 2 files changed, 110 insertions(+), 27 deletions(-) diff --git a/pickhardt-payments/src/main/java/de/cotto/lndmanagej/pickhardtpayments/MultiPathPaymentObserver.java b/pickhardt-payments/src/main/java/de/cotto/lndmanagej/pickhardtpayments/MultiPathPaymentObserver.java index a222ed55..01774a96 100644 --- a/pickhardt-payments/src/main/java/de/cotto/lndmanagej/pickhardtpayments/MultiPathPaymentObserver.java +++ b/pickhardt-payments/src/main/java/de/cotto/lndmanagej/pickhardtpayments/MultiPathPaymentObserver.java @@ -7,6 +7,7 @@ import de.cotto.lndmanagej.model.FailureCode; import de.cotto.lndmanagej.model.HexString; import de.cotto.lndmanagej.model.PaymentAttemptHop; import de.cotto.lndmanagej.model.Route; +import de.cotto.lndmanagej.pickhardtpayments.model.PaymentInformation; import de.cotto.lndmanagej.service.LiquidityInformationUpdater; import org.springframework.stereotype.Component; @@ -15,11 +16,12 @@ import java.util.List; import java.util.Map; import java.util.Optional; import java.util.concurrent.ConcurrentHashMap; +import java.util.function.Function; @Component public class MultiPathPaymentObserver { private final LiquidityInformationUpdater liquidityInformationUpdater; - private final Map inFlight = new ConcurrentHashMap<>(); + private final Map map = new ConcurrentHashMap<>(); public MultiPathPaymentObserver(LiquidityInformationUpdater liquidityInformationUpdater) { this.liquidityInformationUpdater = liquidityInformationUpdater; @@ -30,11 +32,33 @@ public class MultiPathPaymentObserver { } public Coins getInFlight(HexString paymentHash) { - return inFlight.getOrDefault(paymentHash, Coins.NONE); + return get(paymentHash).inFlight(); + } + + public boolean isSettled(HexString paymentHash) { + return get(paymentHash).settled(); + } + + public boolean isFailed(HexString paymentHash) { + return get(paymentHash).failed(); } private void addInFlight(HexString paymentHash, Coins amount) { - inFlight.compute(paymentHash, (key, value) -> value == null ? amount : amount.add(value)); + update(paymentHash, value -> value.withAdditionalInFlight(amount)); + } + + private PaymentInformation get(HexString paymentHash) { + return map.getOrDefault(paymentHash, PaymentInformation.DEFAULT); + } + + private void update(HexString paymentHash, Function updater) { + map.compute(paymentHash, (key, value) -> { + PaymentInformation newValue = updater.apply(value == null ? PaymentInformation.DEFAULT : value); + if (PaymentInformation.DEFAULT.equals(newValue)) { + return null; + } + return newValue; + }); } private List topPaymentAttemptHops(Route route) { @@ -71,6 +95,13 @@ public class MultiPathPaymentObserver { @Override public void onValue(HexString preimage, FailureCode failureCode) { + if (preimage.equals(HexString.EMPTY)) { + if (failureCode.isErrorFromFinalNode()) { + update(paymentHash, PaymentInformation::withIsFailed); + } + } else { + update(paymentHash, PaymentInformation::withIsSettled); + } addInFlight(paymentHash, route.getAmount().negate()); } } diff --git a/pickhardt-payments/src/test/java/de/cotto/lndmanagej/pickhardtpayments/MultiPathPaymentObserverTest.java b/pickhardt-payments/src/test/java/de/cotto/lndmanagej/pickhardtpayments/MultiPathPaymentObserverTest.java index c1eff683..e0ff4330 100644 --- a/pickhardt-payments/src/test/java/de/cotto/lndmanagej/pickhardtpayments/MultiPathPaymentObserverTest.java +++ b/pickhardt-payments/src/test/java/de/cotto/lndmanagej/pickhardtpayments/MultiPathPaymentObserverTest.java @@ -27,6 +27,7 @@ import static org.mockito.Mockito.verify; @ExtendWith(MockitoExtension.class) class MultiPathPaymentObserverTest { + private static final HexString PAYMENT_HASH = DECODED_PAYMENT_REQUEST.paymentHash(); @InjectMocks private MultiPathPaymentObserver multiPathPaymentObserver; @@ -35,25 +36,21 @@ class MultiPathPaymentObserverTest { @Test void cancels_in_flight_on_error() { - HexString paymentHash = DECODED_PAYMENT_REQUEST.paymentHash(); - SendToRouteObserver sendToRouteObserver = - multiPathPaymentObserver.getFor(ROUTE, paymentHash); + SendToRouteObserver sendToRouteObserver = multiPathPaymentObserver.getFor(ROUTE, PAYMENT_HASH); sendToRouteObserver.onError(new NullPointerException()); - assertThat(multiPathPaymentObserver.getInFlight(paymentHash)).isEqualTo(Coins.NONE); + assertThat(multiPathPaymentObserver.getInFlight(PAYMENT_HASH)).isEqualTo(Coins.NONE); } @Test void cancels_in_flight_using_liquidity_information_updater_on_error() { - SendToRouteObserver sendToRouteObserver = - multiPathPaymentObserver.getFor(ROUTE, DECODED_PAYMENT_REQUEST.paymentHash()); + SendToRouteObserver sendToRouteObserver = multiPathPaymentObserver.getFor(ROUTE, PAYMENT_HASH); sendToRouteObserver.onError(new NullPointerException()); verify(liquidityInformationUpdater).removeInFlight(hops()); } @Test void accepts_value() { - SendToRouteObserver sendToRouteObserver = - multiPathPaymentObserver.getFor(ROUTE, DECODED_PAYMENT_REQUEST.paymentHash()); + SendToRouteObserver sendToRouteObserver = multiPathPaymentObserver.getFor(ROUTE, PAYMENT_HASH); assertThatCode( () -> sendToRouteObserver.onValue(HexString.EMPTY, FailureCode.UNKNOWN_FAILURE) ).doesNotThrowAnyException(); @@ -61,41 +58,96 @@ class MultiPathPaymentObserverTest { @Test void inFlight_initially_zero() { - assertThat(multiPathPaymentObserver.getInFlight(DECODED_PAYMENT_REQUEST.paymentHash())) - .isEqualTo(Coins.NONE); + assertThat(multiPathPaymentObserver.getInFlight(PAYMENT_HASH)).isEqualTo(Coins.NONE); } @Test void inFlight_initialized_with_route_amount() { - HexString paymentHash = DECODED_PAYMENT_REQUEST.paymentHash(); - multiPathPaymentObserver.getFor(ROUTE, paymentHash); - assertThat(multiPathPaymentObserver.getInFlight(paymentHash)).isEqualTo(ROUTE.getAmount()); + multiPathPaymentObserver.getFor(ROUTE, PAYMENT_HASH); + assertThat(multiPathPaymentObserver.getInFlight(PAYMENT_HASH)).isEqualTo(ROUTE.getAmount()); } @Test void inFlight_reset_on_success() { - HexString paymentHash = DECODED_PAYMENT_REQUEST.paymentHash(); - SendToRouteObserver observer = multiPathPaymentObserver.getFor(ROUTE, paymentHash); + SendToRouteObserver observer = multiPathPaymentObserver.getFor(ROUTE, PAYMENT_HASH); observer.onValue(new HexString("AABBCC"), FailureCode.UNKNOWN_FAILURE); - assertThat(multiPathPaymentObserver.getInFlight(paymentHash)).isEqualTo(Coins.NONE); + assertThat(multiPathPaymentObserver.getInFlight(PAYMENT_HASH)).isEqualTo(Coins.NONE); + } + + @Test + void isSettled_success() { + SendToRouteObserver observer = multiPathPaymentObserver.getFor(ROUTE, PAYMENT_HASH); + observer.onValue(new HexString("AABBCC"), FailureCode.UNKNOWN_FAILURE); + assertThat(multiPathPaymentObserver.isSettled(PAYMENT_HASH)).isTrue(); + } + + @Test + void isSettled_failure() { + SendToRouteObserver observer = multiPathPaymentObserver.getFor(ROUTE, PAYMENT_HASH); + observer.onValue(HexString.EMPTY, FailureCode.FEE_INSUFFICIENT); + assertThat(multiPathPaymentObserver.isSettled(PAYMENT_HASH)).isFalse(); + } + + @Test + void isSettled_error() { + SendToRouteObserver observer = multiPathPaymentObserver.getFor(ROUTE, PAYMENT_HASH); + observer.onError(new NullPointerException()); + assertThat(multiPathPaymentObserver.isSettled(PAYMENT_HASH)).isFalse(); + } + + @Test + void isFailed_success() { + SendToRouteObserver observer = multiPathPaymentObserver.getFor(ROUTE, PAYMENT_HASH); + observer.onValue(new HexString("AABBCC"), FailureCode.UNKNOWN_FAILURE); + assertThat(multiPathPaymentObserver.isFailed(PAYMENT_HASH)).isFalse(); + } + + @Test + void isFailed_failure_from_final_node() { + SendToRouteObserver observer = multiPathPaymentObserver.getFor(ROUTE, PAYMENT_HASH); + observer.onValue(HexString.EMPTY, FailureCode.FINAL_INCORRECT_CLTV_EXPIRY); + assertThat(multiPathPaymentObserver.isFailed(PAYMENT_HASH)).isTrue(); + } + + @Test + void isFailed_failure_from_other_node() { + SendToRouteObserver observer = multiPathPaymentObserver.getFor(ROUTE, PAYMENT_HASH); + observer.onValue(HexString.EMPTY, FailureCode.PERMANENT_CHANNEL_FAILURE); + assertThat(multiPathPaymentObserver.isFailed(PAYMENT_HASH)).isFalse(); + } + + @Test + void isFailed_error() { + SendToRouteObserver observer = multiPathPaymentObserver.getFor(ROUTE, PAYMENT_HASH); + observer.onError(new NullPointerException()); + assertThat(multiPathPaymentObserver.isFailed(PAYMENT_HASH)).isFalse(); + } + + @Test + void isSettled_initial() { + multiPathPaymentObserver.getFor(ROUTE, PAYMENT_HASH); + assertThat(multiPathPaymentObserver.isSettled(PAYMENT_HASH)).isFalse(); + } + + @Test + void isSettled_unknown() { + assertThat(multiPathPaymentObserver.isSettled(PAYMENT_HASH)).isFalse(); } @Test void inFlight_reset_on_failure() { - HexString paymentHash = DECODED_PAYMENT_REQUEST.paymentHash(); - SendToRouteObserver observer = multiPathPaymentObserver.getFor(ROUTE, paymentHash); - observer.onValue(HexString.EMPTY, FailureCode.PERMANENT_CHANNEL_FAILURE); - assertThat(multiPathPaymentObserver.getInFlight(paymentHash)).isEqualTo(Coins.NONE); + SendToRouteObserver observer = multiPathPaymentObserver.getFor(ROUTE, PAYMENT_HASH); + observer.onValue(HexString.EMPTY, FailureCode.FEE_INSUFFICIENT); + assertThat(multiPathPaymentObserver.getInFlight(PAYMENT_HASH)).isEqualTo(Coins.NONE); } @Test void inFlight_updated_with_several_routes() { - HexString paymentHash = DECODED_PAYMENT_REQUEST.paymentHash(); - multiPathPaymentObserver.getFor(ROUTE, paymentHash); + multiPathPaymentObserver.getFor(ROUTE, PAYMENT_HASH); multiPathPaymentObserver.getFor(ROUTE_2, HexString.EMPTY); - multiPathPaymentObserver.getFor(ROUTE_3, paymentHash); + multiPathPaymentObserver.getFor(ROUTE_3, PAYMENT_HASH); Coins expectedAmount = ROUTE.getAmount().add(ROUTE_3.getAmount()); - assertThat(multiPathPaymentObserver.getInFlight(paymentHash)).isEqualTo(expectedAmount); + assertThat(multiPathPaymentObserver.getInFlight(PAYMENT_HASH)).isEqualTo(expectedAmount); } private List hops() {