top-up: impose stricter fee limit for all hops (but not the ones from the top-up peer)

This commit is contained in:
Carsten Otto
2022-05-26 10:09:02 +02:00
parent 22c5866f6b
commit da4abdc2f8
9 changed files with 115 additions and 65 deletions

View File

@@ -9,8 +9,10 @@ import de.cotto.lndmanagej.model.DirectedChannelEdge;
import de.cotto.lndmanagej.model.Edge;
import de.cotto.lndmanagej.model.EdgeWithLiquidityInformation;
import de.cotto.lndmanagej.model.LocalChannel;
import de.cotto.lndmanagej.model.Policy;
import de.cotto.lndmanagej.model.Pubkey;
import de.cotto.lndmanagej.pickhardtpayments.model.EdgesWithLiquidityInformation;
import de.cotto.lndmanagej.pickhardtpayments.model.PaymentOptions;
import de.cotto.lndmanagej.service.BalanceService;
import de.cotto.lndmanagej.service.ChannelService;
import de.cotto.lndmanagej.service.LiquidityBoundsService;
@@ -27,8 +29,6 @@ import java.util.function.Function;
@Component
public class EdgeComputation {
private static final long NO_FEE_RATE_LIMIT = -1L;
private final Logger logger = LoggerFactory.getLogger(getClass());
private final GrpcGraph grpcGraph;
@@ -57,11 +57,7 @@ public class EdgeComputation {
this.routeHintService = routeHintService;
}
public EdgesWithLiquidityInformation getEdges() {
return getEdges(NO_FEE_RATE_LIMIT);
}
public EdgesWithLiquidityInformation getEdges(long feeRateLimit) {
public EdgesWithLiquidityInformation getEdges(PaymentOptions paymentOptions) {
Set<DirectedChannelEdge> channelEdges = grpcGraph.getChannelEdges().orElse(null);
if (channelEdges == null) {
logger.warn("Unable to get graph");
@@ -71,7 +67,7 @@ public class EdgeComputation {
Pubkey ownPubkey = grpcGetInfo.getPubkey();
Set<DirectedChannelEdge> edgesFromPaymentHints = routeHintService.getEdgesFromPaymentHints();
for (DirectedChannelEdge channelEdge : Sets.union(channelEdges, edgesFromPaymentHints)) {
if (shouldIgnore(channelEdge, feeRateLimit)) {
if (shouldIgnore(channelEdge, paymentOptions, ownPubkey)) {
continue;
}
ChannelId channelId = channelEdge.channelId();
@@ -111,14 +107,31 @@ public class EdgeComputation {
return withFeeReserve.subtract(Coins.ofSatoshis(1_000)).maximum(Coins.NONE);
}
private boolean shouldIgnore(DirectedChannelEdge channelEdge, long feeRateLimit) {
if (channelEdge.policy().disabled()) {
private boolean shouldIgnore(DirectedChannelEdge channelEdge, PaymentOptions paymentOptions, Pubkey pubkey) {
Policy policy = channelEdge.policy();
if (policy.disabled()) {
return true;
}
if (feeRateLimit == NO_FEE_RATE_LIMIT) {
Long feeRateLimit = paymentOptions.feeRateLimit().orElse(null);
if (feeRateLimit == null) {
return false;
}
return channelEdge.policy().feeRate() >= feeRateLimit;
long feeRate = policy.feeRate();
if (feeRate >= feeRateLimit) {
return true;
}
if (isIncomingEdge(channelEdge, pubkey)) {
return false;
}
Long feeRateLimitFirstHops = paymentOptions.feeRateLimitExceptIncomingHops().orElse(null);
if (feeRateLimitFirstHops == null) {
return false;
}
return feeRate >= feeRateLimitFirstHops;
}
private boolean isIncomingEdge(DirectedChannelEdge channelEdge, Pubkey ownPubkey) {
return ownPubkey.equals(channelEdge.target());
}
private Optional<Coins> getKnownLiquidity(Edge edge, Pubkey ownPubKey) {

View File

@@ -38,7 +38,7 @@ public class FlowComputation {
int quantization = getQuantization(amount);
int piecewiseLinearApproximations = configurationService.getIntegerValue(PIECEWISE_LINEAR_APPROXIMATIONS)
.orElse(DEFAULT_PIECEWISE_LINEAR_APPROXIMATIONS);
EdgesWithLiquidityInformation edges = getEdges(paymentOptions);
EdgesWithLiquidityInformation edges = edgeComputation.getEdges(paymentOptions);
MinCostFlowSolver minCostFlowSolver = new MinCostFlowSolver(
edges,
Map.of(source, amount),
@@ -52,16 +52,6 @@ public class FlowComputation {
return minCostFlowSolver.solve();
}
private EdgesWithLiquidityInformation getEdges(PaymentOptions paymentOptions) {
EdgesWithLiquidityInformation edges;
if (paymentOptions.feeRateLimit().isPresent()) {
edges = edgeComputation.getEdges(paymentOptions.feeRateLimit().get());
} else {
edges = edgeComputation.getEdges();
}
return edges;
}
private int getQuantization(Coins amount) {
int quantization = configurationService.getIntegerValue(QUANTIZATION)
.orElse(DEFAULT_QUANTIZATION);

View File

@@ -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, pubkey);
PaymentOptions paymentOptions = PaymentOptions.forTopUp(ourFeeRate, peerFeeRate, pubkey);
return multiPathPaymentSender.payPaymentRequest(paymentRequest, paymentOptions);
}

View File

@@ -7,20 +7,27 @@ import java.util.Optional;
public record PaymentOptions(
int feeRateWeight,
Optional<Long> feeRateLimit,
Optional<Long> feeRateLimitExceptIncomingHops,
boolean ignoreFeesForOwnChannels,
Optional<Pubkey> peer
) {
public static final PaymentOptions DEFAULT_PAYMENT_OPTIONS = forFeeRateWeight(0);
public static PaymentOptions forFeeRateWeight(int feeRateWeight) {
return new PaymentOptions(feeRateWeight, Optional.empty(), true, Optional.empty());
return new PaymentOptions(feeRateWeight, Optional.empty(), Optional.empty(), true, Optional.empty());
}
public static PaymentOptions forFeeRateLimit(long feeRateLimit) {
return new PaymentOptions(0, Optional.of(feeRateLimit), true, Optional.empty());
return new PaymentOptions(0, Optional.of(feeRateLimit), Optional.of(feeRateLimit), true, Optional.empty());
}
public static PaymentOptions forTopUp(long feeRateLimit, Pubkey peer) {
return new PaymentOptions(5, Optional.of(feeRateLimit), false, Optional.of(peer));
public static PaymentOptions forTopUp(long ourFeeRate, long peerFeeRate, Pubkey peer) {
return new PaymentOptions(
5,
Optional.of(ourFeeRate),
Optional.of(ourFeeRate - peerFeeRate),
false,
Optional.of(peer)
);
}
}

View File

@@ -9,6 +9,7 @@ import de.cotto.lndmanagej.model.EdgeWithLiquidityInformation;
import de.cotto.lndmanagej.model.Node;
import de.cotto.lndmanagej.model.Policy;
import de.cotto.lndmanagej.model.Pubkey;
import de.cotto.lndmanagej.pickhardtpayments.model.PaymentOptions;
import de.cotto.lndmanagej.service.BalanceService;
import de.cotto.lndmanagej.service.ChannelService;
import de.cotto.lndmanagej.service.LiquidityBoundsService;
@@ -37,6 +38,7 @@ import static de.cotto.lndmanagej.model.PolicyFixtures.POLICY_WITH_BASE_FEE;
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.PaymentOptions.DEFAULT_PAYMENT_OPTIONS;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.lenient;
@@ -79,23 +81,24 @@ class EdgeComputationTest {
@Test
void no_graph() {
assertThat(edgeComputation.getEdges().edges()).isEmpty();
assertThat(edgeComputation.getEdges(DEFAULT_PAYMENT_OPTIONS).edges()).isEmpty();
}
@Test
void does_not_add_edge_for_disabled_channel() {
DirectedChannelEdge edge = new DirectedChannelEdge(CHANNEL_ID, CAPACITY, PUBKEY, PUBKEY_2, POLICY_DISABLED);
when(grpcGraph.getChannelEdges()).thenReturn(Optional.of(Set.of(edge)));
assertThat(edgeComputation.getEdges().edges()).isEmpty();
assertThat(edgeComputation.getEdges(DEFAULT_PAYMENT_OPTIONS).edges()).isEmpty();
}
@Test
void does_not_add_edge_with_fee_rate_at_or_above_limit() {
int feeRateLimit = 199;
Policy policyExpensive = new Policy(200, Coins.NONE, true, 40, Coins.ofSatoshis(0));
PaymentOptions paymentOptions = PaymentOptions.forFeeRateLimit(feeRateLimit);
Policy policyExpensive = policy(200);
// needs to be excluded to avoid sending top-up payments in a tiny loop: S-X-S
Policy policyAtLimit = new Policy(199, Coins.NONE, true, 40, Coins.ofSatoshis(0));
Policy policyOk = new Policy(198, Coins.NONE, true, 40, Coins.ofSatoshis(0));
Policy policyAtLimit = policy(199);
Policy policyOk = policy(198);
DirectedChannelEdge edgeExpensive =
new DirectedChannelEdge(CHANNEL_ID, CAPACITY, PUBKEY, PUBKEY_2, policyExpensive);
DirectedChannelEdge edgeAtLimit =
@@ -103,8 +106,33 @@ class EdgeComputationTest {
DirectedChannelEdge edgeOk =
new DirectedChannelEdge(CHANNEL_ID_3, CAPACITY, PUBKEY, PUBKEY_2, policyOk);
when(grpcGraph.getChannelEdges()).thenReturn(Optional.of(Set.of(edgeExpensive, edgeAtLimit, edgeOk)));
assertThat(edgeComputation.getEdges(feeRateLimit).edges().stream().map(EdgeWithLiquidityInformation::channelId))
.containsExactly(CHANNEL_ID_3);
assertThat(
edgeComputation.getEdges(paymentOptions).edges().stream().map(EdgeWithLiquidityInformation::channelId)
).containsExactly(CHANNEL_ID_3);
}
@Test
void does_not_add_first_hop_edge_with_fee_rate_at_or_above_limit_for_first_hops() {
Pubkey ownPubkey = EDGE.startNode();
Pubkey topUpPeer = PUBKEY_4;
int feeRateLimit = 200;
int feeRateLimitForFirstHops = 100;
when(grpcGetInfo.getPubkey()).thenReturn(ownPubkey);
PaymentOptions paymentOptions = PaymentOptions.forTopUp(feeRateLimit, feeRateLimitForFirstHops, topUpPeer);
Policy lastHopPolicy = policy(199);
Policy firstHopPolicyExpensive = policy(100);
Policy firstHopPolicyOk = policy(99);
DirectedChannelEdge lastHop =
new DirectedChannelEdge(CHANNEL_ID, CAPACITY, topUpPeer, ownPubkey, lastHopPolicy);
DirectedChannelEdge firstHopExpensive =
new DirectedChannelEdge(CHANNEL_ID_2, CAPACITY, ownPubkey, PUBKEY_2, firstHopPolicyExpensive);
DirectedChannelEdge firstHopOk =
new DirectedChannelEdge(CHANNEL_ID_3, CAPACITY, ownPubkey, PUBKEY_2, firstHopPolicyOk);
when(grpcGraph.getChannelEdges()).thenReturn(Optional.of(Set.of(lastHop, firstHopExpensive, firstHopOk)));
assertThat(
edgeComputation.getEdges(paymentOptions).edges().stream().map(EdgeWithLiquidityInformation::channelId)
).containsExactlyInAnyOrder(CHANNEL_ID, CHANNEL_ID_3);
}
@Test
@@ -112,7 +140,7 @@ class EdgeComputationTest {
DirectedChannelEdge edge =
new DirectedChannelEdge(CHANNEL_ID, CAPACITY, PUBKEY, PUBKEY_2, POLICY_WITH_BASE_FEE);
when(grpcGraph.getChannelEdges()).thenReturn(Optional.of(Set.of(edge)));
assertThat(edgeComputation.getEdges().edges()).isNotEmpty();
assertThat(edgeComputation.getEdges(DEFAULT_PAYMENT_OPTIONS).edges()).isNotEmpty();
}
@Test
@@ -124,7 +152,7 @@ class EdgeComputationTest {
Coins availableKnownLiquidity = getAvailableKnownLiquidity(knownLiquidity);
when(balanceService.getAvailableLocalBalance(EDGE.channelId())).thenReturn(knownLiquidity);
assertThat(edgeComputation.getEdges().edges())
assertThat(edgeComputation.getEdges(DEFAULT_PAYMENT_OPTIONS).edges())
.contains(EdgeWithLiquidityInformation.forKnownLiquidity(EDGE, availableKnownLiquidity));
}
@@ -143,7 +171,7 @@ class EdgeComputationTest {
edge.policy()
)
));
assertThat(edgeComputation.getEdges().edges())
assertThat(edgeComputation.getEdges(DEFAULT_PAYMENT_OPTIONS).edges())
.contains(EdgeWithLiquidityInformation.forKnownLiquidity(edge, fiftyCoins));
}
@@ -152,7 +180,7 @@ class EdgeComputationTest {
mockEdge();
when(grpcGetInfo.getPubkey()).thenReturn(EDGE.startNode());
assertThat(edgeComputation.getEdges().edges())
assertThat(edgeComputation.getEdges(DEFAULT_PAYMENT_OPTIONS).edges())
.contains(EdgeWithLiquidityInformation.forKnownLiquidity(EDGE, Coins.NONE));
}
@@ -162,7 +190,7 @@ class EdgeComputationTest {
mockOfflinePeer();
when(grpcGetInfo.getPubkey()).thenReturn(EDGE.startNode());
assertThat(edgeComputation.getEdges().edges())
assertThat(edgeComputation.getEdges(DEFAULT_PAYMENT_OPTIONS).edges())
.contains(EdgeWithLiquidityInformation.forKnownLiquidity(EDGE, Coins.NONE));
verify(balanceService, never()).getAvailableLocalBalance(EDGE.channelId());
}
@@ -176,7 +204,7 @@ class EdgeComputationTest {
Coins availableKnownLiquidity = getAvailableKnownLiquidity(knownLiquidity);
when(balanceService.getAvailableRemoteBalance(EDGE.channelId())).thenReturn(knownLiquidity);
assertThat(edgeComputation.getEdges().edges())
assertThat(edgeComputation.getEdges(DEFAULT_PAYMENT_OPTIONS).edges())
.contains(EdgeWithLiquidityInformation.forKnownLiquidity(EDGE, availableKnownLiquidity));
}
@@ -185,7 +213,7 @@ class EdgeComputationTest {
mockEdge();
when(grpcGetInfo.getPubkey()).thenReturn(EDGE.endNode());
assertThat(edgeComputation.getEdges().edges())
assertThat(edgeComputation.getEdges(DEFAULT_PAYMENT_OPTIONS).edges())
.contains(EdgeWithLiquidityInformation.forKnownLiquidity(EDGE, Coins.NONE));
}
@@ -195,7 +223,7 @@ class EdgeComputationTest {
mockOfflinePeer();
when(grpcGetInfo.getPubkey()).thenReturn(EDGE.endNode());
assertThat(edgeComputation.getEdges().edges())
assertThat(edgeComputation.getEdges(DEFAULT_PAYMENT_OPTIONS).edges())
.contains(EdgeWithLiquidityInformation.forKnownLiquidity(EDGE, Coins.NONE));
verify(balanceService, never()).getAvailableLocalBalance(EDGE.channelId());
}
@@ -211,14 +239,14 @@ class EdgeComputationTest {
mockEdge();
Coins upperBound = Coins.ofSatoshis(100);
mockUpperBound(upperBound);
assertThat(edgeComputation.getEdges().edges())
assertThat(edgeComputation.getEdges(DEFAULT_PAYMENT_OPTIONS).edges())
.contains(EdgeWithLiquidityInformation.forUpperBound(EDGE, upperBound));
}
@Test
void default_if_no_liquidity_information_is_known() {
mockEdge();
assertThat(edgeComputation.getEdges().edges())
assertThat(edgeComputation.getEdges(DEFAULT_PAYMENT_OPTIONS).edges())
.contains(EdgeWithLiquidityInformation.forUpperBound(EDGE, EDGE.capacity()));
}
@@ -321,4 +349,8 @@ class EdgeComputationTest {
Coins withOnChainReserve = withFeeReserve.subtract(Coins.ofSatoshis(1_000));
return withOnChainReserve.maximum(Coins.NONE);
}
private static Policy policy(int feeRate) {
return new Policy(feeRate, Coins.NONE, true, 40, Coins.ofSatoshis(0));
}
}

View File

@@ -31,7 +31,6 @@ import static de.cotto.lndmanagej.model.PubkeyFixtures.PUBKEY_3;
import static de.cotto.lndmanagej.model.PubkeyFixtures.PUBKEY_4;
import static de.cotto.lndmanagej.pickhardtpayments.model.PaymentOptions.DEFAULT_PAYMENT_OPTIONS;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.Mockito.lenient;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@@ -62,23 +61,24 @@ class FlowComputationTest {
@Test
void solve_no_edge() {
when(edgeComputation.getEdges()).thenReturn(EdgesWithLiquidityInformation.EMPTY);
when(edgeComputation.getEdges(DEFAULT_PAYMENT_OPTIONS)).thenReturn(EdgesWithLiquidityInformation.EMPTY);
assertThat(flowComputation.getOptimalFlows(PUBKEY, PUBKEY_2, Coins.ofSatoshis(1), DEFAULT_PAYMENT_OPTIONS))
.isEqualTo(new Flows());
}
@Test
void passes_fee_rate_limit_to_get_edges() {
when(edgeComputation.getEdges(anyLong())).thenReturn(EdgesWithLiquidityInformation.EMPTY);
flowComputation.getOptimalFlows(PUBKEY, PUBKEY_2, Coins.ofSatoshis(1), PaymentOptions.forFeeRateLimit(123));
verify(edgeComputation).getEdges(123);
PaymentOptions paymentOptions = PaymentOptions.forFeeRateLimit(123);
when(edgeComputation.getEdges(paymentOptions)).thenReturn(EdgesWithLiquidityInformation.EMPTY);
flowComputation.getOptimalFlows(PUBKEY, PUBKEY_2, Coins.ofSatoshis(1), paymentOptions);
verify(edgeComputation).getEdges(paymentOptions);
}
@Test
void solve() {
Coins amount = Coins.ofSatoshis(1);
EdgeWithLiquidityInformation edge = EdgeWithLiquidityInformation.forUpperBound(EDGE, EDGE.capacity());
when(edgeComputation.getEdges()).thenReturn(new EdgesWithLiquidityInformation(edge));
when(edgeComputation.getEdges(DEFAULT_PAYMENT_OPTIONS)).thenReturn(new EdgesWithLiquidityInformation(edge));
Flow expectedFlow = new Flow(EDGE, amount);
assertThat(flowComputation.getOptimalFlows(PUBKEY, PUBKEY_2, amount, DEFAULT_PAYMENT_OPTIONS))
.isEqualTo(new Flows(expectedFlow));
@@ -89,7 +89,7 @@ class FlowComputationTest {
when(configurationService.getIntegerValue(QUANTIZATION)).thenReturn(Optional.of(10));
Coins amount = Coins.ofSatoshis(9);
EdgeWithLiquidityInformation edge = EdgeWithLiquidityInformation.forUpperBound(EDGE, EDGE.capacity());
when(edgeComputation.getEdges()).thenReturn(new EdgesWithLiquidityInformation(edge));
when(edgeComputation.getEdges(DEFAULT_PAYMENT_OPTIONS)).thenReturn(new EdgesWithLiquidityInformation(edge));
assertThat(flowComputation.getOptimalFlows(PUBKEY, PUBKEY_2, amount, DEFAULT_PAYMENT_OPTIONS))
.isEqualTo(new Flows(new Flow(EDGE, amount)));
}
@@ -98,7 +98,7 @@ class FlowComputationTest {
void solve_avoids_sending_from_depleted_local_channel() {
Edge edge1 = new Edge(CHANNEL_ID, PUBKEY, PUBKEY_2, LARGE, POLICY_1);
Edge edge2 = new Edge(CHANNEL_ID_2, PUBKEY, PUBKEY_2, SMALL, POLICY_2);
when(edgeComputation.getEdges()).thenReturn(new EdgesWithLiquidityInformation(
when(edgeComputation.getEdges(DEFAULT_PAYMENT_OPTIONS)).thenReturn(new EdgesWithLiquidityInformation(
EdgeWithLiquidityInformation.forKnownLiquidity(edge1, Coins.NONE),
EdgeWithLiquidityInformation.forUpperBound(edge2, SMALL)
));
@@ -114,7 +114,7 @@ class FlowComputationTest {
Edge edge1a = new Edge(CHANNEL_ID, PUBKEY_2, PUBKEY_3, LARGE, POLICY_1);
Edge edge1b = new Edge(CHANNEL_ID_2, PUBKEY_3, PUBKEY_4, LARGE, POLICY_1);
Edge edge2 = new Edge(CHANNEL_ID_3, PUBKEY_2, PUBKEY_4, SMALL, POLICY_2);
when(edgeComputation.getEdges()).thenReturn(new EdgesWithLiquidityInformation(
when(edgeComputation.getEdges(DEFAULT_PAYMENT_OPTIONS)).thenReturn(new EdgesWithLiquidityInformation(
EdgeWithLiquidityInformation.forUpperBound(edge1a, amount),
EdgeWithLiquidityInformation.forUpperBound(edge1b, LARGE),
EdgeWithLiquidityInformation.forUpperBound(edge2, SMALL)
@@ -130,7 +130,7 @@ class FlowComputationTest {
Edge edge1a = new Edge(CHANNEL_ID, PUBKEY_3, PUBKEY_4, LARGE, POLICY_2);
Edge edge1b = new Edge(CHANNEL_ID_2, PUBKEY_4, PUBKEY, LARGE, POLICY_2);
Edge edge2 = new Edge(CHANNEL_ID_3, PUBKEY_3, PUBKEY, SMALL, POLICY_1);
when(edgeComputation.getEdges()).thenReturn(new EdgesWithLiquidityInformation(
when(edgeComputation.getEdges(DEFAULT_PAYMENT_OPTIONS)).thenReturn(new EdgesWithLiquidityInformation(
EdgeWithLiquidityInformation.forUpperBound(edge1a, Coins.ofSatoshis(5_000_000)),
EdgeWithLiquidityInformation.forUpperBound(edge1b, LARGE),
EdgeWithLiquidityInformation.forUpperBound(edge2, SMALL)

View File

@@ -123,7 +123,7 @@ class MultiPathPaymentSplitterTest {
int feeRate = 200;
Coins amount = Coins.ofSatoshis(1_000_000);
Policy policy = policyFor(feeRate);
PaymentOptions paymentOptions = PaymentOptions.forTopUp(feeRate - 1, PUBKEY_2);
PaymentOptions paymentOptions = PaymentOptions.forTopUp(feeRate - 1, 0, PUBKEY_2);
mockFlow(amount, policy, paymentOptions);
MultiPathPayment multiPathPayment =
@@ -137,7 +137,7 @@ class MultiPathPaymentSplitterTest {
int feeRate = 200;
Coins amount = Coins.ofSatoshis(2_000_000);
Policy policy = policyFor(feeRate);
PaymentOptions paymentOptions = PaymentOptions.forTopUp(feeRate, PUBKEY_2);
PaymentOptions paymentOptions = PaymentOptions.forTopUp(feeRate, 0, PUBKEY_2);
mockFlow(amount, policy, paymentOptions);
MultiPathPayment multiPathPayment =
@@ -151,7 +151,7 @@ class MultiPathPaymentSplitterTest {
mockExtensionEdge(PUBKEY_3, feeRate);
Coins amount = Coins.ofSatoshis(2_000_000);
Policy policy = policyFor(0);
PaymentOptions paymentOptions = PaymentOptions.forTopUp(feeRate - 1, PUBKEY_2);
PaymentOptions paymentOptions = PaymentOptions.forTopUp(feeRate - 1, 0, PUBKEY_2);
mockFlow(amount, policy, paymentOptions);
MultiPathPayment multiPathPayment =
@@ -197,11 +197,13 @@ class MultiPathPaymentSplitterTest {
@Test
void one_flow_has_fee_rate_above_limit_but_average_fee_rate_is_below_limit_including_fees_from_first_hop() {
// The cheaper flows might fail while the more expensive one settles. This is not what we want, so we have
// to disregard all flows.
mockExtensionEdge(PUBKEY_4, 0);
int feeRate = 200;
Coins halfOfAmount = Coins.ofSatoshis(500_000);
Coins amount = halfOfAmount.add(halfOfAmount);
PaymentOptions paymentOptions = PaymentOptions.forTopUp(feeRate - 1, PUBKEY_2);
PaymentOptions paymentOptions = PaymentOptions.forTopUp(feeRate - 1, 0, PUBKEY_2);
Edge edge1 = new Edge(CHANNEL_ID, PUBKEY, PUBKEY_2, CAPACITY, policyFor(0));
Edge edge2 = new Edge(CHANNEL_ID, PUBKEY, PUBKEY_2, CAPACITY, policyFor(feeRate));
Flow flow1 = new Flow(edge1, halfOfAmount);
@@ -348,7 +350,7 @@ class MultiPathPaymentSplitterTest {
}
private MultiPathPayment attemptTopUpPayment() {
PaymentOptions paymentOptions = PaymentOptions.forTopUp(500, PUBKEY_2);
PaymentOptions paymentOptions = PaymentOptions.forTopUp(500, 123, PUBKEY_2);
when(flowComputation.getOptimalFlows(PUBKEY, PUBKEY_2, AMOUNT, paymentOptions)).thenReturn(new Flows(FLOW));
return multiPathPaymentSplitter.getMultiPathPayment(PUBKEY, PUBKEY_3, AMOUNT, paymentOptions);
}

View File

@@ -184,7 +184,7 @@ class TopUpServiceTest {
private void assertTopUp(Coins expectedTopUpAmount, Duration expiry) {
PaymentStatus paymentStatus = topUpService.topUp(PUBKEY, AMOUNT);
verify(grpcInvoices).createPaymentRequest(expectedTopUpAmount, DESCRIPTION, expiry);
PaymentOptions paymentOptions = PaymentOptions.forTopUp(OUR_FEE_RATE, PUBKEY);
PaymentOptions paymentOptions = PaymentOptions.forTopUp(OUR_FEE_RATE, PEER_FEE_RATE, PUBKEY);
verify(multiPathPaymentSender).payPaymentRequest(DECODED_PAYMENT_REQUEST, paymentOptions);
assertThat(paymentStatus.isPending()).isTrue();
}

View File

@@ -12,19 +12,25 @@ class PaymentOptionsTest {
@Test
void forFeeRateWeight() {
assertThat(PaymentOptions.forFeeRateWeight(12))
.isEqualTo(new PaymentOptions(12, Optional.empty(), true, Optional.empty()));
.isEqualTo(new PaymentOptions(12, Optional.empty(), Optional.empty(), true, Optional.empty()));
}
@Test
void forFeeRateLimit() {
assertThat(PaymentOptions.forFeeRateLimit(123))
.isEqualTo(new PaymentOptions(0, Optional.of(123L), true, Optional.empty()));
.isEqualTo(new PaymentOptions(0, Optional.of(123L), Optional.of(123L), true, Optional.empty()));
}
@Test
void forTopUp() {
assertThat(PaymentOptions.forTopUp(123, PUBKEY))
.isEqualTo(new PaymentOptions(5, Optional.of(123L), false, Optional.of(PUBKEY)));
assertThat(PaymentOptions.forTopUp(123, 100, PUBKEY))
.isEqualTo(new PaymentOptions(5, Optional.of(123L), Optional.of(23L), false, Optional.of(PUBKEY)));
}
@Test
void feeRateLimitFirstHops() {
PaymentOptions paymentOptions = PaymentOptions.forTopUp(200, 30, PUBKEY);
assertThat(paymentOptions.feeRateLimitExceptIncomingHops()).contains(170L);
}
@Test