add in flight at start of payment attempt

This commit is contained in:
Carsten Otto
2022-05-03 20:57:48 +02:00
parent 93da8762df
commit 0438238367
9 changed files with 67 additions and 27 deletions

View File

@@ -17,12 +17,6 @@ import java.util.List;
import java.util.Optional;
import java.util.function.Function;
import static de.cotto.lndmanagej.model.FailureCode.CHANNEL_DISABLED;
import static de.cotto.lndmanagej.model.FailureCode.INCORRECT_OR_UNKNOWN_PAYMENT_DETAILS;
import static de.cotto.lndmanagej.model.FailureCode.MPP_TIMEOUT;
import static de.cotto.lndmanagej.model.FailureCode.TEMPORARY_CHANNEL_FAILURE;
import static de.cotto.lndmanagej.model.FailureCode.UNKNOWN_NEXT_PEER;
@Service
public class LiquidityInformationUpdater implements PaymentListener {
private final GrpcGetInfo grpcGetInfo;
@@ -40,6 +34,11 @@ public class LiquidityInformationUpdater implements PaymentListener {
this.liquidityBoundsService = liquidityBoundsService;
}
@Override
public void forNewPaymentAttempt(List<PaymentAttemptHop> paymentAttemptHops) {
addInFlight(paymentAttemptHops);
}
@Override
public void success(HexString preimage, List<PaymentAttemptHop> paymentAttemptHops) {
removeInFlight(paymentAttemptHops);
@@ -68,14 +67,27 @@ public class LiquidityInformationUpdater implements PaymentListener {
}
}
private void addInFlight(List<PaymentAttemptHop> paymentAttemptHops) {
updateInFlight(paymentAttemptHops, false);
}
private void removeInFlight(List<PaymentAttemptHop> paymentAttemptHops) {
updateInFlight(paymentAttemptHops, true);
}
private void updateInFlight(List<PaymentAttemptHop> paymentAttemptHops, boolean negate) {
Pubkey startNode = grpcGetInfo.getPubkey();
for (PaymentAttemptHop hop : paymentAttemptHops) {
Pubkey endNode = getOtherNode(hop, startNode).orElse(null);
if (endNode == null) {
return;
}
liquidityBoundsService.markAsInFlight(startNode, endNode, hop.amount().negate());
Coins amount = hop.amount();
if (negate) {
liquidityBoundsService.markAsInFlight(startNode, endNode, amount.negate());
} else {
liquidityBoundsService.markAsInFlight(startNode, endNode, amount);
}
startNode = endNode;
}
}

View File

@@ -82,6 +82,14 @@ class LiquidityInformationUpdaterTest {
assertThat(liquidityInformationUpdater).isInstanceOf(PaymentListener.class);
}
@Test
void forNewPayment_adds_in_flight() {
liquidityInformationUpdater.forNewPaymentAttempt(hopsWithChannelIds);
verify(liquidityBoundsService).markAsInFlight(PUBKEY, PUBKEY_2, Coins.ofSatoshis(100));
verify(liquidityBoundsService).markAsInFlight(PUBKEY_2, PUBKEY_3, Coins.ofSatoshis(90));
verify(liquidityBoundsService).markAsInFlight(PUBKEY_3, PUBKEY_4, Coins.ofSatoshis(80));
}
@Nested
class Success {
private static final HexString PREIMAGE = new HexString("00");

View File

@@ -15,7 +15,7 @@ public class HtlcAttemptListener extends AbstractResponseListener<HTLCAttempt> {
@Override
public void acceptResponse(HTLCAttempt response, long requestId) {
paymentListenerUpdater.update(response.getPreimage(), response.getRoute(), response.getFailure());
paymentListenerUpdater.forResponse(response.getPreimage(), response.getRoute(), response.getFailure());
}
}

View File

@@ -24,10 +24,13 @@ public class PaymentListenerUpdater {
this.paymentListeners = paymentListeners;
}
public void update(ByteString preimage, Route route, Failure failure) {
List<PaymentAttemptHop> paymentAttemptHops = route.getHopsList().stream()
.map(this::toPaymentAttemptHop)
.toList();
public void forNewPaymentAttempt(Route route) {
List<PaymentAttemptHop> hops = toPaymentAttemptHops(route);
paymentListeners.forEach(paymentListener -> paymentListener.forNewPaymentAttempt(hops));
}
public void forResponse(ByteString preimage, Route route, Failure failure) {
List<PaymentAttemptHop> paymentAttemptHops = toPaymentAttemptHops(route);
if (preimage.isEmpty()) {
FailureCode failureCode = FailureCode.getFor(failure.getCodeValue());
int failureSourceIndex = failure.getFailureSourceIndex();
@@ -41,6 +44,10 @@ public class PaymentListenerUpdater {
}
}
private List<PaymentAttemptHop> toPaymentAttemptHops(Route route) {
return route.getHopsList().stream().map(this::toPaymentAttemptHop).toList();
}
private PaymentAttemptHop toPaymentAttemptHop(Hop hop) {
Optional<Pubkey> optionalPubkey;
if (hop.getPubKey().isBlank()) {
@@ -61,5 +68,4 @@ public class PaymentListenerUpdater {
optionalPubkey
);
}
}

View File

@@ -25,6 +25,7 @@ public class SendToRouteListener extends RequestResponseListener<SendToRouteRequ
@Override
public void acceptRequest(SendToRouteRequest request, long requestId) {
paymentListenerUpdater.forNewPaymentAttempt(request.getRoute());
requests.put(requestId, request);
}
@@ -34,6 +35,6 @@ public class SendToRouteListener extends RequestResponseListener<SendToRouteRequ
if (request == null) {
return;
}
paymentListenerUpdater.update(response.getPreimage(), request.getRoute(), response.getFailure());
paymentListenerUpdater.forResponse(response.getPreimage(), request.getRoute(), response.getFailure());
}
}

View File

@@ -52,7 +52,7 @@ class HtlcAttemptListenerTest {
.setRoute(route)
.build();
htlcAttemptListener.acceptResponse(response, REQUEST_ID);
verify(paymentListenerUpdater).update(PREIMAGE_BYTESTRING, route, NO_FAILURE);
verify(paymentListenerUpdater).forResponse(PREIMAGE_BYTESTRING, route, NO_FAILURE);
verifyNoMoreInteractions(paymentListenerUpdater);
}
@@ -69,7 +69,7 @@ class HtlcAttemptListenerTest {
.setRoute(route)
.build();
htlcAttemptListener.acceptResponse(response, REQUEST_ID);
verify(paymentListenerUpdater).update(NO_PREIMAGE_BYTESTRING, route, failure);
verify(paymentListenerUpdater).forResponse(NO_PREIMAGE_BYTESTRING, route, failure);
verifyNoMoreInteractions(paymentListenerUpdater);
}

View File

@@ -46,8 +46,15 @@ class PaymentListenerUpdaterTest {
}
@Test
void notifiesListenerForSuccess() {
paymentListenerUpdater.update(PREIMAGE_BYTESTRING, getRoute(), NO_FAILURE);
void notifies_listener_for_new_payment_attempt() {
paymentListenerUpdater.forNewPaymentAttempt(getRoute());
verify(paymentListener).forNewPaymentAttempt(getPaymentHops());
verifyNoMoreInteractions(paymentListener);
}
@Test
void notifies_listener_for_success() {
paymentListenerUpdater.forResponse(PREIMAGE_BYTESTRING, getRoute(), NO_FAILURE);
verify(paymentListener).success(PREIMAGE, getPaymentHops());
verifyNoMoreInteractions(paymentListener);
@@ -56,7 +63,7 @@ class PaymentListenerUpdaterTest {
@Test
void hop_without_pubkey() {
Route route = Route.newBuilder().addHops(hop(CHANNEL_ID_2, 456_000)).build();
paymentListenerUpdater.update(PREIMAGE_BYTESTRING, route, NO_FAILURE);
paymentListenerUpdater.forResponse(PREIMAGE_BYTESTRING, route, NO_FAILURE);
List<PaymentAttemptHop> paymentAttemptHops = List.of(
new PaymentAttemptHop(Optional.of(CHANNEL_ID_2), Coins.ofSatoshis(456), Optional.empty())
);
@@ -67,7 +74,7 @@ class PaymentListenerUpdaterTest {
@Test
void hop_without_channel_id_and_pubkey() {
Route route = Route.newBuilder().addHops(hop(100)).build();
paymentListenerUpdater.update(PREIMAGE_BYTESTRING, route, NO_FAILURE);
paymentListenerUpdater.forResponse(PREIMAGE_BYTESTRING, route, NO_FAILURE);
List<PaymentAttemptHop> paymentAttemptHops = List.of(
new PaymentAttemptHop(Optional.empty(), Coins.ofMilliSatoshis(100), Optional.empty())
);
@@ -78,7 +85,7 @@ class PaymentListenerUpdaterTest {
@Test
void hop_without_channel_id() {
Route route = Route.newBuilder().addHops(hop(123_000, PUBKEY)).build();
paymentListenerUpdater.update(PREIMAGE_BYTESTRING, route, NO_FAILURE);
paymentListenerUpdater.forResponse(PREIMAGE_BYTESTRING, route, NO_FAILURE);
List<PaymentAttemptHop> paymentAttemptHops = List.of(
new PaymentAttemptHop(Optional.empty(), Coins.ofSatoshis(123), Optional.of(PUBKEY))
);
@@ -92,7 +99,7 @@ class PaymentListenerUpdaterTest {
.setCode(Failure.FailureCode.TEMPORARY_CHANNEL_FAILURE)
.setFailureSourceIndex(3)
.build();
paymentListenerUpdater.update(NO_PREIMAGE_BYTESTRING, getRoute(), failure);
paymentListenerUpdater.forResponse(NO_PREIMAGE_BYTESTRING, getRoute(), failure);
List<PaymentAttemptHop> paymentAttemptHops = getPaymentHops();
verify(paymentListener).failure(paymentAttemptHops, FailureCode.TEMPORARY_CHANNEL_FAILURE, 3);

View File

@@ -22,7 +22,6 @@ import static de.cotto.lndmanagej.model.PubkeyFixtures.PUBKEY_2;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoInteractions;
import static org.mockito.Mockito.verifyNoMoreInteractions;
@ExtendWith(MockitoExtension.class)
class SendToRouteListenerTest {
@@ -49,6 +48,13 @@ class SendToRouteListenerTest {
assertThat(sendToRouteListener.getResponseType()).isEqualTo("routerrpc.SendToRouteResponse");
}
@Test
void notifies_for_request_before_response_arrives() {
Route route = getRoute();
acceptRequestWithRoute(route);
verify(paymentListenerUpdater).forNewPaymentAttempt(route);
}
@Test
void responseWithoutRequest() {
sendToRouteListener.acceptResponse(SendToRouteResponse.newBuilder().build(), REQUEST_ID);
@@ -63,8 +69,7 @@ class SendToRouteListenerTest {
.setPreimage(PREIMAGE_BYTESTRING)
.build();
sendToRouteListener.acceptResponse(response, REQUEST_ID);
verify(paymentListenerUpdater).update(PREIMAGE_BYTESTRING, route, NO_FAILURE);
verifyNoMoreInteractions(paymentListenerUpdater);
verify(paymentListenerUpdater).forResponse(PREIMAGE_BYTESTRING, route, NO_FAILURE);
}
@Test
@@ -80,8 +85,7 @@ class SendToRouteListenerTest {
.setFailure(failure)
.build();
sendToRouteListener.acceptResponse(response, REQUEST_ID);
verify(paymentListenerUpdater).update(NO_PREIMAGE_BYTESTRING, route, failure);
verifyNoMoreInteractions(paymentListenerUpdater);
verify(paymentListenerUpdater).forResponse(NO_PREIMAGE_BYTESTRING, route, failure);
}
private void acceptRequestWithRoute(Route route) {

View File

@@ -3,6 +3,8 @@ package de.cotto.lndmanagej.model;
import java.util.List;
public interface PaymentListener {
void forNewPaymentAttempt(List<PaymentAttemptHop> route);
void success(HexString preimage, List<PaymentAttemptHop> paymentAttemptHops);
void failure(List<PaymentAttemptHop> paymentAttemptHops, FailureCode failureCode, int failureSourceIndex);