skip "in-flight" payments without in-flight HTLC created more than a day ago

this is a workaround for https://github.com/lightningnetwork/lnd/issues/6834
This commit is contained in:
Carsten Otto
2022-11-16 11:10:27 +01:00
parent cbc513b7ee
commit 54569f9c36
2 changed files with 72 additions and 11 deletions

View File

@@ -20,6 +20,7 @@ import java.math.BigInteger;
import java.time.Instant;
import java.time.LocalDateTime;
import java.time.ZoneOffset;
import java.time.temporal.ChronoUnit;
import java.util.List;
import java.util.Optional;
import java.util.Set;
@@ -81,10 +82,30 @@ public class GrpcPayments {
return Optional.empty();
}
return Optional.of(list.getPaymentsList().stream()
.filter(payment -> payment.getStatus() != FAILED)
.filter(this::isPendingOrSuccessful)
.map(this::toPayment));
}
@SuppressWarnings("PMD.UnnecessaryLocalBeforeReturn")
private boolean isPendingOrSuccessful(lnrpc.Payment payment) {
if (payment.getStatus() == FAILED) {
return false;
}
if (payment.getStatus() == lnrpc.Payment.PaymentStatus.IN_FLIGHT) {
Instant creationDate = Instant.ofEpochMilli(payment.getCreationTimeNs() / 1_000_000);
Instant oneDayAgo = Instant.now().minus(1, ChronoUnit.DAYS);
boolean isYoung = creationDate.isAfter(oneDayAgo);
if (isYoung) {
return true;
}
boolean allHtlcsFailed = payment.getHtlcsList().stream()
.allMatch(htlc -> htlc.getStatus() == HTLCAttempt.HTLCStatus.FAILED);
return !allHtlcsFailed;
}
return true;
}
private Set<RouteHint> getRouteHints(Pubkey destination, List<lnrpc.RouteHint> routeHintsList) {
return routeHintsList.stream()
.filter(routeHint -> routeHint.getHopHintsCount() == 1)
@@ -104,7 +125,7 @@ public class GrpcPayments {
if (lndPayment.getStatus() != SUCCEEDED) {
return Optional.empty();
}
Instant timestamp = Instant.ofEpochMilli(lndPayment.getCreationTimeNs() / 1_000);
Instant timestamp = Instant.ofEpochMilli(lndPayment.getCreationTimeNs() / 1_000_000);
String paymentHash = lndPayment.getPaymentHash();
return Optional.of(new Payment(
lndPayment.getPaymentIndex(),

View File

@@ -25,7 +25,9 @@ import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import javax.annotation.Nullable;
import java.time.Duration;
import java.time.Instant;
import java.time.LocalDateTime;
import java.time.ZoneOffset;
import java.util.ArrayList;
import java.util.Arrays;
@@ -41,6 +43,9 @@ import static de.cotto.lndmanagej.model.PaymentFixtures.PAYMENT_2;
import static de.cotto.lndmanagej.model.PubkeyFixtures.PUBKEY;
import static de.cotto.lndmanagej.model.PubkeyFixtures.PUBKEY_3;
import static de.cotto.lndmanagej.model.PubkeyFixtures.PUBKEY_4;
import static lnrpc.HTLCAttempt.HTLCStatus.FAILED;
import static lnrpc.Payment.PaymentStatus.IN_FLIGHT;
import static lnrpc.Payment.PaymentStatus.SUCCEEDED;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyInt;
@@ -77,7 +82,7 @@ class GrpcPaymentsTest {
@Test
void with_payments() {
mockResponse(payment(PaymentStatus.SUCCEEDED, PAYMENT), payment(PaymentStatus.SUCCEEDED, PAYMENT_2));
mockResponse(payment(SUCCEEDED, PAYMENT), payment(SUCCEEDED, PAYMENT_2));
assertThat(grpcPayments.getCompletePaymentsAfter(0L)).contains(
List.of(PAYMENT, PAYMENT_2)
);
@@ -91,7 +96,7 @@ class GrpcPaymentsTest {
.setStatus(HTLCAttempt.HTLCStatus.IN_FLIGHT)
.build();
lnrpc.Payment payment = lnrpc.Payment.newBuilder()
.setStatus(PaymentStatus.SUCCEEDED)
.setStatus(SUCCEEDED)
.addHtlcs(htlc)
.build();
@@ -117,7 +122,7 @@ class GrpcPaymentsTest {
@Test
void ignores_non_settled_payment() {
mockResponse(payment(PaymentStatus.IN_FLIGHT, null));
mockResponse(payment(IN_FLIGHT, null));
assertThat(grpcPayments.getCompletePaymentsAfter(0L)).contains(List.of());
}
@@ -156,7 +161,7 @@ class GrpcPaymentsTest {
.setRoute(route)
.build();
lnrpc.Payment payment = lnrpc.Payment.newBuilder()
.setStatus(PaymentStatus.SUCCEEDED)
.setStatus(SUCCEEDED)
.setPaymentIndex(PAYMENT.index())
.setPaymentHash(PAYMENT.paymentHash())
.setValueMsat(PAYMENT.value().milliSatoshis())
@@ -184,7 +189,7 @@ class GrpcPaymentsTest {
@Test
void with_payments() {
mockResponse(payment(PaymentStatus.SUCCEEDED, PAYMENT), payment(PaymentStatus.SUCCEEDED, PAYMENT_2));
mockResponse(payment(SUCCEEDED, PAYMENT), payment(SUCCEEDED, PAYMENT_2));
assertThat(grpcPayments.getCompleteAndPendingPaymentsAfter(0L)).contains(
List.of(Optional.of(PAYMENT), Optional.of(PAYMENT_2))
);
@@ -192,7 +197,7 @@ class GrpcPaymentsTest {
@Test
void skips_failed_payment() {
mockResponse(payment(PaymentStatus.FAILED, PAYMENT), payment(PaymentStatus.SUCCEEDED, PAYMENT_2));
mockResponse(payment(PaymentStatus.FAILED, PAYMENT), payment(SUCCEEDED, PAYMENT_2));
assertThat(grpcPayments.getCompleteAndPendingPaymentsAfter(0L)).contains(
List.of(Optional.of(PAYMENT_2))
);
@@ -200,12 +205,28 @@ class GrpcPaymentsTest {
@Test
void with_in_flight_payment() {
mockResponse(payment(PaymentStatus.IN_FLIGHT, PAYMENT), payment(PaymentStatus.SUCCEEDED, PAYMENT_2));
mockResponse(payment(IN_FLIGHT, PAYMENT), payment(SUCCEEDED, PAYMENT_2));
assertThat(grpcPayments.getCompleteAndPendingPaymentsAfter(0L)).contains(
List.of(Optional.empty(), Optional.of(PAYMENT_2))
);
}
@Test
void with_recent_in_flight_payment_without_pending_htlc() {
Payment payment = paymentWithAge(Duration.ofHours(23));
mockResponse(payment(IN_FLIGHT, payment, FAILED));
assertThat(grpcPayments.getCompleteAndPendingPaymentsAfter(0L)).contains(
List.of(Optional.empty())
);
}
@Test
void skips_old_in_flight_payment_without_pending_htlc() {
Payment payment = paymentWithAge(Duration.ofHours(24));
mockResponse(payment(IN_FLIGHT, payment, FAILED));
assertThat(grpcPayments.getCompleteAndPendingPaymentsAfter(0L)).contains(List.of());
}
@Test
void starts_at_the_beginning() {
grpcPayments.getCompleteAndPendingPaymentsAfter(ADD_INDEX_OFFSET);
@@ -225,6 +246,17 @@ class GrpcPaymentsTest {
}
}
private static Payment paymentWithAge(Duration age) {
return new Payment(
PAYMENT.index(),
PAYMENT.paymentHash(),
LocalDateTime.now(ZoneOffset.UTC).minus(age),
PAYMENT.value(),
PAYMENT.fees(),
PAYMENT.routes()
);
}
@Test
void getLimit() {
assertThat(grpcPayments.getLimit()).isEqualTo(LIMIT);
@@ -307,6 +339,14 @@ class GrpcPaymentsTest {
}
private lnrpc.Payment payment(PaymentStatus paymentStatus, @Nullable Payment payment) {
return payment(paymentStatus, payment, HTLCAttempt.HTLCStatus.SUCCEEDED);
}
private lnrpc.Payment payment(
PaymentStatus paymentStatus,
@Nullable Payment payment,
HTLCAttempt.HTLCStatus htlcStatus
) {
if (payment == null) {
return lnrpc.Payment.newBuilder().setStatus(paymentStatus).build();
}
@@ -317,7 +357,7 @@ class GrpcPaymentsTest {
paymentRoute.firstHop().ifPresent(hop -> addHop(routeBuilder, hop));
paymentRoute.lastHop().ifPresent(hop -> addHop(routeBuilder, hop));
htlcBuilder.setRoute(routeBuilder.build());
htlcBuilder.setStatus(HTLCAttempt.HTLCStatus.SUCCEEDED);
htlcBuilder.setStatus(htlcStatus);
htlcs.add(htlcBuilder.build());
}
lnrpc.Payment.Builder builder = lnrpc.Payment.newBuilder()
@@ -326,7 +366,7 @@ class GrpcPaymentsTest {
.setPaymentHash(payment.paymentHash())
.setValueMsat(payment.value().milliSatoshis())
.setFeeMsat(payment.fees().milliSatoshis())
.setCreationTimeNs(payment.creationDateTime().toInstant(ZoneOffset.UTC).toEpochMilli() * 1_000);
.setCreationTimeNs(payment.creationDateTime().toInstant(ZoneOffset.UTC).toEpochMilli() * 1_000_000);
htlcs.forEach(builder::addHtlcs);
return builder.build();
}