From a8cd99a82381046ddca6a7ef5aac13f8bf16f255 Mon Sep 17 00:00:00 2001 From: Carsten Otto Date: Fri, 27 May 2022 22:28:25 +0200 Subject: [PATCH 1/3] fix defaults in sample configuration --- example-lnd-manageJ.conf | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/example-lnd-manageJ.conf b/example-lnd-manageJ.conf index 70d6a664..919f092b 100644 --- a/example-lnd-manageJ.conf +++ b/example-lnd-manageJ.conf @@ -29,10 +29,10 @@ online_changes_threshold=50 quantization=10000 piecewise_linear_approximations=5 use_mission_control=true -liquidity_information_max_age_in_seconds=3600 +liquidity_information_max_age_in_seconds=600 [top-up] -expiry_seconds=30 +expiry_seconds=1800 threshold_sat=10000 sleep_after_failure_milliseconds=500 max_retries_after_failure=5 From 36133159ef0e91826efc414a113f26bf1e086f24 Mon Sep 17 00:00:00 2001 From: Carsten Otto Date: Fri, 27 May 2022 22:41:31 +0200 Subject: [PATCH 2/3] pass payment options via POST --- .../pickhardtpayments/TopUpService.java | 2 +- .../pickhardtpayments/TopUpServiceTest.java | 5 +- .../PickhardtPaymentsControllerIT.java | 65 +++++++++----- .../PickhardtPaymentsController.java | 74 +++++++++------- .../controller/dto/PaymentOptionsDto.java | 55 ++++++++++++ .../PickhardtPaymentsControllerTest.java | 55 +++++++++--- .../controller/dto/PaymentOptionsDtoTest.java | 84 +++++++++++++++++++ 7 files changed, 275 insertions(+), 65 deletions(-) create mode 100644 web/src/main/java/de/cotto/lndmanagej/controller/dto/PaymentOptionsDto.java create mode 100644 web/src/test/java/de/cotto/lndmanagej/controller/dto/PaymentOptionsDtoTest.java diff --git a/pickhardt-payments/src/main/java/de/cotto/lndmanagej/pickhardtpayments/TopUpService.java b/pickhardt-payments/src/main/java/de/cotto/lndmanagej/pickhardtpayments/TopUpService.java index 13e1c446..bcc067ab 100644 --- a/pickhardt-payments/src/main/java/de/cotto/lndmanagej/pickhardtpayments/TopUpService.java +++ b/pickhardt-payments/src/main/java/de/cotto/lndmanagej/pickhardtpayments/TopUpService.java @@ -51,7 +51,7 @@ public class TopUpService { this.policyService = policyService; } - public PaymentStatus topUp(Pubkey pubkey, Coins amount) { + public PaymentStatus topUp(Pubkey pubkey, Coins amount, PaymentOptions paymentOptions) { if (noChannelWith(pubkey)) { String alias = nodeService.getAlias(pubkey); return PaymentStatus.createFailure("No channel with %s (%s)".formatted(pubkey, alias)); diff --git a/pickhardt-payments/src/test/java/de/cotto/lndmanagej/pickhardtpayments/TopUpServiceTest.java b/pickhardt-payments/src/test/java/de/cotto/lndmanagej/pickhardtpayments/TopUpServiceTest.java index 2609abf6..f5744517 100644 --- a/pickhardt-payments/src/test/java/de/cotto/lndmanagej/pickhardtpayments/TopUpServiceTest.java +++ b/pickhardt-payments/src/test/java/de/cotto/lndmanagej/pickhardtpayments/TopUpServiceTest.java @@ -28,6 +28,7 @@ import static de.cotto.lndmanagej.model.ChannelIdFixtures.CHANNEL_ID; import static de.cotto.lndmanagej.model.DecodedPaymentRequestFixtures.DECODED_PAYMENT_REQUEST; import static de.cotto.lndmanagej.model.LocalOpenChannelFixtures.LOCAL_OPEN_CHANNEL; import static de.cotto.lndmanagej.model.PubkeyFixtures.PUBKEY; +import static de.cotto.lndmanagej.pickhardtpayments.model.PaymentOptions.DEFAULT_PAYMENT_OPTIONS; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; @@ -182,7 +183,7 @@ class TopUpServiceTest { } private void assertTopUp(Coins expectedTopUpAmount, Duration expiry) { - PaymentStatus paymentStatus = topUpService.topUp(PUBKEY, AMOUNT); + PaymentStatus paymentStatus = topUpService.topUp(PUBKEY, AMOUNT, DEFAULT_PAYMENT_OPTIONS); verify(grpcInvoices).createPaymentRequest(expectedTopUpAmount, DESCRIPTION, expiry); PaymentOptions paymentOptions = PaymentOptions.forTopUp(OUR_FEE_RATE, PEER_FEE_RATE, PUBKEY); verify(multiPathPaymentSender).payPaymentRequest(DECODED_PAYMENT_REQUEST, paymentOptions); @@ -190,7 +191,7 @@ class TopUpServiceTest { } private void assertFailure(String reason) { - PaymentStatus paymentStatus = topUpService.topUp(PUBKEY, AMOUNT); + PaymentStatus paymentStatus = topUpService.topUp(PUBKEY, AMOUNT, DEFAULT_PAYMENT_OPTIONS); assertThat(paymentStatus.isFailure()).isTrue(); assertThat(paymentStatus.getMessages().stream().map(InstantWithString::string)).containsExactly(reason); verifyNoInteractions(multiPathPaymentSender); diff --git a/web/src/integrationTest/java/de/cotto/lndmanagej/controller/PickhardtPaymentsControllerIT.java b/web/src/integrationTest/java/de/cotto/lndmanagej/controller/PickhardtPaymentsControllerIT.java index bc84ca19..f39d0c7f 100644 --- a/web/src/integrationTest/java/de/cotto/lndmanagej/controller/PickhardtPaymentsControllerIT.java +++ b/web/src/integrationTest/java/de/cotto/lndmanagej/controller/PickhardtPaymentsControllerIT.java @@ -17,20 +17,26 @@ import org.springframework.boot.test.mock.mockito.MockBean; import org.springframework.context.annotation.Import; import org.springframework.test.web.servlet.MockMvc; +import java.util.Optional; + import static de.cotto.lndmanagej.model.ChannelIdFixtures.CHANNEL_ID; import static de.cotto.lndmanagej.model.ChannelIdFixtures.CHANNEL_ID_3; import static de.cotto.lndmanagej.model.ChannelIdFixtures.CHANNEL_ID_5; import static de.cotto.lndmanagej.model.PubkeyFixtures.PUBKEY; import static de.cotto.lndmanagej.model.PubkeyFixtures.PUBKEY_2; +import static de.cotto.lndmanagej.model.PubkeyFixtures.PUBKEY_4; import static de.cotto.lndmanagej.model.RouteFixtures.ROUTE; import static de.cotto.lndmanagej.pickhardtpayments.model.MultiPathPaymentFixtures.MULTI_PATH_PAYMENT; import static de.cotto.lndmanagej.pickhardtpayments.model.PaymentOptions.DEFAULT_PAYMENT_OPTIONS; import static org.hamcrest.Matchers.contains; import static org.hamcrest.Matchers.hasSize; import static org.hamcrest.core.Is.is; +import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +import static org.springframework.http.MediaType.APPLICATION_JSON; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; @@ -40,6 +46,20 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers. class PickhardtPaymentsControllerIT { private static final String PREFIX = "/beta/pickhardt-payments"; private static final String PAYMENT_REQUEST = "xxx"; + private static final PaymentOptions PAYMENT_OPTIONS = new PaymentOptions( + 123, + Optional.of(999L), + Optional.of(777L), + false, + Optional.of(PUBKEY_4) + ); + private static final String DTO_AS_STRING = "{" + + " \"feeRateWeight\": 123," + + " \"feeRateLimit\": 999," + + " \"feeRateLimitExceptIncomingHops\": 777," + + " \"ignoreFeesForOwnChannels\": false," + + " \"peer\": \"000000000000000000000000000000000000000000000000000000000000000004\"" + + "}"; @Autowired private MockMvc mockMvc; @@ -73,12 +93,11 @@ class PickhardtPaymentsControllerIT { } @Test - void payPaymentRequest_with_fee_rate_weight() throws Exception { - int feeRateWeight = 987; - PaymentOptions paymentOptions = PaymentOptions.forFeeRateWeight(feeRateWeight); - when(multiPathPaymentSender.payPaymentRequest(PAYMENT_REQUEST, paymentOptions)).thenReturn(paymentStatus); - String url = "%s/pay-payment-request/%s/fee-rate-weight/%d".formatted(PREFIX, PAYMENT_REQUEST, feeRateWeight); - mockMvc.perform(get(url)).andExpect(status().isOk()); + void payPaymentRequest_with_payment_options() throws Exception { + when(multiPathPaymentSender.payPaymentRequest(PAYMENT_REQUEST, PAYMENT_OPTIONS)).thenReturn(paymentStatus); + String url = "%s/pay-payment-request/%s".formatted(PREFIX, PAYMENT_REQUEST); + mockMvc.perform(post(url).contentType(APPLICATION_JSON).content(DTO_AS_STRING)) + .andExpect(status().isOk()); } @Test @@ -118,15 +137,13 @@ class PickhardtPaymentsControllerIT { } @Test - void sendTo_with_fee_rate_weight() throws Exception { - int feeRateWeight = 999; + void sendTo_with_payment_options() throws Exception { Coins amount = MULTI_PATH_PAYMENT.amount(); - PaymentOptions paymentOptions = PaymentOptions.forFeeRateWeight(feeRateWeight); - when(multiPathPaymentSplitter.getMultiPathPaymentTo(PUBKEY, amount, paymentOptions)) + when(multiPathPaymentSplitter.getMultiPathPaymentTo(PUBKEY, amount, PAYMENT_OPTIONS)) .thenReturn(MULTI_PATH_PAYMENT); - String url = "%s/to/%s/amount/%d/fee-rate-weight/%d" - .formatted(PREFIX, PUBKEY, amount.satoshis(), feeRateWeight); - mockMvc.perform(get(url)).andExpect(status().isOk()); + String url = "%s/to/%s/amount/%d".formatted(PREFIX, PUBKEY, amount.satoshis()); + mockMvc.perform(post(url).contentType(APPLICATION_JSON).content(DTO_AS_STRING)) + .andExpect(status().isOk()); } @Test @@ -152,21 +169,27 @@ class PickhardtPaymentsControllerIT { } @Test - void send_with_fee_rate_weight() throws Exception { - int feeRateWeight = 999; + void send_with_payment_options() throws Exception { Coins amount = MULTI_PATH_PAYMENT.amount(); - PaymentOptions paymentOptions = PaymentOptions.forFeeRateWeight(feeRateWeight); - when(multiPathPaymentSplitter.getMultiPathPayment(PUBKEY, PUBKEY_2, amount, paymentOptions)) + when(multiPathPaymentSplitter.getMultiPathPayment(PUBKEY, PUBKEY_2, amount, PAYMENT_OPTIONS)) .thenReturn(MULTI_PATH_PAYMENT); - String url = "%s/from/%s/to/%s/amount/%d/fee-rate-weight/%d" - .formatted(PREFIX, PUBKEY, PUBKEY_2, amount.satoshis(), feeRateWeight); - mockMvc.perform(get(url)).andExpect(status().isOk()); + String url = "%s/from/%s/to/%s/amount/%d".formatted(PREFIX, PUBKEY, PUBKEY_2, amount.satoshis()); + mockMvc.perform(post(url).contentType(APPLICATION_JSON).content(DTO_AS_STRING)) + .andExpect(status().isOk()); } @Test void topUp() throws Exception { String url = "%s/top-up/%s/amount/%s".formatted(PREFIX, PUBKEY, "123"); mockMvc.perform(get(url)).andExpect(status().isOk()); - verify(topUpService).topUp(PUBKEY, Coins.ofSatoshis(123)); + verify(topUpService).topUp(PUBKEY, Coins.ofSatoshis(123), DEFAULT_PAYMENT_OPTIONS); + } + + @Test + void topUp_with_payment_options() throws Exception { + when(topUpService.topUp(any(), any(), any())).thenReturn(new PaymentStatus(HexString.EMPTY)); + String url = "%s/top-up/%s/amount/%s".formatted(PREFIX, PUBKEY, "123"); + mockMvc.perform(post(url).contentType(APPLICATION_JSON).content(DTO_AS_STRING)).andExpect(status().isOk()); + verify(topUpService).topUp(PUBKEY, Coins.ofSatoshis(123), PAYMENT_OPTIONS); } } diff --git a/web/src/main/java/de/cotto/lndmanagej/controller/PickhardtPaymentsController.java b/web/src/main/java/de/cotto/lndmanagej/controller/PickhardtPaymentsController.java index 6d6e13ac..87d88b25 100644 --- a/web/src/main/java/de/cotto/lndmanagej/controller/PickhardtPaymentsController.java +++ b/web/src/main/java/de/cotto/lndmanagej/controller/PickhardtPaymentsController.java @@ -2,6 +2,7 @@ package de.cotto.lndmanagej.controller; import com.codahale.metrics.annotation.Timed; import de.cotto.lndmanagej.controller.dto.MultiPathPaymentDto; +import de.cotto.lndmanagej.controller.dto.PaymentOptionsDto; import de.cotto.lndmanagej.model.Coins; import de.cotto.lndmanagej.model.Pubkey; import de.cotto.lndmanagej.pickhardtpayments.MultiPathPaymentSender; @@ -14,11 +15,12 @@ import de.cotto.lndmanagej.service.GraphService; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.servlet.mvc.method.annotation.StreamingResponseBody; -import static de.cotto.lndmanagej.pickhardtpayments.model.PaymentOptions.DEFAULT_PAYMENT_OPTIONS; import static org.springframework.http.MediaType.APPLICATION_NDJSON; @RestController @@ -47,56 +49,41 @@ public class PickhardtPaymentsController { @Timed @GetMapping("/pay-payment-request/{paymentRequest}") public ResponseEntity payPaymentRequest(@PathVariable String paymentRequest) { - return payPaymentRequest(paymentRequest, DEFAULT_PAYMENT_OPTIONS.feeRateWeight()); + return payPaymentRequest(paymentRequest, new PaymentOptionsDto()); } @Timed - @GetMapping("/pay-payment-request/{paymentRequest}/fee-rate-weight/{feeRateWeight}") + @PostMapping("/pay-payment-request/{paymentRequest}") public ResponseEntity payPaymentRequest( @PathVariable String paymentRequest, - @PathVariable int feeRateWeight + @RequestBody PaymentOptionsDto paymentOptionsDto ) { - PaymentOptions paymentOptions = PaymentOptions.forFeeRateWeight(feeRateWeight); + PaymentOptions paymentOptions = paymentOptionsDto.toModel(); PaymentStatus paymentStatus = multiPathPaymentSender.payPaymentRequest(paymentRequest, paymentOptions); return toStream(paymentStatus); } - @Timed - @GetMapping("/to/{pubkey}/amount/{amount}/fee-rate-weight/{feeRateWeight}") - public MultiPathPaymentDto sendTo( - @PathVariable Pubkey pubkey, - @PathVariable long amount, - @PathVariable int feeRateWeight - ) { - Coins coins = Coins.ofSatoshis(amount); - PaymentOptions paymentOptions = PaymentOptions.forFeeRateWeight(feeRateWeight); - MultiPathPayment multiPathPaymentTo = - multiPathPaymentSplitter.getMultiPathPaymentTo(pubkey, coins, paymentOptions); - return MultiPathPaymentDto.fromModel(multiPathPaymentTo); - } - @Timed @GetMapping("/to/{pubkey}/amount/{amount}") public MultiPathPaymentDto sendTo( @PathVariable Pubkey pubkey, @PathVariable long amount ) { - return sendTo(pubkey, amount, DEFAULT_PAYMENT_OPTIONS.feeRateWeight()); + return sendTo(pubkey, amount, new PaymentOptionsDto()); } @Timed - @GetMapping("/from/{source}/to/{target}/amount/{amount}/fee-rate-weight/{feeRateWeight}") - public MultiPathPaymentDto send( - @PathVariable Pubkey source, - @PathVariable Pubkey target, + @PostMapping("/to/{pubkey}/amount/{amount}") + public MultiPathPaymentDto sendTo( + @PathVariable Pubkey pubkey, @PathVariable long amount, - @PathVariable int feeRateWeight + @RequestBody PaymentOptionsDto paymentOptionsDto ) { Coins coins = Coins.ofSatoshis(amount); - PaymentOptions paymentOptions = PaymentOptions.forFeeRateWeight(feeRateWeight); - MultiPathPayment multiPathPayment = - multiPathPaymentSplitter.getMultiPathPayment(source, target, coins, paymentOptions); - return MultiPathPaymentDto.fromModel(multiPathPayment); + PaymentOptions paymentOptions = paymentOptionsDto.toModel(); + MultiPathPayment multiPathPaymentTo = + multiPathPaymentSplitter.getMultiPathPaymentTo(pubkey, coins, paymentOptions); + return MultiPathPaymentDto.fromModel(multiPathPaymentTo); } @Timed @@ -106,13 +93,38 @@ public class PickhardtPaymentsController { @PathVariable Pubkey target, @PathVariable long amount ) { - return send(source, target, amount, DEFAULT_PAYMENT_OPTIONS.feeRateWeight()); + return send(source, target, amount, new PaymentOptionsDto()); + } + + @Timed + @PostMapping("/from/{source}/to/{target}/amount/{amount}") + public MultiPathPaymentDto send( + @PathVariable Pubkey source, + @PathVariable Pubkey target, + @PathVariable long amount, + @RequestBody PaymentOptionsDto paymentOptionsDto + ) { + Coins coins = Coins.ofSatoshis(amount); + PaymentOptions paymentOptions = paymentOptionsDto.toModel(); + MultiPathPayment multiPathPayment = + multiPathPaymentSplitter.getMultiPathPayment(source, target, coins, paymentOptions); + return MultiPathPaymentDto.fromModel(multiPathPayment); } @Timed @GetMapping("/top-up/{pubkey}/amount/{amount}") public ResponseEntity topUp(@PathVariable Pubkey pubkey, @PathVariable long amount) { - PaymentStatus paymentStatus = topUpService.topUp(pubkey, Coins.ofSatoshis(amount)); + return topUp(pubkey, amount, new PaymentOptionsDto()); + } + + @Timed + @PostMapping("/top-up/{pubkey}/amount/{amount}") + public ResponseEntity topUp( + @PathVariable Pubkey pubkey, + @PathVariable long amount, + @RequestBody PaymentOptionsDto paymentOptionsDto + ) { + PaymentStatus paymentStatus = topUpService.topUp(pubkey, Coins.ofSatoshis(amount), paymentOptionsDto.toModel()); return toStream(paymentStatus); } diff --git a/web/src/main/java/de/cotto/lndmanagej/controller/dto/PaymentOptionsDto.java b/web/src/main/java/de/cotto/lndmanagej/controller/dto/PaymentOptionsDto.java new file mode 100644 index 00000000..7fb017a7 --- /dev/null +++ b/web/src/main/java/de/cotto/lndmanagej/controller/dto/PaymentOptionsDto.java @@ -0,0 +1,55 @@ +package de.cotto.lndmanagej.controller.dto; + +import de.cotto.lndmanagej.model.Pubkey; +import de.cotto.lndmanagej.pickhardtpayments.model.PaymentOptions; + +import javax.annotation.Nullable; +import java.util.Optional; + +import static de.cotto.lndmanagej.pickhardtpayments.model.PaymentOptions.DEFAULT_PAYMENT_OPTIONS; + +public class PaymentOptionsDto { + private int feeRateWeight; + @Nullable + private Long feeRateLimit; + private boolean ignoreFeesForOwnChannels; + @Nullable + private Pubkey peer; + @Nullable + private Long feeRateLimitExceptIncomingHops; + + public PaymentOptionsDto() { + feeRateWeight = DEFAULT_PAYMENT_OPTIONS.feeRateWeight(); + ignoreFeesForOwnChannels = DEFAULT_PAYMENT_OPTIONS.ignoreFeesForOwnChannels(); + } + + public PaymentOptions toModel() { + return new PaymentOptions( + feeRateWeight, + Optional.ofNullable(feeRateLimit), + Optional.ofNullable(feeRateLimitExceptIncomingHops), + ignoreFeesForOwnChannels, + Optional.ofNullable(peer) + ); + } + + public void setFeeRateWeight(int feeRateWeight) { + this.feeRateWeight = feeRateWeight; + } + + public void setFeeRateLimit(@Nullable Long feeRateLimit) { + this.feeRateLimit = feeRateLimit; + } + + public void setIgnoreFeesForOwnChannels(boolean ignoreFeesForOwnChannels) { + this.ignoreFeesForOwnChannels = ignoreFeesForOwnChannels; + } + + public void setPeer(@Nullable Pubkey peer) { + this.peer = peer; + } + + public void setFeeRateLimitExceptIncomingHops(@Nullable Long feeRateLimitExceptIncomingHops) { + this.feeRateLimitExceptIncomingHops = feeRateLimitExceptIncomingHops; + } +} diff --git a/web/src/test/java/de/cotto/lndmanagej/controller/PickhardtPaymentsControllerTest.java b/web/src/test/java/de/cotto/lndmanagej/controller/PickhardtPaymentsControllerTest.java index 93ed3a32..188c0ba6 100644 --- a/web/src/test/java/de/cotto/lndmanagej/controller/PickhardtPaymentsControllerTest.java +++ b/web/src/test/java/de/cotto/lndmanagej/controller/PickhardtPaymentsControllerTest.java @@ -1,6 +1,7 @@ package de.cotto.lndmanagej.controller; import de.cotto.lndmanagej.controller.dto.MultiPathPaymentDto; +import de.cotto.lndmanagej.controller.dto.PaymentOptionsDto; import de.cotto.lndmanagej.model.Coins; import de.cotto.lndmanagej.model.HexString; import de.cotto.lndmanagej.pickhardtpayments.MultiPathPaymentSender; @@ -18,9 +19,11 @@ import org.mockito.junit.jupiter.MockitoExtension; import org.springframework.http.HttpStatus; import java.nio.charset.StandardCharsets; +import java.util.Optional; import static de.cotto.lndmanagej.model.PubkeyFixtures.PUBKEY; import static de.cotto.lndmanagej.model.PubkeyFixtures.PUBKEY_2; +import static de.cotto.lndmanagej.model.PubkeyFixtures.PUBKEY_4; import static de.cotto.lndmanagej.pickhardtpayments.model.MultiPathPaymentFixtures.MULTI_PATH_PAYMENT; import static de.cotto.lndmanagej.pickhardtpayments.model.PaymentOptions.DEFAULT_PAYMENT_OPTIONS; import static org.assertj.core.api.Assertions.assertThat; @@ -35,6 +38,27 @@ class PickhardtPaymentsControllerTest { private static final String PAYMENT_REQUEST = "xxx"; private static final String STREAM_RESPONSE = "beep beep boop!"; + private static final PaymentOptions PAYMENT_OPTIONS; + private static final PaymentOptionsDto PAYMENT_OPTIONS_DTO; + + static { + PAYMENT_OPTIONS = new PaymentOptions( + 123, + Optional.of(999L), + Optional.of(777L), + false, + Optional.of(PUBKEY_4) + ); + PAYMENT_OPTIONS_DTO = new PaymentOptionsDto(); + PAYMENT_OPTIONS_DTO.setFeeRateWeight(PAYMENT_OPTIONS.feeRateWeight()); + PAYMENT_OPTIONS_DTO.setFeeRateLimit(PAYMENT_OPTIONS.feeRateLimit().orElse(null)); + PAYMENT_OPTIONS_DTO.setFeeRateLimitExceptIncomingHops( + PAYMENT_OPTIONS.feeRateLimitExceptIncomingHops().orElse(null) + ); + PAYMENT_OPTIONS_DTO.setIgnoreFeesForOwnChannels(PAYMENT_OPTIONS.ignoreFeesForOwnChannels()); + PAYMENT_OPTIONS_DTO.setPeer(PAYMENT_OPTIONS.peer().orElse(null)); + } + @InjectMocks private PickhardtPaymentsController controller; @@ -70,10 +94,10 @@ class PickhardtPaymentsControllerTest { } @Test - void payPaymentRequest_with_fee_rate_weight() { + void payPaymentRequest_with_payment_options() { PaymentOptions paymentOptions = PaymentOptions.forFeeRateWeight(456); when(multiPathPaymentSender.payPaymentRequest(PAYMENT_REQUEST, paymentOptions)).thenReturn(paymentStatus); - assertThat(controller.payPaymentRequest(PAYMENT_REQUEST, 456).getStatusCode()) + assertThat(controller.payPaymentRequest(PAYMENT_REQUEST, withFeeRateWeight(456)).getStatusCode()) .isEqualTo(HttpStatus.OK); } @@ -86,12 +110,12 @@ class PickhardtPaymentsControllerTest { } @Test - void sendTo_with_fee_rate_weight() { + void sendTo_with_payment_options() { int feeRateWeight = 10; PaymentOptions paymentOptions = PaymentOptions.forFeeRateWeight(feeRateWeight); when(multiPathPaymentSplitter.getMultiPathPaymentTo(PUBKEY, Coins.ofSatoshis(456), paymentOptions)) .thenReturn(MULTI_PATH_PAYMENT); - assertThat(controller.sendTo(PUBKEY, 456, feeRateWeight)) + assertThat(controller.sendTo(PUBKEY, 456, withFeeRateWeight(feeRateWeight))) .isEqualTo(MultiPathPaymentDto.fromModel(MULTI_PATH_PAYMENT)); } @@ -108,22 +132,27 @@ class PickhardtPaymentsControllerTest { } @Test - void send_with_fee_rate_weight() { - int feeRateWeight = 20; + void send_with_payment_options() { when(multiPathPaymentSplitter.getMultiPathPayment( PUBKEY, PUBKEY_2, Coins.ofSatoshis(123), - PaymentOptions.forFeeRateWeight(feeRateWeight) + PAYMENT_OPTIONS )).thenReturn(MULTI_PATH_PAYMENT); - assertThat(controller.send(PUBKEY, PUBKEY_2, 123, feeRateWeight)) + assertThat(controller.send(PUBKEY, PUBKEY_2, 123, PAYMENT_OPTIONS_DTO)) .isEqualTo(MultiPathPaymentDto.fromModel(MULTI_PATH_PAYMENT)); } @Test void topUp() { - controller.topUp(PUBKEY, 123); - verify(topUpService).topUp(PUBKEY, Coins.ofSatoshis(123)); + assertThat(controller.topUp(PUBKEY, 123).getStatusCode()).isEqualTo(HttpStatus.OK); + verify(topUpService).topUp(PUBKEY, Coins.ofSatoshis(123), DEFAULT_PAYMENT_OPTIONS); + } + + @Test + void topUp_with_payment_options() { + assertThat(controller.topUp(PUBKEY, 123, PAYMENT_OPTIONS_DTO).getStatusCode()).isEqualTo(HttpStatus.OK); + verify(topUpService).topUp(PUBKEY, Coins.ofSatoshis(123), PAYMENT_OPTIONS); } @Test @@ -131,4 +160,10 @@ class PickhardtPaymentsControllerTest { controller.resetGraph(); verify(graphService).resetCache(); } + + private static PaymentOptionsDto withFeeRateWeight(int feeRateWeight) { + PaymentOptionsDto dto = new PaymentOptionsDto(); + dto.setFeeRateWeight(feeRateWeight); + return dto; + } } diff --git a/web/src/test/java/de/cotto/lndmanagej/controller/dto/PaymentOptionsDtoTest.java b/web/src/test/java/de/cotto/lndmanagej/controller/dto/PaymentOptionsDtoTest.java new file mode 100644 index 00000000..4de0e253 --- /dev/null +++ b/web/src/test/java/de/cotto/lndmanagej/controller/dto/PaymentOptionsDtoTest.java @@ -0,0 +1,84 @@ +package de.cotto.lndmanagej.controller.dto; + +import de.cotto.lndmanagej.pickhardtpayments.model.PaymentOptions; +import org.junit.jupiter.api.Test; + +import java.util.Optional; + +import static de.cotto.lndmanagej.model.PubkeyFixtures.PUBKEY; +import static de.cotto.lndmanagej.pickhardtpayments.model.PaymentOptions.DEFAULT_PAYMENT_OPTIONS; +import static org.assertj.core.api.Assertions.assertThat; + +class PaymentOptionsDtoTest { + @Test + void toModel_default() { + assertThat(new PaymentOptionsDto().toModel()).isEqualTo(DEFAULT_PAYMENT_OPTIONS); + } + + @Test + void just_fee_rate_weight() { + PaymentOptionsDto dto = new PaymentOptionsDto(); + dto.setFeeRateWeight(123); + assertThat(dto.toModel()).isEqualTo(PaymentOptions.forFeeRateWeight(123)); + } + + @Test + void feeRateLimit() { + PaymentOptionsDto dto = new PaymentOptionsDto(); + long feeRateLimit = 555; + dto.setFeeRateLimit(feeRateLimit); + PaymentOptions expected = new PaymentOptions( + DEFAULT_PAYMENT_OPTIONS.feeRateWeight(), + Optional.of(feeRateLimit), + Optional.empty(), + DEFAULT_PAYMENT_OPTIONS.ignoreFeesForOwnChannels(), + Optional.empty() + ); + assertThat(dto.toModel()).isEqualTo(expected); + } + + @Test + void feeRateLimit_and_feeRateLimitExceptIncomingHops() { + PaymentOptionsDto dto = new PaymentOptionsDto(); + long feeRateLimit = 555; + long feeRateLimitExceptIncomingHops = 777; + dto.setFeeRateLimit(feeRateLimit); + dto.setFeeRateLimitExceptIncomingHops(feeRateLimitExceptIncomingHops); + PaymentOptions expected = new PaymentOptions( + DEFAULT_PAYMENT_OPTIONS.feeRateWeight(), + Optional.of(feeRateLimit), + Optional.of(feeRateLimitExceptIncomingHops), + DEFAULT_PAYMENT_OPTIONS.ignoreFeesForOwnChannels(), + Optional.empty() + ); + assertThat(dto.toModel()).isEqualTo(expected); + } + + @Test + void ignoreFeesForOwnChannels() { + PaymentOptionsDto dto = new PaymentOptionsDto(); + dto.setIgnoreFeesForOwnChannels(false); + PaymentOptions expected = new PaymentOptions( + DEFAULT_PAYMENT_OPTIONS.feeRateWeight(), + Optional.empty(), + Optional.empty(), + false, + Optional.empty() + ); + assertThat(dto.toModel()).isEqualTo(expected); + } + + @Test + void peer() { + PaymentOptionsDto dto = new PaymentOptionsDto(); + dto.setPeer(PUBKEY); + PaymentOptions expected = new PaymentOptions( + DEFAULT_PAYMENT_OPTIONS.feeRateWeight(), + Optional.empty(), + Optional.empty(), + DEFAULT_PAYMENT_OPTIONS.ignoreFeesForOwnChannels(), + Optional.of(PUBKEY) + ); + assertThat(dto.toModel()).isEqualTo(expected); + } +} From 51d0338e5e40213e9a9c981d2cbf2c3316a23c2f Mon Sep 17 00:00:00 2001 From: Carsten Otto Date: Fri, 27 May 2022 23:09:36 +0200 Subject: [PATCH 3/3] use payment options for top-up --- .../pickhardtpayments/FlowComputation.java | 3 +- .../pickhardtpayments/TopUpService.java | 27 +++- .../model/PaymentOptions.java | 20 ++- .../pickhardtpayments/TopUpServiceTest.java | 129 +++++++++++++++++- .../model/PaymentOptionsTest.java | 27 +++- .../PickhardtPaymentsControllerIT.java | 2 +- .../PickhardtPaymentsController.java | 10 +- .../controller/dto/PaymentOptionsDto.java | 15 +- .../PickhardtPaymentsControllerTest.java | 4 +- .../controller/dto/PaymentOptionsDtoTest.java | 10 +- 10 files changed, 213 insertions(+), 34 deletions(-) diff --git a/pickhardt-payments/src/main/java/de/cotto/lndmanagej/pickhardtpayments/FlowComputation.java b/pickhardt-payments/src/main/java/de/cotto/lndmanagej/pickhardtpayments/FlowComputation.java index 9432a2a1..57da8948 100644 --- a/pickhardt-payments/src/main/java/de/cotto/lndmanagej/pickhardtpayments/FlowComputation.java +++ b/pickhardt-payments/src/main/java/de/cotto/lndmanagej/pickhardtpayments/FlowComputation.java @@ -13,6 +13,7 @@ import java.util.Map; import static de.cotto.lndmanagej.configuration.PickhardtPaymentsConfigurationSettings.PIECEWISE_LINEAR_APPROXIMATIONS; import static de.cotto.lndmanagej.configuration.PickhardtPaymentsConfigurationSettings.QUANTIZATION; +import static de.cotto.lndmanagej.pickhardtpayments.model.PaymentOptions.DEFAULT_PAYMENT_OPTIONS; @Component public class FlowComputation { @@ -45,7 +46,7 @@ public class FlowComputation { Map.of(target, amount), quantization, piecewiseLinearApproximations, - paymentOptions.feeRateWeight(), + paymentOptions.feeRateWeight().orElse(DEFAULT_PAYMENT_OPTIONS.feeRateWeight().orElseThrow()), grpcGetInfo.getPubkey(), paymentOptions.ignoreFeesForOwnChannels() ); diff --git a/pickhardt-payments/src/main/java/de/cotto/lndmanagej/pickhardtpayments/TopUpService.java b/pickhardt-payments/src/main/java/de/cotto/lndmanagej/pickhardtpayments/TopUpService.java index bcc067ab..cff73d33 100644 --- a/pickhardt-payments/src/main/java/de/cotto/lndmanagej/pickhardtpayments/TopUpService.java +++ b/pickhardt-payments/src/main/java/de/cotto/lndmanagej/pickhardtpayments/TopUpService.java @@ -51,7 +51,7 @@ public class TopUpService { this.policyService = policyService; } - public PaymentStatus topUp(Pubkey pubkey, Coins amount, PaymentOptions paymentOptions) { + public PaymentStatus topUp(Pubkey pubkey, Coins amount, PaymentOptions paymentOptionsFromRequest) { if (noChannelWith(pubkey)) { String alias = nodeService.getAlias(pubkey); return PaymentStatus.createFailure("No channel with %s (%s)".formatted(pubkey, alias)); @@ -71,10 +71,10 @@ public class TopUpService { return PaymentStatus.createFailure(reason); } - return sendPayment(pubkey, topUpAmount); + return sendPayment(pubkey, topUpAmount, paymentOptionsFromRequest); } - private PaymentStatus sendPayment(Pubkey pubkey, Coins topUpAmount) { + private PaymentStatus sendPayment(Pubkey pubkey, Coins topUpAmount, PaymentOptions paymentOptionsFromRequest) { long ourFeeRate = policyService.getMinimumFeeRateTo(pubkey).orElse(0L); long peerFeeRate = policyService.getMinimumFeeRateFrom(pubkey).orElse(0L); if (peerFeeRate >= ourFeeRate) { @@ -88,7 +88,7 @@ public class TopUpService { String alias = nodeService.getAlias(pubkey); return PaymentStatus.createFailure("Unable to create payment request (%s, %s)".formatted(pubkey, alias)); } - PaymentOptions paymentOptions = PaymentOptions.forTopUp(ourFeeRate, peerFeeRate, pubkey); + PaymentOptions paymentOptions = getPaymentOptions(pubkey, ourFeeRate, peerFeeRate, paymentOptionsFromRequest); return multiPathPaymentSender.payPaymentRequest(paymentRequest, paymentOptions); } @@ -114,4 +114,23 @@ public class TopUpService { ChannelId channelId = channelService.getOpenChannelsWith(pubkey).iterator().next().getId(); return "Topping up channel %s with %s (%s)".formatted(channelId, pubkey, alias); } + + private PaymentOptions getPaymentOptions( + Pubkey pubkey, + long ourFeeRate, + long peerFeeRate, + PaymentOptions paymentOptionsFromRequest + ) { + long feeRateLimitExceptIncomingHops = Math.min( + ourFeeRate - peerFeeRate, + paymentOptionsFromRequest.feeRateLimitExceptIncomingHops().orElse(Long.MAX_VALUE) + ); + return new PaymentOptions( + Optional.of(paymentOptionsFromRequest.feeRateWeight().orElse(5)), + Optional.of(Math.min(ourFeeRate, paymentOptionsFromRequest.feeRateLimit().orElse(Long.MAX_VALUE))), + Optional.of(feeRateLimitExceptIncomingHops), + false, + Optional.of(pubkey) + ); + } } diff --git a/pickhardt-payments/src/main/java/de/cotto/lndmanagej/pickhardtpayments/model/PaymentOptions.java b/pickhardt-payments/src/main/java/de/cotto/lndmanagej/pickhardtpayments/model/PaymentOptions.java index df04ad8b..78dfcb3f 100644 --- a/pickhardt-payments/src/main/java/de/cotto/lndmanagej/pickhardtpayments/model/PaymentOptions.java +++ b/pickhardt-payments/src/main/java/de/cotto/lndmanagej/pickhardtpayments/model/PaymentOptions.java @@ -5,7 +5,7 @@ import de.cotto.lndmanagej.model.Pubkey; import java.util.Optional; public record PaymentOptions( - int feeRateWeight, + Optional feeRateWeight, Optional feeRateLimit, Optional feeRateLimitExceptIncomingHops, boolean ignoreFeesForOwnChannels, @@ -14,16 +14,28 @@ public record PaymentOptions( public static final PaymentOptions DEFAULT_PAYMENT_OPTIONS = forFeeRateWeight(0); public static PaymentOptions forFeeRateWeight(int feeRateWeight) { - return new PaymentOptions(feeRateWeight, Optional.empty(), Optional.empty(), true, Optional.empty()); + return new PaymentOptions( + Optional.of(feeRateWeight), + Optional.empty(), + Optional.empty(), + true, + Optional.empty() + ); } public static PaymentOptions forFeeRateLimit(long feeRateLimit) { - return new PaymentOptions(0, Optional.of(feeRateLimit), Optional.empty(), true, Optional.empty()); + return new PaymentOptions( + Optional.of(0), + Optional.of(feeRateLimit), + Optional.empty(), + true, + Optional.empty() + ); } public static PaymentOptions forTopUp(long ourFeeRate, long peerFeeRate, Pubkey peer) { return new PaymentOptions( - 5, + Optional.of(5), Optional.of(ourFeeRate), Optional.of(ourFeeRate - peerFeeRate), false, diff --git a/pickhardt-payments/src/test/java/de/cotto/lndmanagej/pickhardtpayments/TopUpServiceTest.java b/pickhardt-payments/src/test/java/de/cotto/lndmanagej/pickhardtpayments/TopUpServiceTest.java index f5744517..83eba280 100644 --- a/pickhardt-payments/src/test/java/de/cotto/lndmanagej/pickhardtpayments/TopUpServiceTest.java +++ b/pickhardt-payments/src/test/java/de/cotto/lndmanagej/pickhardtpayments/TopUpServiceTest.java @@ -163,6 +163,113 @@ class TopUpServiceTest { assertTopUp(AMOUNT, DEFAULT_EXPIRY); } + // CPD-OFF + @Test + void uses_lower_fee_rate_limit_if_configured() { + long feeRateLimit = 111L; + PaymentOptions given = new PaymentOptions( + Optional.empty(), + Optional.of(feeRateLimit), + Optional.empty(), + true, + Optional.empty() + ); + PaymentOptions expected = new PaymentOptions( + Optional.of(5), + Optional.of(feeRateLimit), + Optional.of(OUR_FEE_RATE - PEER_FEE_RATE), + false, + Optional.of(PUBKEY) + ); + when(balanceService.getAvailableLocalBalanceForPeer(PUBKEY)).thenReturn(Coins.NONE); + assertTopUp(AMOUNT, DEFAULT_EXPIRY, given, expected); + } + + @Test + void ignores_configured_lower_fee_rate_limit_if_too_high() { + long feeRateLimit = OUR_FEE_RATE + 1; + PaymentOptions given = new PaymentOptions( + Optional.empty(), + Optional.of(feeRateLimit), + Optional.empty(), + true, + Optional.empty() + ); + PaymentOptions expected = new PaymentOptions( + Optional.of(5), + Optional.of(OUR_FEE_RATE), + Optional.of(OUR_FEE_RATE - PEER_FEE_RATE), + false, + Optional.of(PUBKEY) + ); + when(balanceService.getAvailableLocalBalanceForPeer(PUBKEY)).thenReturn(Coins.NONE); + assertTopUp(AMOUNT, DEFAULT_EXPIRY, given, expected); + } + + @Test + void uses_lower_fee_rate_limit_except_incoming_hops_if_configured() { + long feeRateLimitExceptIncomingHops = 0L; + PaymentOptions given = new PaymentOptions( + Optional.empty(), + Optional.empty(), + Optional.of(feeRateLimitExceptIncomingHops), + true, + Optional.empty() + ); + PaymentOptions expected = new PaymentOptions( + Optional.of(5), + Optional.of(OUR_FEE_RATE), + Optional.of(feeRateLimitExceptIncomingHops), + false, + Optional.of(PUBKEY) + ); + when(balanceService.getAvailableLocalBalanceForPeer(PUBKEY)).thenReturn(Coins.NONE); + assertTopUp(AMOUNT, DEFAULT_EXPIRY, given, expected); + } + + @Test + void ignores_configured_lower_fee_rate_limit_except_incoming_hops_if_too_high() { + long feeRateLimitExceptIncomingHops = 2L; + PaymentOptions given = new PaymentOptions( + Optional.empty(), + Optional.empty(), + Optional.of(feeRateLimitExceptIncomingHops), + true, + Optional.empty() + ); + PaymentOptions expected = new PaymentOptions( + Optional.of(5), + Optional.of(OUR_FEE_RATE), + Optional.of(OUR_FEE_RATE - PEER_FEE_RATE), + false, + Optional.of(PUBKEY) + ); + when(balanceService.getAvailableLocalBalanceForPeer(PUBKEY)).thenReturn(Coins.NONE); + assertTopUp(AMOUNT, DEFAULT_EXPIRY, given, expected); + } + + @Test + void uses_fee_rate_weight_if_configured() { + int feeRateWeight = 0; + PaymentOptions given = new PaymentOptions( + Optional.of(feeRateWeight), + Optional.empty(), + Optional.empty(), + true, + Optional.empty() + ); + PaymentOptions expected = new PaymentOptions( + Optional.of(feeRateWeight), + Optional.of(OUR_FEE_RATE), + Optional.of(OUR_FEE_RATE - PEER_FEE_RATE), + false, + Optional.of(PUBKEY) + ); + when(balanceService.getAvailableLocalBalanceForPeer(PUBKEY)).thenReturn(Coins.NONE); + assertTopUp(AMOUNT, DEFAULT_EXPIRY, given, expected); + } + // CPD-ON + @Test void uses_configured_expiry() { int expiry = 900; @@ -183,10 +290,26 @@ class TopUpServiceTest { } private void assertTopUp(Coins expectedTopUpAmount, Duration expiry) { - PaymentStatus paymentStatus = topUpService.topUp(PUBKEY, AMOUNT, DEFAULT_PAYMENT_OPTIONS); - verify(grpcInvoices).createPaymentRequest(expectedTopUpAmount, DESCRIPTION, expiry); PaymentOptions paymentOptions = PaymentOptions.forTopUp(OUR_FEE_RATE, PEER_FEE_RATE, PUBKEY); - verify(multiPathPaymentSender).payPaymentRequest(DECODED_PAYMENT_REQUEST, paymentOptions); + PaymentOptions emptyPaymentOptions = new PaymentOptions( + Optional.empty(), + Optional.empty(), + Optional.empty(), + true, + Optional.empty() + ); + assertTopUp(expectedTopUpAmount, expiry, emptyPaymentOptions, paymentOptions); + } + + private void assertTopUp( + Coins expectedTopUpAmount, + Duration expiry, + PaymentOptions givenPaymentOptions, + PaymentOptions expectedPaymentOptions + ) { + PaymentStatus paymentStatus = topUpService.topUp(PUBKEY, AMOUNT, givenPaymentOptions); + verify(grpcInvoices).createPaymentRequest(expectedTopUpAmount, DESCRIPTION, expiry); + verify(multiPathPaymentSender).payPaymentRequest(DECODED_PAYMENT_REQUEST, expectedPaymentOptions); assertThat(paymentStatus.isPending()).isTrue(); } diff --git a/pickhardt-payments/src/test/java/de/cotto/lndmanagej/pickhardtpayments/model/PaymentOptionsTest.java b/pickhardt-payments/src/test/java/de/cotto/lndmanagej/pickhardtpayments/model/PaymentOptionsTest.java index bda88956..90fa9794 100644 --- a/pickhardt-payments/src/test/java/de/cotto/lndmanagej/pickhardtpayments/model/PaymentOptionsTest.java +++ b/pickhardt-payments/src/test/java/de/cotto/lndmanagej/pickhardtpayments/model/PaymentOptionsTest.java @@ -11,20 +11,35 @@ import static org.assertj.core.api.Assertions.assertThat; class PaymentOptionsTest { @Test void forFeeRateWeight() { - assertThat(PaymentOptions.forFeeRateWeight(12)) - .isEqualTo(new PaymentOptions(12, Optional.empty(), Optional.empty(), true, Optional.empty())); + assertThat(PaymentOptions.forFeeRateWeight(12)).isEqualTo(new PaymentOptions( + Optional.of(12), + Optional.empty(), + Optional.empty(), + true, + Optional.empty() + )); } @Test void forFeeRateLimit() { - assertThat(PaymentOptions.forFeeRateLimit(123)) - .isEqualTo(new PaymentOptions(0, Optional.of(123L), Optional.empty(), true, Optional.empty())); + assertThat(PaymentOptions.forFeeRateLimit(123)).isEqualTo(new PaymentOptions( + Optional.of(0), + Optional.of(123L), + Optional.empty(), + true, + Optional.empty() + )); } @Test void forTopUp() { - assertThat(PaymentOptions.forTopUp(123, 100, PUBKEY)) - .isEqualTo(new PaymentOptions(5, Optional.of(123L), Optional.of(23L), false, Optional.of(PUBKEY))); + assertThat(PaymentOptions.forTopUp(123, 100, PUBKEY)).isEqualTo(new PaymentOptions( + Optional.of(5), + Optional.of(123L), + Optional.of(23L), + false, + Optional.of(PUBKEY) + )); } @Test diff --git a/web/src/integrationTest/java/de/cotto/lndmanagej/controller/PickhardtPaymentsControllerIT.java b/web/src/integrationTest/java/de/cotto/lndmanagej/controller/PickhardtPaymentsControllerIT.java index f39d0c7f..fab143da 100644 --- a/web/src/integrationTest/java/de/cotto/lndmanagej/controller/PickhardtPaymentsControllerIT.java +++ b/web/src/integrationTest/java/de/cotto/lndmanagej/controller/PickhardtPaymentsControllerIT.java @@ -47,7 +47,7 @@ class PickhardtPaymentsControllerIT { private static final String PREFIX = "/beta/pickhardt-payments"; private static final String PAYMENT_REQUEST = "xxx"; private static final PaymentOptions PAYMENT_OPTIONS = new PaymentOptions( - 123, + Optional.of(123), Optional.of(999L), Optional.of(777L), false, diff --git a/web/src/main/java/de/cotto/lndmanagej/controller/PickhardtPaymentsController.java b/web/src/main/java/de/cotto/lndmanagej/controller/PickhardtPaymentsController.java index 87d88b25..74abe708 100644 --- a/web/src/main/java/de/cotto/lndmanagej/controller/PickhardtPaymentsController.java +++ b/web/src/main/java/de/cotto/lndmanagej/controller/PickhardtPaymentsController.java @@ -26,6 +26,8 @@ import static org.springframework.http.MediaType.APPLICATION_NDJSON; @RestController @RequestMapping("/beta/pickhardt-payments/") public class PickhardtPaymentsController { + private static final PaymentOptionsDto PAYMENT_OPTIONS_DTO = PaymentOptionsDto.DEFAULT; + private final MultiPathPaymentSplitter multiPathPaymentSplitter; private final MultiPathPaymentSender multiPathPaymentSender; private final PaymentStatusStream paymentStatusStream; @@ -49,7 +51,7 @@ public class PickhardtPaymentsController { @Timed @GetMapping("/pay-payment-request/{paymentRequest}") public ResponseEntity payPaymentRequest(@PathVariable String paymentRequest) { - return payPaymentRequest(paymentRequest, new PaymentOptionsDto()); + return payPaymentRequest(paymentRequest, PAYMENT_OPTIONS_DTO); } @Timed @@ -69,7 +71,7 @@ public class PickhardtPaymentsController { @PathVariable Pubkey pubkey, @PathVariable long amount ) { - return sendTo(pubkey, amount, new PaymentOptionsDto()); + return sendTo(pubkey, amount, PAYMENT_OPTIONS_DTO); } @Timed @@ -93,7 +95,7 @@ public class PickhardtPaymentsController { @PathVariable Pubkey target, @PathVariable long amount ) { - return send(source, target, amount, new PaymentOptionsDto()); + return send(source, target, amount, PAYMENT_OPTIONS_DTO); } @Timed @@ -114,7 +116,7 @@ public class PickhardtPaymentsController { @Timed @GetMapping("/top-up/{pubkey}/amount/{amount}") public ResponseEntity topUp(@PathVariable Pubkey pubkey, @PathVariable long amount) { - return topUp(pubkey, amount, new PaymentOptionsDto()); + return topUp(pubkey, amount, PAYMENT_OPTIONS_DTO); } @Timed diff --git a/web/src/main/java/de/cotto/lndmanagej/controller/dto/PaymentOptionsDto.java b/web/src/main/java/de/cotto/lndmanagej/controller/dto/PaymentOptionsDto.java index 7fb017a7..b10ff94d 100644 --- a/web/src/main/java/de/cotto/lndmanagej/controller/dto/PaymentOptionsDto.java +++ b/web/src/main/java/de/cotto/lndmanagej/controller/dto/PaymentOptionsDto.java @@ -9,7 +9,14 @@ import java.util.Optional; import static de.cotto.lndmanagej.pickhardtpayments.model.PaymentOptions.DEFAULT_PAYMENT_OPTIONS; public class PaymentOptionsDto { - private int feeRateWeight; + public static final PaymentOptionsDto DEFAULT = new PaymentOptionsDto(); + + static { + DEFAULT.setFeeRateWeight(DEFAULT_PAYMENT_OPTIONS.feeRateWeight().orElse(null)); + } + + @Nullable + private Integer feeRateWeight; @Nullable private Long feeRateLimit; private boolean ignoreFeesForOwnChannels; @@ -19,13 +26,13 @@ public class PaymentOptionsDto { private Long feeRateLimitExceptIncomingHops; public PaymentOptionsDto() { - feeRateWeight = DEFAULT_PAYMENT_OPTIONS.feeRateWeight(); ignoreFeesForOwnChannels = DEFAULT_PAYMENT_OPTIONS.ignoreFeesForOwnChannels(); } public PaymentOptions toModel() { + return new PaymentOptions( - feeRateWeight, + Optional.ofNullable(feeRateWeight), Optional.ofNullable(feeRateLimit), Optional.ofNullable(feeRateLimitExceptIncomingHops), ignoreFeesForOwnChannels, @@ -33,7 +40,7 @@ public class PaymentOptionsDto { ); } - public void setFeeRateWeight(int feeRateWeight) { + public void setFeeRateWeight(@Nullable Integer feeRateWeight) { this.feeRateWeight = feeRateWeight; } diff --git a/web/src/test/java/de/cotto/lndmanagej/controller/PickhardtPaymentsControllerTest.java b/web/src/test/java/de/cotto/lndmanagej/controller/PickhardtPaymentsControllerTest.java index 188c0ba6..65191802 100644 --- a/web/src/test/java/de/cotto/lndmanagej/controller/PickhardtPaymentsControllerTest.java +++ b/web/src/test/java/de/cotto/lndmanagej/controller/PickhardtPaymentsControllerTest.java @@ -43,14 +43,14 @@ class PickhardtPaymentsControllerTest { static { PAYMENT_OPTIONS = new PaymentOptions( - 123, + Optional.of(123), Optional.of(999L), Optional.of(777L), false, Optional.of(PUBKEY_4) ); PAYMENT_OPTIONS_DTO = new PaymentOptionsDto(); - PAYMENT_OPTIONS_DTO.setFeeRateWeight(PAYMENT_OPTIONS.feeRateWeight()); + PAYMENT_OPTIONS_DTO.setFeeRateWeight(PAYMENT_OPTIONS.feeRateWeight().orElse(null)); PAYMENT_OPTIONS_DTO.setFeeRateLimit(PAYMENT_OPTIONS.feeRateLimit().orElse(null)); PAYMENT_OPTIONS_DTO.setFeeRateLimitExceptIncomingHops( PAYMENT_OPTIONS.feeRateLimitExceptIncomingHops().orElse(null) diff --git a/web/src/test/java/de/cotto/lndmanagej/controller/dto/PaymentOptionsDtoTest.java b/web/src/test/java/de/cotto/lndmanagej/controller/dto/PaymentOptionsDtoTest.java index 4de0e253..7adf9ade 100644 --- a/web/src/test/java/de/cotto/lndmanagej/controller/dto/PaymentOptionsDtoTest.java +++ b/web/src/test/java/de/cotto/lndmanagej/controller/dto/PaymentOptionsDtoTest.java @@ -12,7 +12,7 @@ import static org.assertj.core.api.Assertions.assertThat; class PaymentOptionsDtoTest { @Test void toModel_default() { - assertThat(new PaymentOptionsDto().toModel()).isEqualTo(DEFAULT_PAYMENT_OPTIONS); + assertThat(PaymentOptionsDto.DEFAULT.toModel()).isEqualTo(DEFAULT_PAYMENT_OPTIONS); } @Test @@ -28,7 +28,7 @@ class PaymentOptionsDtoTest { long feeRateLimit = 555; dto.setFeeRateLimit(feeRateLimit); PaymentOptions expected = new PaymentOptions( - DEFAULT_PAYMENT_OPTIONS.feeRateWeight(), + Optional.empty(), Optional.of(feeRateLimit), Optional.empty(), DEFAULT_PAYMENT_OPTIONS.ignoreFeesForOwnChannels(), @@ -45,7 +45,7 @@ class PaymentOptionsDtoTest { dto.setFeeRateLimit(feeRateLimit); dto.setFeeRateLimitExceptIncomingHops(feeRateLimitExceptIncomingHops); PaymentOptions expected = new PaymentOptions( - DEFAULT_PAYMENT_OPTIONS.feeRateWeight(), + Optional.empty(), Optional.of(feeRateLimit), Optional.of(feeRateLimitExceptIncomingHops), DEFAULT_PAYMENT_OPTIONS.ignoreFeesForOwnChannels(), @@ -59,7 +59,7 @@ class PaymentOptionsDtoTest { PaymentOptionsDto dto = new PaymentOptionsDto(); dto.setIgnoreFeesForOwnChannels(false); PaymentOptions expected = new PaymentOptions( - DEFAULT_PAYMENT_OPTIONS.feeRateWeight(), + Optional.empty(), Optional.empty(), Optional.empty(), false, @@ -73,7 +73,7 @@ class PaymentOptionsDtoTest { PaymentOptionsDto dto = new PaymentOptionsDto(); dto.setPeer(PUBKEY); PaymentOptions expected = new PaymentOptions( - DEFAULT_PAYMENT_OPTIONS.feeRateWeight(), + Optional.empty(), Optional.empty(), Optional.empty(), DEFAULT_PAYMENT_OPTIONS.ignoreFeesForOwnChannels(),