allow channels with non-zero base fee

Note that this is an over-approximation. In the routing cost computation
it is assumed that the base fee is paid not just once per payment, but
possibly several times.

As an example, with a base fee of 2sat and a quantization of 10,000sat
(which is the current default), the assumed fee rate is the original
fee rate plus 200ppm (2sat * 1,000,000 / 10,000).

fixes #31
This commit is contained in:
Carsten Otto
2022-05-17 10:13:19 +02:00
parent 8e61ff4d7d
commit 9593dfd161
4 changed files with 51 additions and 10 deletions

View File

@@ -91,7 +91,8 @@ class ArcInitializer {
if (ownPubkey.equals(edge.startNode())) {
return 0;
}
return edge.policy().feeRate();
long fromBaseFee = (long) Math.ceil(1.0 * 1_000 / quantization * edge.policy().baseFee().milliSatoshis());
return edge.policy().feeRate() + fromBaseFee;
}
private long quantize(Coins coins) {

View File

@@ -9,7 +9,6 @@ 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.service.BalanceService;
@@ -106,8 +105,7 @@ public class EdgeComputation {
}
private boolean shouldIgnore(DirectedChannelEdge channelEdge) {
Policy policy = channelEdge.policy();
return policy.disabled() || policy.baseFee().isPositive();
return channelEdge.policy().disabled();
}
private Optional<Coins> getKnownLiquidity(Edge edge, Pubkey ownPubKey) {

View File

@@ -5,6 +5,7 @@ import com.google.ortools.graph.MinCostFlow;
import de.cotto.lndmanagej.model.Coins;
import de.cotto.lndmanagej.model.Edge;
import de.cotto.lndmanagej.model.EdgeWithLiquidityInformation;
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.IntegerMapping;
@@ -13,6 +14,8 @@ import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
import static de.cotto.lndmanagej.model.ChannelFixtures.CAPACITY;
import static de.cotto.lndmanagej.model.ChannelIdFixtures.CHANNEL_ID;
import static de.cotto.lndmanagej.model.EdgeFixtures.EDGE;
import static de.cotto.lndmanagej.model.EdgeFixtures.EDGE_1_3;
import static de.cotto.lndmanagej.model.EdgeFixtures.EDGE_2_3;
@@ -206,17 +209,56 @@ class ArcInitializerTest {
@Test
void adds_fee_rate_as_cost() {
int feeRateWeight = 1;
ArcInitializer arcInitializer = new ArcInitializer(
ArcInitializer arcInitializer = getArcInitializer(QUANTIZATION, feeRateWeight);
arcInitializer.addArcs(new EdgesWithLiquidityInformation(edgeWithLiquidityInformation));
assertThat(minCostFlow.getUnitCost(0)).isEqualTo(200);
}
@Test
void includes_base_fee_in_assumed_fee_rate() {
Coins baseFee = Coins.ofMilliSatoshis(100);
int feeRateWeight = 1;
int quantization = 10_000;
ArcInitializer arcInitializer = getArcInitializer(quantization, feeRateWeight);
addEdgeWithBaseFee(baseFee, quantization, arcInitializer);
long expectedFeeRate = (long) Math.ceil(200 + 10);
assertThat(minCostFlow.getUnitCost(0)).isEqualTo(expectedFeeRate);
}
@Test
void includes_base_fee_in_assumed_fee_rate_rounds_up() {
Coins baseFee = Coins.ofMilliSatoshis(111);
int feeRateWeight = 1;
int quantization = 10_000;
ArcInitializer arcInitializer = getArcInitializer(quantization, feeRateWeight);
addEdgeWithBaseFee(baseFee, quantization, arcInitializer);
long expectedFeeRate = (long) Math.ceil(200 + 12);
assertThat(minCostFlow.getUnitCost(0)).isEqualTo(expectedFeeRate);
}
private void addEdgeWithBaseFee(Coins baseFee, int quantization, ArcInitializer arcInitializer) {
Policy policy = new Policy(200, baseFee, true, 40, Coins.ofSatoshis(10_000));
EdgeWithLiquidityInformation edge = EdgeWithLiquidityInformation.forKnownLiquidity(
new Edge(CHANNEL_ID, PUBKEY, PUBKEY_2, CAPACITY, policy),
Coins.ofSatoshis(30L * quantization)
);
arcInitializer.addArcs(new EdgesWithLiquidityInformation(edge));
}
private ArcInitializer getArcInitializer(int quantization, int feeRateWeight) {
return new ArcInitializer(
minCostFlow,
integerMapping,
edgeMapping,
QUANTIZATION,
quantization,
PIECEWISE_LINEAR_APPROXIMATIONS,
feeRateWeight,
PUBKEY_2
);
arcInitializer.addArcs(new EdgesWithLiquidityInformation(edgeWithLiquidityInformation));
assertThat(minCostFlow.getUnitCost(0)).isEqualTo(200);
}
}

View File

@@ -88,11 +88,11 @@ class EdgeComputationTest {
}
@Test
void does_not_add_edge_for_channel_with_base_fee() {
void adds_edge_for_channel_with_base_fee() {
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()).isEmpty();
assertThat(edgeComputation.getEdges().edges()).isNotEmpty();
}
@Test