mirror of
https://github.com/aljazceru/lnd-manageJ.git
synced 2026-01-24 08:24:20 +01:00
@@ -41,16 +41,21 @@ class ArcInitializer {
|
||||
}
|
||||
|
||||
private void addArcs(EdgeWithLiquidityInformation edgeWithLiquidityInformation, Coins maximumCapacity) {
|
||||
long capacitySat = edgeWithLiquidityInformation.availableLiquidityUpperBound().satoshis();
|
||||
if (capacitySat < quantization) {
|
||||
return;
|
||||
}
|
||||
Edge edge = edgeWithLiquidityInformation.edge();
|
||||
int startNode = pubkeyToIntegerMapping.getMappedInteger(edge.startNode());
|
||||
int endNode = pubkeyToIntegerMapping.getMappedInteger(edge.endNode());
|
||||
long capacity = capacitySat / quantization;
|
||||
long unitCost = maximumCapacity.satoshis() / capacitySat;
|
||||
long capacityPiece = capacity / piecewiseLinearApproximations;
|
||||
|
||||
long quantizedLowerBound = quantize(edgeWithLiquidityInformation.availableLiquidityLowerBound());
|
||||
addArcForKnownLiquidity(edge, startNode, endNode, quantizedLowerBound);
|
||||
|
||||
Coins upperBound = edgeWithLiquidityInformation.availableLiquidityUpperBound();
|
||||
long quantizedUpperBound = quantize(upperBound);
|
||||
long uncertainButPossibleLiquidity = quantizedUpperBound - quantizedLowerBound;
|
||||
long capacityPiece = uncertainButPossibleLiquidity / piecewiseLinearApproximations;
|
||||
if (capacityPiece == 0) {
|
||||
return;
|
||||
}
|
||||
long unitCost = quantize(maximumCapacity) / uncertainButPossibleLiquidity;
|
||||
for (int i = 1; i <= piecewiseLinearApproximations; i++) {
|
||||
int arcIndex = minCostFlow.addArcWithCapacityAndUnitCost(
|
||||
startNode,
|
||||
@@ -62,6 +67,14 @@ class ArcInitializer {
|
||||
}
|
||||
}
|
||||
|
||||
private void addArcForKnownLiquidity(Edge edge, int startNode, int endNode, long quantizedLowerBound) {
|
||||
if (quantizedLowerBound <= 0) {
|
||||
return;
|
||||
}
|
||||
int arcIndex = minCostFlow.addArcWithCapacityAndUnitCost(startNode, endNode, quantizedLowerBound, 0);
|
||||
edgeMapping.put(arcIndex, edge);
|
||||
}
|
||||
|
||||
private Coins getMaximumCapacity(Collection<EdgeWithLiquidityInformation> edgesWithLiquidityInformation) {
|
||||
return edgesWithLiquidityInformation.stream()
|
||||
.map(EdgeWithLiquidityInformation::edge)
|
||||
@@ -69,4 +82,8 @@ class ArcInitializer {
|
||||
.max(Comparator.naturalOrder())
|
||||
.orElse(Coins.NONE);
|
||||
}
|
||||
|
||||
private long quantize(Coins coins) {
|
||||
return coins.milliSatoshis() / 1_000 / quantization;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -94,28 +94,25 @@ public class FlowComputation {
|
||||
|
||||
private Optional<Coins> getKnownLiquidity(Edge edge, Pubkey ownPubKey) {
|
||||
Pubkey source = edge.startNode();
|
||||
Coins capacity = edge.capacity();
|
||||
ChannelId channelId = edge.channelId();
|
||||
if (ownPubKey.equals(source)) {
|
||||
return Optional.of(getLocalChannelAvailableLocal(capacity, channelId));
|
||||
return getLocalChannelAvailableLocal(channelId);
|
||||
}
|
||||
Pubkey target = edge.endNode();
|
||||
if (ownPubKey.equals(target)) {
|
||||
return Optional.of(getLocalChannelAvailableRemote(capacity, channelId));
|
||||
return getLocalChannelAvailableRemote(channelId);
|
||||
}
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
private Coins getLocalChannelAvailableLocal(Coins capacity, ChannelId channelId) {
|
||||
private Optional<Coins> getLocalChannelAvailableLocal(ChannelId channelId) {
|
||||
return channelService.getLocalChannel(channelId)
|
||||
.map(c -> balanceService.getAvailableLocalBalance(channelId))
|
||||
.orElse(capacity);
|
||||
.map(c -> balanceService.getAvailableLocalBalance(channelId));
|
||||
}
|
||||
|
||||
private Coins getLocalChannelAvailableRemote(Coins capacity, ChannelId channelId) {
|
||||
private Optional<Coins> getLocalChannelAvailableRemote(ChannelId channelId) {
|
||||
return channelService.getLocalChannel(channelId)
|
||||
.map(c -> balanceService.getAvailableRemoteBalance(channelId))
|
||||
.orElse(capacity);
|
||||
.map(c -> balanceService.getAvailableRemoteBalance(channelId));
|
||||
}
|
||||
|
||||
private Coins getAvailableLiquidityUpperBound(Edge edge) {
|
||||
|
||||
@@ -7,11 +7,29 @@ public record EdgeWithLiquidityInformation(
|
||||
Coins availableLiquidityLowerBound,
|
||||
Coins availableLiquidityUpperBound
|
||||
) {
|
||||
public EdgeWithLiquidityInformation {
|
||||
if (availableLiquidityLowerBound.compareTo(availableLiquidityUpperBound) > 0) {
|
||||
throw new IllegalArgumentException("lower bound must not be higher than upper bound");
|
||||
}
|
||||
}
|
||||
|
||||
public static EdgeWithLiquidityInformation forKnownLiquidity(Edge edge, Coins knownLiquidity) {
|
||||
return new EdgeWithLiquidityInformation(edge, knownLiquidity, knownLiquidity);
|
||||
}
|
||||
|
||||
public static EdgeWithLiquidityInformation forLowerBound(Edge edge, Coins availableLiquidityLowerBound) {
|
||||
return new EdgeWithLiquidityInformation(edge, availableLiquidityLowerBound, edge.capacity());
|
||||
}
|
||||
|
||||
public static EdgeWithLiquidityInformation forUpperBound(Edge edge, Coins availableLiquidityUpperBound) {
|
||||
return new EdgeWithLiquidityInformation(edge, Coins.NONE, availableLiquidityUpperBound);
|
||||
}
|
||||
|
||||
public static EdgeWithLiquidityInformation forLowerAndUpperBound(
|
||||
Edge edge,
|
||||
Coins availableLiquidityLowerBound,
|
||||
Coins availableLiquidityUpperBound
|
||||
) {
|
||||
return new EdgeWithLiquidityInformation(edge, availableLiquidityLowerBound, availableLiquidityUpperBound);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,6 +7,8 @@ import de.cotto.lndmanagej.model.Pubkey;
|
||||
import de.cotto.lndmanagej.pickhardtpayments.model.Edge;
|
||||
import de.cotto.lndmanagej.pickhardtpayments.model.EdgeWithLiquidityInformation;
|
||||
import de.cotto.lndmanagej.pickhardtpayments.model.IntegerMapping;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Nested;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.util.LinkedHashMap;
|
||||
@@ -67,6 +69,118 @@ class ArcInitializerTest {
|
||||
assertThat(minCostFlow.getNumArcs()).isOne();
|
||||
}
|
||||
|
||||
@Test
|
||||
@SuppressWarnings("PMD.JUnitTestContainsTooManyAsserts")
|
||||
void edge_with_known_liquidity_is_added_as_arc_without_cost() {
|
||||
Coins capacity = Coins.ofSatoshis(100);
|
||||
Coins knownLiquidity = Coins.ofSatoshis(25);
|
||||
EdgeWithLiquidityInformation edgeWithLiquidityInformation =
|
||||
EdgeWithLiquidityInformation.forKnownLiquidity(EDGE.withCapacity(capacity), knownLiquidity);
|
||||
|
||||
arcInitializer.addArcs(Set.of(edgeWithLiquidityInformation));
|
||||
|
||||
assertThat(minCostFlow.getNumArcs()).isEqualTo(1);
|
||||
assertThat(minCostFlow.getUnitCost(0)).isEqualTo(0);
|
||||
assertThat(minCostFlow.getCapacity(0)).isEqualTo(25);
|
||||
}
|
||||
|
||||
@Nested
|
||||
class EdgeWithLowerAndUpperBound {
|
||||
private EdgeWithLiquidityInformation edgeWithLiquidityInformation;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
Coins capacity = Coins.ofSatoshis(100);
|
||||
Coins knownLiquidity = Coins.ofSatoshis(25);
|
||||
edgeWithLiquidityInformation = EdgeWithLiquidityInformation.forLowerAndUpperBound(
|
||||
EDGE.withCapacity(capacity),
|
||||
knownLiquidity,
|
||||
capacity
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
void added_as_arc_without_cost() {
|
||||
arcInitializer.addArcs(Set.of(edgeWithLiquidityInformation));
|
||||
assertThat(minCostFlow.getUnitCost(0)).isEqualTo(0);
|
||||
assertThat(minCostFlow.getCapacity(0)).isEqualTo(25);
|
||||
}
|
||||
|
||||
@Test
|
||||
void adds_uncertain_liquidity_as_second_arc() {
|
||||
arcInitializer.addArcs(Set.of(edgeWithLiquidityInformation));
|
||||
assertThat(minCostFlow.getUnitCost(1)).isEqualTo(1);
|
||||
assertThat(minCostFlow.getCapacity(1)).isEqualTo(75);
|
||||
}
|
||||
|
||||
@Test
|
||||
void splits_uncertain_liquidity_as_additional_arcs() {
|
||||
ArcInitializer arcInitializer = new ArcInitializer(
|
||||
minCostFlow,
|
||||
integerMapping,
|
||||
edgeMapping,
|
||||
QUANTIZATION,
|
||||
5
|
||||
);
|
||||
arcInitializer.addArcs(Set.of(edgeWithLiquidityInformation));
|
||||
assertThat(minCostFlow.getNumArcs()).isEqualTo(6);
|
||||
}
|
||||
|
||||
@Test
|
||||
void known_amount_matches_quantization() {
|
||||
ArcInitializer arcInitializer = new ArcInitializer(
|
||||
minCostFlow,
|
||||
integerMapping,
|
||||
edgeMapping,
|
||||
edgeWithLiquidityInformation.availableLiquidityLowerBound().satoshis(),
|
||||
PIECEWISE_LINEAR_APPROXIMATIONS
|
||||
);
|
||||
arcInitializer.addArcs(Set.of(edgeWithLiquidityInformation));
|
||||
assertThat(minCostFlow.getUnitCost(0)).isEqualTo(0);
|
||||
}
|
||||
|
||||
@Test
|
||||
void known_amount_rounded_due_to_quantization() {
|
||||
ArcInitializer arcInitializer = new ArcInitializer(
|
||||
minCostFlow,
|
||||
integerMapping,
|
||||
edgeMapping,
|
||||
20,
|
||||
PIECEWISE_LINEAR_APPROXIMATIONS
|
||||
);
|
||||
arcInitializer.addArcs(Set.of(edgeWithLiquidityInformation));
|
||||
assertThat(minCostFlow.getCapacity(0)).isEqualTo(1);
|
||||
}
|
||||
|
||||
@Test
|
||||
void splits_remaining_liquidity_after_rounding_known_liquidity() {
|
||||
ArcInitializer arcInitializer = new ArcInitializer(
|
||||
minCostFlow,
|
||||
integerMapping,
|
||||
edgeMapping,
|
||||
10,
|
||||
QUANTIZATION
|
||||
);
|
||||
arcInitializer.addArcs(Set.of(edgeWithLiquidityInformation));
|
||||
// one arc for the known liquidity (25 / 10 = 2), 100 / 10 - 2 = 8 remaining
|
||||
assertThat(minCostFlow.getCapacity(1)).isEqualTo(8);
|
||||
}
|
||||
|
||||
@Test
|
||||
void does_not_add_arcs_without_capacity() {
|
||||
ArcInitializer arcInitializer = new ArcInitializer(
|
||||
minCostFlow,
|
||||
integerMapping,
|
||||
edgeMapping,
|
||||
20,
|
||||
5
|
||||
);
|
||||
arcInitializer.addArcs(Set.of(edgeWithLiquidityInformation));
|
||||
// one arc for the known liquidity (25 / 20 = 1), 100 / 20 - 1 = 4 remaining: 4 < 5, no additional arc added
|
||||
assertThat(minCostFlow.getNumArcs()).isEqualTo(1);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void adds_edge_to_edgeMapping() {
|
||||
int piecesPerChannel = 2;
|
||||
|
||||
@@ -100,11 +100,10 @@ class FlowComputationTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
void solve_avoids_sending_from_local_channel_lacking_capacity() {
|
||||
// TODO use balance of local channel as known balance, not upper bound
|
||||
void solve_avoids_sending_from_depleted_local_channel() {
|
||||
when(channelService.getLocalChannel(CHANNEL_ID)).thenReturn(Optional.of(LOCAL_OPEN_CHANNEL));
|
||||
when(channelService.getLocalChannel(CHANNEL_ID_2)).thenReturn(Optional.empty());
|
||||
when(balanceService.getAvailableLocalBalance(CHANNEL_ID)).thenReturn(Coins.ofSatoshis(1));
|
||||
when(balanceService.getAvailableLocalBalance(CHANNEL_ID)).thenReturn(Coins.NONE);
|
||||
Coins amount = Coins.ofSatoshis(100);
|
||||
DirectedChannelEdge largerButDepletedChannel =
|
||||
new DirectedChannelEdge(CHANNEL_ID, LARGE, PUBKEY, PUBKEY_2, POLICY_1);
|
||||
@@ -119,11 +118,10 @@ class FlowComputationTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
void solve_avoids_sending_to_local_channel_lacking_capacity() {
|
||||
// TODO use balance of local channel as known balance, not upper bound
|
||||
void solve_avoids_sending_to_depleted_local_channel() {
|
||||
when(channelService.getLocalChannel(CHANNEL_ID)).thenReturn(Optional.of(LOCAL_OPEN_CHANNEL));
|
||||
when(channelService.getLocalChannel(CHANNEL_ID_2)).thenReturn(Optional.empty());
|
||||
when(balanceService.getAvailableRemoteBalance(CHANNEL_ID)).thenReturn(Coins.ofSatoshis(1));
|
||||
when(balanceService.getAvailableRemoteBalance(CHANNEL_ID)).thenReturn(Coins.NONE);
|
||||
Coins amount = Coins.ofSatoshis(100);
|
||||
DirectedChannelEdge largerButDepletedChannel =
|
||||
new DirectedChannelEdge(CHANNEL_ID, LARGE, PUBKEY_2, PUBKEY, POLICY_1);
|
||||
|
||||
@@ -6,6 +6,7 @@ import org.junit.jupiter.api.Test;
|
||||
import static de.cotto.lndmanagej.pickhardtpayments.model.EdgeFixtures.EDGE;
|
||||
import static de.cotto.lndmanagej.pickhardtpayments.model.EdgeWithLiquidityInformationFixtures.EDGE_WITH_LIQUIDITY_INFORMATION;
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
|
||||
|
||||
class EdgeWithLiquidityInformationTest {
|
||||
@Test
|
||||
@@ -20,6 +21,13 @@ class EdgeWithLiquidityInformationTest {
|
||||
.isEqualTo(new EdgeWithLiquidityInformation(EDGE, knownLiquidity, knownLiquidity));
|
||||
}
|
||||
|
||||
@Test
|
||||
void forLowerBound() {
|
||||
Coins lowerBound = Coins.ofSatoshis(300);
|
||||
assertThat(EdgeWithLiquidityInformation.forLowerBound(EDGE, lowerBound))
|
||||
.isEqualTo(new EdgeWithLiquidityInformation(EDGE, lowerBound, EDGE.capacity()));
|
||||
}
|
||||
|
||||
@Test
|
||||
void forUpperBound() {
|
||||
Coins upperBound = Coins.ofSatoshis(300);
|
||||
@@ -27,6 +35,23 @@ class EdgeWithLiquidityInformationTest {
|
||||
.isEqualTo(new EdgeWithLiquidityInformation(EDGE, Coins.NONE, upperBound));
|
||||
}
|
||||
|
||||
@Test
|
||||
void forLowerAndUpperBound() {
|
||||
Coins lowerBound = Coins.ofSatoshis(100);
|
||||
Coins upperBound = Coins.ofSatoshis(300);
|
||||
assertThat(EdgeWithLiquidityInformation.forLowerAndUpperBound(EDGE, lowerBound, upperBound))
|
||||
.isEqualTo(new EdgeWithLiquidityInformation(EDGE, lowerBound, upperBound));
|
||||
}
|
||||
|
||||
@Test
|
||||
void forLowerAndUpperBound_lower_more_than_upper() {
|
||||
Coins lowerBound = Coins.ofSatoshis(301);
|
||||
Coins upperBound = Coins.ofSatoshis(300);
|
||||
assertThatIllegalArgumentException().isThrownBy(
|
||||
() -> EdgeWithLiquidityInformation.forLowerAndUpperBound(EDGE, lowerBound, upperBound)
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
void availableLiquidityUpperBound() {
|
||||
assertThat(EDGE_WITH_LIQUIDITY_INFORMATION.availableLiquidityUpperBound()).isEqualTo(Coins.ofSatoshis(123));
|
||||
|
||||
Reference in New Issue
Block a user