get policy for non-local channel

This commit is contained in:
Carsten Otto
2022-03-26 20:36:00 +01:00
parent d67a17d707
commit 1d89353744
6 changed files with 91 additions and 247 deletions

View File

@@ -1,52 +1,38 @@
package de.cotto.lndmanagej.service;
import com.codahale.metrics.annotation.Timed;
import de.cotto.lndmanagej.grpc.GrpcFees;
import de.cotto.lndmanagej.grpc.GrpcChannelPolicy;
import de.cotto.lndmanagej.model.ChannelId;
import de.cotto.lndmanagej.model.Coins;
import de.cotto.lndmanagej.model.LocalChannel;
import de.cotto.lndmanagej.model.PoliciesForLocalChannel;
import de.cotto.lndmanagej.model.Policy;
import de.cotto.lndmanagej.model.Pubkey;
import org.springframework.stereotype.Component;
import java.util.Optional;
@Component
public class PolicyService {
private final GrpcFees grpcFees;
private final GrpcChannelPolicy grpcChannelPolicy;
public PolicyService(GrpcFees grpcFees) {
this.grpcFees = grpcFees;
public PolicyService(GrpcChannelPolicy grpcChannelPolicy) {
this.grpcChannelPolicy = grpcChannelPolicy;
}
@Timed
public PoliciesForLocalChannel getPolicies(LocalChannel localChannel) {
ChannelId channelId = localChannel.getId();
return new PoliciesForLocalChannel(
new Policy(getOutgoingFeeRate(channelId), getOutgoingBaseFee(channelId), isEnabledLocal(channelId)),
new Policy(getIncomingFeeRate(channelId), getIncomingBaseFee(channelId), isEnabledRemote(channelId))
grpcChannelPolicy.getLocalPolicy(channelId).orElseThrow(IllegalStateException::new),
grpcChannelPolicy.getRemotePolicy(channelId).orElseThrow(IllegalStateException::new)
);
}
private long getIncomingFeeRate(ChannelId channelId) {
return grpcFees.getIncomingFeeRate(channelId).orElseThrow(IllegalStateException::new);
public Optional<Policy> getPolicyFrom(ChannelId channelId, Pubkey pubkey) {
return grpcChannelPolicy.getPolicyFrom(channelId, pubkey);
}
private long getOutgoingFeeRate(ChannelId channelId) {
return grpcFees.getOutgoingFeeRate(channelId).orElseThrow(IllegalStateException::new);
}
private Coins getOutgoingBaseFee(ChannelId channelId) {
return grpcFees.getOutgoingBaseFee(channelId).orElseThrow(IllegalStateException::new);
}
private Coins getIncomingBaseFee(ChannelId channelId) {
return grpcFees.getIncomingBaseFee(channelId).orElseThrow(IllegalStateException::new);
}
private boolean isEnabledLocal(ChannelId channelId) {
return grpcFees.isEnabledLocal(channelId).orElseThrow(IllegalStateException::new);
}
private boolean isEnabledRemote(ChannelId channelId) {
return grpcFees.isEnabledRemote(channelId).orElseThrow(IllegalStateException::new);
public Optional<Policy> getPolicyTo(ChannelId channelId, Pubkey pubkey) {
return grpcChannelPolicy.getPolicyTo(channelId, pubkey);
}
}

View File

@@ -1,9 +1,8 @@
package de.cotto.lndmanagej.service;
import de.cotto.lndmanagej.grpc.GrpcFees;
import de.cotto.lndmanagej.model.Coins;
import de.cotto.lndmanagej.grpc.GrpcChannelPolicy;
import de.cotto.lndmanagej.model.PoliciesForLocalChannel;
import de.cotto.lndmanagej.model.Policy;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks;
@@ -14,6 +13,9 @@ import java.util.Optional;
import static de.cotto.lndmanagej.model.ChannelIdFixtures.CHANNEL_ID;
import static de.cotto.lndmanagej.model.LocalOpenChannelFixtures.LOCAL_OPEN_CHANNEL;
import static de.cotto.lndmanagej.model.PolicyFixtures.POLICY_1;
import static de.cotto.lndmanagej.model.PolicyFixtures.POLICY_2;
import static de.cotto.lndmanagej.model.PubkeyFixtures.PUBKEY;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatIllegalStateException;
import static org.mockito.Mockito.when;
@@ -24,45 +26,50 @@ class PolicyServiceTest {
private PolicyService policyService;
@Mock
private GrpcFees grpcFees;
private GrpcChannelPolicy grpcChannelPolicy;
@Test
void getPolicies() {
PoliciesForLocalChannel expected = new PoliciesForLocalChannel(
new Policy(789, Coins.ofMilliSatoshis(111), true),
new Policy(123, Coins.ofMilliSatoshis(456), false)
);
@Nested
class ForLocalChannel {
@Test
void getPolicies() {
PoliciesForLocalChannel expected = new PoliciesForLocalChannel(POLICY_1, POLICY_2);
mockFees();
when(grpcFees.isEnabledLocal(CHANNEL_ID)).thenReturn(Optional.of(true));
when(grpcFees.isEnabledRemote(CHANNEL_ID)).thenReturn(Optional.of(false));
when(grpcChannelPolicy.getLocalPolicy(LOCAL_OPEN_CHANNEL.getId())).thenReturn(Optional.of(POLICY_1));
when(grpcChannelPolicy.getRemotePolicy(LOCAL_OPEN_CHANNEL.getId())).thenReturn(Optional.of(POLICY_2));
assertThat(policyService.getPolicies(LOCAL_OPEN_CHANNEL)).isEqualTo(expected);
assertThat(policyService.getPolicies(LOCAL_OPEN_CHANNEL)).isEqualTo(expected);
}
@Test
void getPolicies_not_found() {
assertThatIllegalStateException().isThrownBy(() -> policyService.getPolicies(LOCAL_OPEN_CHANNEL));
}
}
@Test
void getPolicies_enabled_disabled_swapped() {
PoliciesForLocalChannel expected = new PoliciesForLocalChannel(
new Policy(789, Coins.ofMilliSatoshis(111), false),
new Policy(123, Coins.ofMilliSatoshis(456), true)
);
@Nested
class ForDirectedChannel {
@Test
void getPolicyFrom_edge_not_found() {
when(grpcChannelPolicy.getPolicyFrom(CHANNEL_ID, PUBKEY)).thenReturn(Optional.empty());
assertThat(policyService.getPolicyFrom(CHANNEL_ID, PUBKEY)).isEmpty();
}
mockFees();
when(grpcFees.isEnabledLocal(CHANNEL_ID)).thenReturn(Optional.of(false));
when(grpcFees.isEnabledRemote(CHANNEL_ID)).thenReturn(Optional.of(true));
@Test
void getPolicyFrom() {
when(grpcChannelPolicy.getPolicyFrom(CHANNEL_ID, PUBKEY)).thenReturn(Optional.of(POLICY_1));
assertThat(policyService.getPolicyFrom(CHANNEL_ID, PUBKEY)).contains(POLICY_1);
}
assertThat(policyService.getPolicies(LOCAL_OPEN_CHANNEL)).isEqualTo(expected);
}
@Test
void getPolicyTo_edge_not_found() {
when(grpcChannelPolicy.getPolicyTo(CHANNEL_ID, PUBKEY)).thenReturn(Optional.empty());
assertThat(policyService.getPolicyTo(CHANNEL_ID, PUBKEY)).isEmpty();
}
@Test
void getPolicies_not_found() {
assertThatIllegalStateException().isThrownBy(() -> policyService.getPolicies(LOCAL_OPEN_CHANNEL));
}
private void mockFees() {
when(grpcFees.getOutgoingFeeRate(CHANNEL_ID)).thenReturn(Optional.of(789L));
when(grpcFees.getOutgoingBaseFee(CHANNEL_ID)).thenReturn(Optional.of(Coins.ofMilliSatoshis(111)));
when(grpcFees.getIncomingFeeRate(CHANNEL_ID)).thenReturn(Optional.of(123L));
when(grpcFees.getIncomingBaseFee(CHANNEL_ID)).thenReturn(Optional.of(Coins.ofMilliSatoshis(456)));
@Test
void getPolicyTo() {
when(grpcChannelPolicy.getPolicyTo(CHANNEL_ID, PUBKEY)).thenReturn(Optional.of(POLICY_1));
assertThat(policyService.getPolicyTo(CHANNEL_ID, PUBKEY)).contains(POLICY_1);
}
}
}

View File

@@ -3,6 +3,8 @@ package de.cotto.lndmanagej.grpc;
import com.github.benmanes.caffeine.cache.LoadingCache;
import de.cotto.lndmanagej.caching.CacheBuilder;
import de.cotto.lndmanagej.model.ChannelId;
import de.cotto.lndmanagej.model.Coins;
import de.cotto.lndmanagej.model.Policy;
import de.cotto.lndmanagej.model.Pubkey;
import lnrpc.ChannelEdge;
import lnrpc.RoutingPolicy;
@@ -40,24 +42,24 @@ public class GrpcChannelPolicy {
return Optional.empty();
}
public Optional<RoutingPolicy> getLocalPolicy(ChannelId channelId) {
public Optional<Policy> getLocalPolicy(ChannelId channelId) {
Pubkey ownPubkey = grpcGetInfo.getPubkey();
return getPolicyFrom(channelId, ownPubkey);
}
public Optional<RoutingPolicy> getRemotePolicy(ChannelId channelId) {
public Optional<Policy> getRemotePolicy(ChannelId channelId) {
Pubkey ownPubkey = grpcGetInfo.getPubkey();
return getPolicyTo(channelId, ownPubkey);
}
public Optional<RoutingPolicy> getPolicyFrom(ChannelId channelId, Pubkey source) {
public Optional<Policy> getPolicyFrom(ChannelId channelId, Pubkey source) {
String sourcePubkey = source.toString();
return getChannelEdge(channelId).map(
channelEdge -> {
if (sourcePubkey.equals(channelEdge.getNode1Pub())) {
return channelEdge.getNode1Policy();
return toPolicy(channelEdge.getNode1Policy());
} else if (sourcePubkey.equals(channelEdge.getNode2Pub())) {
return channelEdge.getNode2Policy();
return toPolicy(channelEdge.getNode2Policy());
} else {
return null;
}
@@ -65,14 +67,14 @@ public class GrpcChannelPolicy {
);
}
public Optional<RoutingPolicy> getPolicyTo(ChannelId channelId, Pubkey target) {
public Optional<Policy> getPolicyTo(ChannelId channelId, Pubkey target) {
String targetPubkey = target.toString();
return getChannelEdge(channelId).map(
channelEdge -> {
if (targetPubkey.equals(channelEdge.getNode2Pub())) {
return channelEdge.getNode1Policy();
return toPolicy(channelEdge.getNode1Policy());
} else if (targetPubkey.equals(channelEdge.getNode1Pub())) {
return channelEdge.getNode2Policy();
return toPolicy(channelEdge.getNode2Policy());
} else {
return null;
}
@@ -80,6 +82,14 @@ public class GrpcChannelPolicy {
);
}
private Policy toPolicy(RoutingPolicy routingPolicy) {
return new Policy(
routingPolicy.getFeeRateMilliMsat(),
Coins.ofMilliSatoshis(routingPolicy.getFeeBaseMsat()),
!routingPolicy.getDisabled()
);
}
private Optional<ChannelEdge> getChannelEdge(ChannelId channelId) {
return channelEdgeCache.get(channelId);
}

View File

@@ -1,51 +0,0 @@
package de.cotto.lndmanagej.grpc;
import de.cotto.lndmanagej.model.ChannelId;
import de.cotto.lndmanagej.model.Coins;
import lnrpc.RoutingPolicy;
import org.springframework.stereotype.Component;
import java.util.Optional;
@Component
public class GrpcFees {
private final GrpcChannelPolicy grpcChannelPolicy;
public GrpcFees(GrpcChannelPolicy grpcChannelPolicy) {
this.grpcChannelPolicy = grpcChannelPolicy;
}
public Optional<Long> getOutgoingFeeRate(ChannelId channelId) {
return grpcChannelPolicy.getLocalPolicy(channelId).map(RoutingPolicy::getFeeRateMilliMsat);
}
public Optional<Long> getIncomingFeeRate(ChannelId channelId) {
return grpcChannelPolicy.getRemotePolicy(channelId).map(RoutingPolicy::getFeeRateMilliMsat);
}
public Optional<Coins> getOutgoingBaseFee(ChannelId channelId) {
return grpcChannelPolicy.getLocalPolicy(channelId)
.map(RoutingPolicy::getFeeBaseMsat)
.map(Coins::ofMilliSatoshis);
}
public Optional<Coins> getIncomingBaseFee(ChannelId channelId) {
return grpcChannelPolicy.getRemotePolicy(channelId)
.map(RoutingPolicy::getFeeBaseMsat)
.map(Coins::ofMilliSatoshis);
}
@SuppressWarnings("PMD.LinguisticNaming")
public Optional<Boolean> isEnabledLocal(ChannelId channelId) {
return grpcChannelPolicy.getLocalPolicy(channelId)
.map(RoutingPolicy::getDisabled)
.map(b -> !b);
}
@SuppressWarnings("PMD.LinguisticNaming")
public Optional<Boolean> isEnabledRemote(ChannelId channelId) {
return grpcChannelPolicy.getRemotePolicy(channelId)
.map(RoutingPolicy::getDisabled)
.map(b -> !b);
}
}

View File

@@ -1,5 +1,7 @@
package de.cotto.lndmanagej.grpc;
import de.cotto.lndmanagej.model.Coins;
import de.cotto.lndmanagej.model.Policy;
import de.cotto.lndmanagej.model.Pubkey;
import lnrpc.ChannelEdge;
import lnrpc.RoutingPolicy;
@@ -43,13 +45,15 @@ class GrpcChannelPolicyTest {
@Test
void getLocalPolicy_local_first() {
when(grpcService.getChannelEdge(CHANNEL_ID)).thenReturn(Optional.of(channelEdge(PUBKEY, PUBKEY_2)));
assertThat(grpcChannelPolicy.getLocalPolicy(CHANNEL_ID)).contains(routingPolicy(FEE_RATE_FIRST));
assertThat(grpcChannelPolicy.getLocalPolicy(CHANNEL_ID))
.contains(new Policy(FEE_RATE_FIRST, Coins.NONE, true));
}
@Test
void getLocalPolicy_local_second() {
when(grpcService.getChannelEdge(CHANNEL_ID)).thenReturn(Optional.of(channelEdge(PUBKEY_2, PUBKEY)));
assertThat(grpcChannelPolicy.getLocalPolicy(CHANNEL_ID)).contains(routingPolicy(FEE_RATE_SECOND));
assertThat(grpcChannelPolicy.getLocalPolicy(CHANNEL_ID))
.contains(new Policy(FEE_RATE_SECOND, Coins.NONE, true));
}
@Test
@@ -66,13 +70,15 @@ class GrpcChannelPolicyTest {
@Test
void getRemotePolicy_local_first() {
when(grpcService.getChannelEdge(CHANNEL_ID)).thenReturn(Optional.of(channelEdge(PUBKEY, PUBKEY_2)));
assertThat(grpcChannelPolicy.getRemotePolicy(CHANNEL_ID)).contains(routingPolicy(FEE_RATE_SECOND));
assertThat(grpcChannelPolicy.getRemotePolicy(CHANNEL_ID))
.contains(new Policy(FEE_RATE_SECOND, Coins.NONE, true));
}
@Test
void getRemotePolicy_local_second() {
when(grpcService.getChannelEdge(CHANNEL_ID)).thenReturn(Optional.of(channelEdge(PUBKEY_2, PUBKEY)));
assertThat(grpcChannelPolicy.getRemotePolicy(CHANNEL_ID)).contains(routingPolicy(FEE_RATE_FIRST));
assertThat(grpcChannelPolicy.getRemotePolicy(CHANNEL_ID))
.contains(new Policy(FEE_RATE_FIRST, Coins.NONE, true));
}
@Test
@@ -84,13 +90,15 @@ class GrpcChannelPolicyTest {
@Test
void getPolicyFrom_first() {
when(grpcService.getChannelEdge(CHANNEL_ID)).thenReturn(Optional.of(channelEdge(PUBKEY_2, PUBKEY_3)));
assertThat(grpcChannelPolicy.getPolicyFrom(CHANNEL_ID, PUBKEY_2)).contains(routingPolicy(FEE_RATE_FIRST));
assertThat(grpcChannelPolicy.getPolicyFrom(CHANNEL_ID, PUBKEY_2))
.contains(new Policy(FEE_RATE_FIRST, Coins.NONE, true));
}
@Test
void getPolicyFrom_second() {
when(grpcService.getChannelEdge(CHANNEL_ID)).thenReturn(Optional.of(channelEdge(PUBKEY_2, PUBKEY_3)));
assertThat(grpcChannelPolicy.getPolicyFrom(CHANNEL_ID, PUBKEY_3)).contains(routingPolicy(FEE_RATE_SECOND));
assertThat(grpcChannelPolicy.getPolicyFrom(CHANNEL_ID, PUBKEY_3))
.contains(new Policy(FEE_RATE_SECOND, Coins.NONE, true));
}
@Test
@@ -102,13 +110,15 @@ class GrpcChannelPolicyTest {
@Test
void getPolicyTo_first() {
when(grpcService.getChannelEdge(CHANNEL_ID)).thenReturn(Optional.of(channelEdge(PUBKEY_2, PUBKEY_3)));
assertThat(grpcChannelPolicy.getPolicyTo(CHANNEL_ID, PUBKEY_3)).contains(routingPolicy(FEE_RATE_FIRST));
assertThat(grpcChannelPolicy.getPolicyTo(CHANNEL_ID, PUBKEY_3))
.contains(new Policy(FEE_RATE_FIRST, Coins.NONE, true));
}
@Test
void getPolicyTo_second() {
when(grpcService.getChannelEdge(CHANNEL_ID)).thenReturn(Optional.of(channelEdge(PUBKEY_2, PUBKEY_3)));
assertThat(grpcChannelPolicy.getPolicyTo(CHANNEL_ID, PUBKEY_2)).contains(routingPolicy(FEE_RATE_SECOND));
assertThat(grpcChannelPolicy.getPolicyTo(CHANNEL_ID, PUBKEY_2))
.contains(new Policy(FEE_RATE_SECOND, Coins.NONE, true));
}
@Test

View File

@@ -1,118 +0,0 @@
package de.cotto.lndmanagej.grpc;
import de.cotto.lndmanagej.model.Coins;
import lnrpc.RoutingPolicy;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import java.util.Optional;
import static de.cotto.lndmanagej.model.ChannelIdFixtures.CHANNEL_ID;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.when;
@ExtendWith(MockitoExtension.class)
class GrpcFeesTest {
private static final Coins BASE_FEE = Coins.ofMilliSatoshis(1L);
private static final long FEE_RATE = 123L;
@InjectMocks
private GrpcFees grpcFees;
@Mock
private GrpcChannelPolicy grpcChannelPolicy;
@Test
void getOutgoingFeeRate() {
when(grpcChannelPolicy.getLocalPolicy(CHANNEL_ID)).thenReturn(Optional.of(routingPolicy()));
assertThat(grpcFees.getOutgoingFeeRate(CHANNEL_ID)).contains(FEE_RATE);
}
@Test
void getOutgoingFeeRate_empty() {
assertThat(grpcFees.getOutgoingFeeRate(CHANNEL_ID)).isEmpty();
}
@Test
void getIncomingFeeRate() {
when(grpcChannelPolicy.getRemotePolicy(CHANNEL_ID)).thenReturn(Optional.of(routingPolicy()));
assertThat(grpcFees.getIncomingFeeRate(CHANNEL_ID)).contains(FEE_RATE);
}
@Test
void getIncomingFeeRate_empty() {
assertThat(grpcFees.getIncomingFeeRate(CHANNEL_ID)).isEmpty();
}
@Test
void getOutgoingBaseFee() {
when(grpcChannelPolicy.getLocalPolicy(CHANNEL_ID)).thenReturn(Optional.of(routingPolicy()));
assertThat(grpcFees.getOutgoingBaseFee(CHANNEL_ID)).contains(BASE_FEE);
}
@Test
void getOutgoingBaseFee_empty() {
assertThat(grpcFees.getOutgoingBaseFee(CHANNEL_ID)).isEmpty();
}
@Test
void getIncomingBaseFee() {
when(grpcChannelPolicy.getRemotePolicy(CHANNEL_ID)).thenReturn(Optional.of(routingPolicy()));
assertThat(grpcFees.getIncomingBaseFee(CHANNEL_ID)).contains(BASE_FEE);
}
@Test
void getIncomingBaseFee_empty() {
assertThat(grpcFees.getIncomingBaseFee(CHANNEL_ID)).isEmpty();
}
@Test
void isEnabledLocal_false() {
when(grpcChannelPolicy.getLocalPolicy(CHANNEL_ID)).thenReturn(Optional.of(routingPolicy(true)));
assertThat(grpcFees.isEnabledLocal(CHANNEL_ID)).contains(false);
}
@Test
void isEnabledLocal_true() {
when(grpcChannelPolicy.getLocalPolicy(CHANNEL_ID)).thenReturn(Optional.of(routingPolicy(false)));
assertThat(grpcFees.isEnabledLocal(CHANNEL_ID)).contains(true);
}
@Test
void isEnabledLocal_empty() {
assertThat(grpcFees.isEnabledLocal(CHANNEL_ID)).isEmpty();
}
@Test
void isEnabledRemote_false() {
when(grpcChannelPolicy.getRemotePolicy(CHANNEL_ID)).thenReturn(Optional.of(routingPolicy(true)));
assertThat(grpcFees.isEnabledRemote(CHANNEL_ID)).contains(false);
}
@Test
void isEnabledRemote_true() {
when(grpcChannelPolicy.getRemotePolicy(CHANNEL_ID)).thenReturn(Optional.of(routingPolicy(false)));
assertThat(grpcFees.isEnabledRemote(CHANNEL_ID)).contains(true);
}
@Test
void isEnabledRemote_empty() {
assertThat(grpcFees.isEnabledRemote(CHANNEL_ID)).isEmpty();
}
private RoutingPolicy routingPolicy() {
return routingPolicy(false);
}
private RoutingPolicy routingPolicy(boolean disabled) {
return RoutingPolicy.newBuilder()
.setFeeRateMilliMsat(FEE_RATE)
.setFeeBaseMsat(BASE_FEE.milliSatoshis())
.setDisabled(disabled)
.build();
}
}