From dd6341a18d5028a9aa75739686725da048a2dd3b Mon Sep 17 00:00:00 2001 From: Carsten Otto Date: Mon, 15 Nov 2021 11:42:57 +0100 Subject: [PATCH] introduce UnresolvedClosedChannel class --- .../controller/LegacyControllerIT.java | 4 +- .../lndmanagej/service/ChannelService.java | 8 +-- .../controller/LegacyControllerTest.java | 4 +- .../service/ChannelServiceTest.java | 6 +- .../cotto/lndmanagej/grpc/GrpcChannels.java | 34 +++++----- .../de/cotto/lndmanagej/grpc/GrpcService.java | 2 +- .../lndmanagej/grpc/GrpcChannelsTest.java | 27 +++++--- .../lndmanagej/grpc/GrpcHtlcEventsTest.java | 31 +++++---- .../de/cotto/lndmanagej/model/ChannelId.java | 50 +++++++++++++- .../cotto/lndmanagej/model/ClosedChannel.java | 7 -- .../model/UnresolvedClosedChannel.java | 7 ++ .../cotto/lndmanagej/model/ChannelIdTest.java | 67 ++++++++++++++++--- .../lndmanagej/model/HtlcDetailsTest.java | 4 +- ....java => UnresolvedClosedChannelTest.java} | 14 ++-- .../lndmanagej/model/ChannelFixtures.java | 1 + .../model/ClosedChannelFixtures.java | 14 ---- .../lndmanagej/model/HtlcDetailsFixtures.java | 4 +- .../UnresolvedClosedChannelFixtures.java | 15 +++++ 18 files changed, 205 insertions(+), 94 deletions(-) delete mode 100644 model/src/main/java/de/cotto/lndmanagej/model/ClosedChannel.java create mode 100644 model/src/main/java/de/cotto/lndmanagej/model/UnresolvedClosedChannel.java rename model/src/test/java/de/cotto/lndmanagej/model/{ClosedChannelTest.java => UnresolvedClosedChannelTest.java} (64%) delete mode 100644 model/src/testFixtures/java/de/cotto/lndmanagej/model/ClosedChannelFixtures.java create mode 100644 model/src/testFixtures/java/de/cotto/lndmanagej/model/UnresolvedClosedChannelFixtures.java diff --git a/application/src/integrationTest/java/de/cotto/lndmanagej/controller/LegacyControllerIT.java b/application/src/integrationTest/java/de/cotto/lndmanagej/controller/LegacyControllerIT.java index 7dd999a4..331df274 100644 --- a/application/src/integrationTest/java/de/cotto/lndmanagej/controller/LegacyControllerIT.java +++ b/application/src/integrationTest/java/de/cotto/lndmanagej/controller/LegacyControllerIT.java @@ -19,8 +19,6 @@ 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_COMPACT; import static de.cotto.lndmanagej.model.ChannelIdFixtures.CHANNEL_ID_COMPACT_3; -import static de.cotto.lndmanagej.model.ClosedChannelFixtures.CLOSED_CHANNEL; -import static de.cotto.lndmanagej.model.ClosedChannelFixtures.CLOSED_CHANNEL_3; import static de.cotto.lndmanagej.model.LocalOpenChannelFixtures.LOCAL_OPEN_CHANNEL; import static de.cotto.lndmanagej.model.LocalOpenChannelFixtures.LOCAL_OPEN_CHANNEL_3; import static de.cotto.lndmanagej.model.LocalOpenChannelFixtures.LOCAL_OPEN_CHANNEL_TO_NODE_3; @@ -28,6 +26,8 @@ import static de.cotto.lndmanagej.model.NodeFixtures.ALIAS; 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_3; +import static de.cotto.lndmanagej.model.UnresolvedClosedChannelFixtures.CLOSED_CHANNEL; +import static de.cotto.lndmanagej.model.UnresolvedClosedChannelFixtures.CLOSED_CHANNEL_3; import static org.mockito.Mockito.when; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; diff --git a/application/src/main/java/de/cotto/lndmanagej/service/ChannelService.java b/application/src/main/java/de/cotto/lndmanagej/service/ChannelService.java index 9988ec3b..40f7ce17 100644 --- a/application/src/main/java/de/cotto/lndmanagej/service/ChannelService.java +++ b/application/src/main/java/de/cotto/lndmanagej/service/ChannelService.java @@ -3,9 +3,9 @@ package de.cotto.lndmanagej.service; import com.google.common.cache.LoadingCache; import de.cotto.lndmanagej.caching.CacheBuilder; import de.cotto.lndmanagej.grpc.GrpcChannels; -import de.cotto.lndmanagej.model.ClosedChannel; import de.cotto.lndmanagej.model.LocalOpenChannel; import de.cotto.lndmanagej.model.Pubkey; +import de.cotto.lndmanagej.model.UnresolvedClosedChannel; import org.springframework.stereotype.Component; import java.util.Set; @@ -16,7 +16,7 @@ public class ChannelService { private static final int CACHE_EXPIRY_MINUTES = 1; private final LoadingCache> channelsCache; - private final LoadingCache> closedChannelsCache; + private final LoadingCache> closedChannelsCache; public ChannelService(GrpcChannels grpcChannels) { channelsCache = new CacheBuilder() @@ -24,14 +24,14 @@ public class ChannelService { .build(grpcChannels::getChannels); closedChannelsCache = new CacheBuilder() .withExpiryMinutes(CACHE_EXPIRY_MINUTES) - .build(grpcChannels::getClosedChannels); + .build(grpcChannels::getUnresolvedClosedChannels); } public Set getOpenChannels() { return channelsCache.getUnchecked(""); } - public Set getClosedChannels() { + public Set getClosedChannels() { return closedChannelsCache.getUnchecked(""); } diff --git a/application/src/test/java/de/cotto/lndmanagej/controller/LegacyControllerTest.java b/application/src/test/java/de/cotto/lndmanagej/controller/LegacyControllerTest.java index baa1fc03..31e66d6b 100644 --- a/application/src/test/java/de/cotto/lndmanagej/controller/LegacyControllerTest.java +++ b/application/src/test/java/de/cotto/lndmanagej/controller/LegacyControllerTest.java @@ -27,8 +27,6 @@ import static de.cotto.lndmanagej.model.ChannelIdFixtures.CHANNEL_ID_3; import static de.cotto.lndmanagej.model.ChannelIdFixtures.CHANNEL_ID_COMPACT; import static de.cotto.lndmanagej.model.ChannelIdFixtures.CHANNEL_ID_COMPACT_3; import static de.cotto.lndmanagej.model.ChannelIdFixtures.CHANNEL_ID_COMPACT_4; -import static de.cotto.lndmanagej.model.ClosedChannelFixtures.CLOSED_CHANNEL; -import static de.cotto.lndmanagej.model.ClosedChannelFixtures.CLOSED_CHANNEL_3; import static de.cotto.lndmanagej.model.LocalOpenChannelFixtures.LOCAL_OPEN_CHANNEL; import static de.cotto.lndmanagej.model.LocalOpenChannelFixtures.LOCAL_OPEN_CHANNEL_2; import static de.cotto.lndmanagej.model.LocalOpenChannelFixtures.LOCAL_OPEN_CHANNEL_3; @@ -39,6 +37,8 @@ import static de.cotto.lndmanagej.model.NodeFixtures.ALIAS_3; 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_3; +import static de.cotto.lndmanagej.model.UnresolvedClosedChannelFixtures.CLOSED_CHANNEL; +import static de.cotto.lndmanagej.model.UnresolvedClosedChannelFixtures.CLOSED_CHANNEL_3; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.ArgumentMatchers.argThat; import static org.mockito.Mockito.verify; diff --git a/application/src/test/java/de/cotto/lndmanagej/service/ChannelServiceTest.java b/application/src/test/java/de/cotto/lndmanagej/service/ChannelServiceTest.java index ff69aad0..990bb8ef 100644 --- a/application/src/test/java/de/cotto/lndmanagej/service/ChannelServiceTest.java +++ b/application/src/test/java/de/cotto/lndmanagej/service/ChannelServiceTest.java @@ -14,14 +14,14 @@ import java.util.Set; import static de.cotto.lndmanagej.model.BalanceInformationFixtures.BALANCE_INFORMATION; import static de.cotto.lndmanagej.model.ChannelIdFixtures.CHANNEL_ID_2; -import static de.cotto.lndmanagej.model.ClosedChannelFixtures.CLOSED_CHANNEL; -import static de.cotto.lndmanagej.model.ClosedChannelFixtures.CLOSED_CHANNEL_2; import static de.cotto.lndmanagej.model.LocalOpenChannelFixtures.LOCAL_OPEN_CHANNEL; import static de.cotto.lndmanagej.model.LocalOpenChannelFixtures.LOCAL_OPEN_CHANNEL_2; import static de.cotto.lndmanagej.model.LocalOpenChannelFixtures.LOCAL_OPEN_CHANNEL_3; 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_3; +import static de.cotto.lndmanagej.model.UnresolvedClosedChannelFixtures.CLOSED_CHANNEL; +import static de.cotto.lndmanagej.model.UnresolvedClosedChannelFixtures.CLOSED_CHANNEL_2; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.Mockito.when; @@ -60,7 +60,7 @@ class ChannelServiceTest { @Test void getClosedChannels() { - when(grpcChannels.getClosedChannels()).thenReturn(Set.of(CLOSED_CHANNEL, CLOSED_CHANNEL_2)); + when(grpcChannels.getUnresolvedClosedChannels()).thenReturn(Set.of(CLOSED_CHANNEL, CLOSED_CHANNEL_2)); assertThat(channelService.getClosedChannels()) .containsExactlyInAnyOrder(CLOSED_CHANNEL, CLOSED_CHANNEL_2); } diff --git a/grpc-adapter/src/main/java/de/cotto/lndmanagej/grpc/GrpcChannels.java b/grpc-adapter/src/main/java/de/cotto/lndmanagej/grpc/GrpcChannels.java index 843c4e55..61d11c0b 100644 --- a/grpc-adapter/src/main/java/de/cotto/lndmanagej/grpc/GrpcChannels.java +++ b/grpc-adapter/src/main/java/de/cotto/lndmanagej/grpc/GrpcChannels.java @@ -4,13 +4,11 @@ import de.cotto.lndmanagej.model.BalanceInformation; import de.cotto.lndmanagej.model.Channel; import de.cotto.lndmanagej.model.ChannelId; import de.cotto.lndmanagej.model.ChannelPoint; -import de.cotto.lndmanagej.model.ClosedChannel; import de.cotto.lndmanagej.model.Coins; import de.cotto.lndmanagej.model.LocalOpenChannel; import de.cotto.lndmanagej.model.Pubkey; +import de.cotto.lndmanagej.model.UnresolvedClosedChannel; import lnrpc.ChannelCloseSummary; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import org.springframework.stereotype.Component; import java.util.Optional; @@ -22,7 +20,6 @@ import static java.util.stream.Collectors.toSet; public class GrpcChannels { private final GrpcService grpcService; private final GrpcGetInfo grpcGetInfo; - private final Logger logger = LoggerFactory.getLogger(getClass()); public GrpcChannels( GrpcService grpcService, @@ -39,17 +36,16 @@ public class GrpcChannels { .collect(toSet()); } - public Set getClosedChannels() { + public Set getUnresolvedClosedChannels() { Pubkey ownPubkey = grpcGetInfo.getPubkey(); return grpcService.getClosedChannels().stream() - .map(channelCloseSummary -> toClosedChannel(channelCloseSummary, ownPubkey)) - .flatMap(Optional::stream) + .map(channelCloseSummary -> toUnresolvedClosedChannel(channelCloseSummary, ownPubkey)) .collect(toSet()); } public Optional getChannel(ChannelId channelId) { Pubkey ownPubkey = grpcGetInfo.getPubkey(); - long expectedChannelId = channelId.shortChannelId(); + long expectedChannelId = channelId.getShortChannelId(); return grpcService.getChannels().stream() .filter(c -> c.getChanId() == expectedChannelId) .map(lndChannel -> toLocalOpenChannel(lndChannel, ownPubkey)) @@ -73,19 +69,25 @@ public class GrpcChannels { return new LocalOpenChannel(channel, ownPubkey, balanceInformation); } - private Optional toClosedChannel(ChannelCloseSummary channelCloseSummary, Pubkey ownPubkey) { - long chanId = channelCloseSummary.getChanId(); - if (chanId == 0) { - logger.warn("Closed channel with unknown channel ID, ignoring: {}", channelCloseSummary); - return Optional.empty(); - } + private UnresolvedClosedChannel toUnresolvedClosedChannel( + ChannelCloseSummary channelCloseSummary, + Pubkey ownPubkey + ) { Channel channel = Channel.builder() - .withChannelId(ChannelId.fromShortChannelId(chanId)) + .withChannelId(getChannelId(channelCloseSummary)) .withChannelPoint(ChannelPoint.create(channelCloseSummary.getChannelPoint())) .withCapacity(Coins.ofSatoshis(channelCloseSummary.getCapacity())) .withNode1(ownPubkey) .withNode2(Pubkey.create(channelCloseSummary.getRemotePubkey())) .build(); - return Optional.of(new ClosedChannel(channel, ownPubkey)); + return new UnresolvedClosedChannel(channel, ownPubkey); + } + + private ChannelId getChannelId(ChannelCloseSummary channelCloseSummary) { + long chanId = channelCloseSummary.getChanId(); + if (chanId == 0) { + return ChannelId.UNRESOLVED; + } + return ChannelId.fromShortChannelId(chanId); } } diff --git a/grpc-adapter/src/main/java/de/cotto/lndmanagej/grpc/GrpcService.java b/grpc-adapter/src/main/java/de/cotto/lndmanagej/grpc/GrpcService.java index b3e59d97..cfd2424a 100644 --- a/grpc-adapter/src/main/java/de/cotto/lndmanagej/grpc/GrpcService.java +++ b/grpc-adapter/src/main/java/de/cotto/lndmanagej/grpc/GrpcService.java @@ -57,7 +57,7 @@ public class GrpcService extends GrpcBase { public Optional getChannelEdge(ChannelId channelId) { mark("getChanInfo"); - ChanInfoRequest build = ChanInfoRequest.newBuilder().setChanId(channelId.shortChannelId()).build(); + ChanInfoRequest build = ChanInfoRequest.newBuilder().setChanId(channelId.getShortChannelId()).build(); return get(() -> lightningStub.getChanInfo(build)); } diff --git a/grpc-adapter/src/test/java/de/cotto/lndmanagej/grpc/GrpcChannelsTest.java b/grpc-adapter/src/test/java/de/cotto/lndmanagej/grpc/GrpcChannelsTest.java index 6d6cdf7c..79914730 100644 --- a/grpc-adapter/src/test/java/de/cotto/lndmanagej/grpc/GrpcChannelsTest.java +++ b/grpc-adapter/src/test/java/de/cotto/lndmanagej/grpc/GrpcChannelsTest.java @@ -16,12 +16,13 @@ import static de.cotto.lndmanagej.model.ChannelFixtures.CAPACITY; import static de.cotto.lndmanagej.model.ChannelIdFixtures.CHANNEL_ID; import static de.cotto.lndmanagej.model.ChannelIdFixtures.CHANNEL_ID_2; import static de.cotto.lndmanagej.model.ChannelPointFixtures.CHANNEL_POINT; -import static de.cotto.lndmanagej.model.ClosedChannelFixtures.CLOSED_CHANNEL; -import static de.cotto.lndmanagej.model.ClosedChannelFixtures.CLOSED_CHANNEL_2; import static de.cotto.lndmanagej.model.LocalOpenChannelFixtures.LOCAL_OPEN_CHANNEL; import static de.cotto.lndmanagej.model.LocalOpenChannelFixtures.LOCAL_OPEN_CHANNEL_2; import static de.cotto.lndmanagej.model.PubkeyFixtures.PUBKEY; import static de.cotto.lndmanagej.model.PubkeyFixtures.PUBKEY_2; +import static de.cotto.lndmanagej.model.UnresolvedClosedChannelFixtures.CLOSED_CHANNEL; +import static de.cotto.lndmanagej.model.UnresolvedClosedChannelFixtures.CLOSED_CHANNEL_2; +import static de.cotto.lndmanagej.model.UnresolvedClosedChannelFixtures.CLOSED_CHANNEL_UNRESOLVED_ID; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.Mockito.when; @@ -53,24 +54,28 @@ class GrpcChannelsTest { } @Test - void getClosedChannels_empty() { - assertThat(grpcChannels.getClosedChannels()).isEmpty(); + void getUnresolvedClosedChannels_empty() { + assertThat(grpcChannels.getUnresolvedClosedChannels()).isEmpty(); } @Test - void getClosedChannels() { + void getUnresolvedClosedChannels() { when(grpcService.getClosedChannels()).thenReturn( - List.of(closedChannel(CHANNEL_ID.shortChannelId()), closedChannel(CHANNEL_ID_2.shortChannelId())) + List.of(closedChannel(CHANNEL_ID.getShortChannelId()), closedChannel(CHANNEL_ID_2.getShortChannelId())) ); - assertThat(grpcChannels.getClosedChannels()).containsExactlyInAnyOrder(CLOSED_CHANNEL, CLOSED_CHANNEL_2); + assertThat(grpcChannels.getUnresolvedClosedChannels()) + .containsExactlyInAnyOrder(CLOSED_CHANNEL, CLOSED_CHANNEL_2); } @Test - void getClosedChannels_with_zero_channel_id() { + void getUnresolvedClosedChannels_with_zero_channel_id() { when(grpcService.getClosedChannels()).thenReturn( - List.of(closedChannel(CHANNEL_ID.shortChannelId()), closedChannel(0)) + List.of(closedChannel(CHANNEL_ID.getShortChannelId()), closedChannel(0)) + ); + assertThat(grpcChannels.getUnresolvedClosedChannels()).containsExactlyInAnyOrder( + CLOSED_CHANNEL, + CLOSED_CHANNEL_UNRESOLVED_ID ); - assertThat(grpcChannels.getClosedChannels()).containsExactlyInAnyOrder(CLOSED_CHANNEL); } @Test @@ -86,7 +91,7 @@ class GrpcChannelsTest { private Channel channel(ChannelId channelId) { return Channel.newBuilder() - .setChanId(channelId.shortChannelId()) + .setChanId(channelId.getShortChannelId()) .setCapacity(CAPACITY.satoshis()) .setRemotePubkey(PUBKEY_2.toString()) .setChannelPoint(CHANNEL_POINT.toString()) diff --git a/grpc-adapter/src/test/java/de/cotto/lndmanagej/grpc/GrpcHtlcEventsTest.java b/grpc-adapter/src/test/java/de/cotto/lndmanagej/grpc/GrpcHtlcEventsTest.java index 5ca6bf14..07910b18 100644 --- a/grpc-adapter/src/test/java/de/cotto/lndmanagej/grpc/GrpcHtlcEventsTest.java +++ b/grpc-adapter/src/test/java/de/cotto/lndmanagej/grpc/GrpcHtlcEventsTest.java @@ -43,14 +43,14 @@ class GrpcHtlcEventsTest { .build()) .build(); - private final HtlcEvent attemptEvent = getAttempt(CHANNEL_ID.shortChannelId(), CHANNEL_ID_2.shortChannelId()); + private final HtlcEvent attemptEvent = getAttempt(CHANNEL_ID.getShortChannelId(), CHANNEL_ID_2.getShortChannelId()); @Nested class GetForwardFailures { private final HtlcEvent failEvent = getFailEvent( - CHANNEL_ID.shortChannelId(), - CHANNEL_ID_2.shortChannelId() + CHANNEL_ID.getShortChannelId(), + CHANNEL_ID_2.getShortChannelId() ); @Test @@ -85,21 +85,21 @@ class GrpcHtlcEventsTest { @Test void with_other_attempt_and_fail_event() { - HtlcEvent attemptEvent = getAttempt(CHANNEL_ID.shortChannelId(), CHANNEL_ID_3.shortChannelId()); + HtlcEvent attemptEvent = getAttempt(CHANNEL_ID.getShortChannelId(), CHANNEL_ID_3.getShortChannelId()); when(grpcRouterService.getHtlcEvents()).thenReturn(List.of(attemptEvent, failEvent).iterator()); assertThat(grpcHtlcEvents.getForwardFailures()).isEmpty(); } @Test void ignores_failure_event_with_zero_incoming_channel_id() { - HtlcEvent failEvent = getFailEvent(0, CHANNEL_ID_2.shortChannelId()); + HtlcEvent failEvent = getFailEvent(0, CHANNEL_ID_2.getShortChannelId()); when(grpcRouterService.getHtlcEvents()).thenReturn(List.of(failEvent).iterator()); assertThat(grpcHtlcEvents.getForwardFailures()).isEmpty(); } @Test void ignores_failure_event_with_zero_outgoing_channel_id() { - HtlcEvent failEvent = getFailEvent(CHANNEL_ID.shortChannelId(), 0); + HtlcEvent failEvent = getFailEvent(CHANNEL_ID.getShortChannelId(), 0); when(grpcRouterService.getHtlcEvents()).thenReturn(List.of(failEvent).iterator()); assertThat(grpcHtlcEvents.getForwardFailures()).isEmpty(); } @@ -123,7 +123,10 @@ class GrpcHtlcEventsTest { } private void createAndProcessOldEvent(long timestamp) { - HtlcEvent oldAttempt = getBuilderWithDefaults(CHANNEL_ID.shortChannelId(), CHANNEL_ID_2.shortChannelId()) + HtlcEvent oldAttempt = getBuilderWithDefaults( + CHANNEL_ID.getShortChannelId(), + CHANNEL_ID_2.getShortChannelId() + ) .setTimestampNs(timestamp) .setForwardEvent(forwardEvent) .build(); @@ -143,8 +146,8 @@ class GrpcHtlcEventsTest { class GetSettledForwards { private final HtlcEvent settleEvent = getSettleEvent( - CHANNEL_ID.shortChannelId(), - CHANNEL_ID_2.shortChannelId() + CHANNEL_ID.getShortChannelId(), + CHANNEL_ID_2.getShortChannelId() ); @Test @@ -181,35 +184,35 @@ class GrpcHtlcEventsTest { @Test void with_other_attempt_and_settle_event() { - HtlcEvent attemptEvent = getAttempt(CHANNEL_ID.shortChannelId(), CHANNEL_ID_3.shortChannelId()); + HtlcEvent attemptEvent = getAttempt(CHANNEL_ID.getShortChannelId(), CHANNEL_ID_3.getShortChannelId()); when(grpcRouterService.getHtlcEvents()).thenReturn(List.of(attemptEvent, settleEvent).iterator()); assertThat(grpcHtlcEvents.getSettledForwards()).isEmpty(); } @Test void ignores_attempt_with_zero_incoming_channel_id() { - HtlcEvent attemptEvent = getAttempt(0, CHANNEL_ID_2.shortChannelId()); + HtlcEvent attemptEvent = getAttempt(0, CHANNEL_ID_2.getShortChannelId()); when(grpcRouterService.getHtlcEvents()).thenReturn(List.of(attemptEvent).iterator()); assertThat(grpcHtlcEvents.getSettledForwards()).isEmpty(); } @Test void ignores_attempt_with_zero_outgoing_channel_id() { - HtlcEvent attemptEvent = getAttempt(CHANNEL_ID.shortChannelId(), 0); + HtlcEvent attemptEvent = getAttempt(CHANNEL_ID.getShortChannelId(), 0); when(grpcRouterService.getHtlcEvents()).thenReturn(List.of(attemptEvent).iterator()); assertThat(grpcHtlcEvents.getSettledForwards()).isEmpty(); } @Test void ignores_settle_event_with_zero_incoming_channel_id() { - HtlcEvent settleEvent = getSettleEvent(0, CHANNEL_ID_2.shortChannelId()); + HtlcEvent settleEvent = getSettleEvent(0, CHANNEL_ID_2.getShortChannelId()); when(grpcRouterService.getHtlcEvents()).thenReturn(List.of(settleEvent).iterator()); assertThat(grpcHtlcEvents.getSettledForwards()).isEmpty(); } @Test void ignores_settle_event_with_zero_outgoing_channel_id() { - HtlcEvent settleEvent = getSettleEvent(CHANNEL_ID.shortChannelId(), 0); + HtlcEvent settleEvent = getSettleEvent(CHANNEL_ID.getShortChannelId(), 0); when(grpcRouterService.getHtlcEvents()).thenReturn(List.of(settleEvent).iterator()); assertThat(grpcHtlcEvents.getSettledForwards()).isEmpty(); } diff --git a/model/src/main/java/de/cotto/lndmanagej/model/ChannelId.java b/model/src/main/java/de/cotto/lndmanagej/model/ChannelId.java index c5ff13d8..5efff44f 100644 --- a/model/src/main/java/de/cotto/lndmanagej/model/ChannelId.java +++ b/model/src/main/java/de/cotto/lndmanagej/model/ChannelId.java @@ -1,9 +1,18 @@ package de.cotto.lndmanagej.model; -public record ChannelId(long shortChannelId) implements Comparable { +import javax.annotation.Nonnull; +import java.util.Objects; + +public final class ChannelId implements Comparable { + public static final ChannelId UNRESOLVED = new ChannelId(-1); + private final long shortChannelId; private static final int EXPECTED_NUMBER_OF_SEGMENTS = 3; private static final long NOT_BEFORE = 430_103_660_018_532_352L; // January 1st 2016 + private ChannelId(long shortChannelId) { + this.shortChannelId = shortChannelId; + } + public static ChannelId fromShortChannelId(long shortChannelId) { if (shortChannelId < NOT_BEFORE) { throw new IllegalArgumentException("Illegal channel ID"); @@ -25,19 +34,56 @@ public record ChannelId(long shortChannelId) implements Comparable { } public String getCompactForm() { + if (isUnresolved()) { + throw new IllegalStateException("Channel ID must be resolved"); + } long block = shortChannelId >> 40; long transaction = shortChannelId >> 16 & 0xFFFFFF; long output = shortChannelId & 0xFFFF; return block + ":" + transaction + ":" + output; } + public long getShortChannelId() { + if (isUnresolved()) { + throw new IllegalStateException("Channel ID must be resolved"); + } + return shortChannelId; + } + + public boolean isUnresolved() { + return UNRESOLVED.equals(this); + } + + @Override + public boolean equals(Object other) { + if (this == other) { + return true; + } + if (other == null || getClass() != other.getClass()) { + return false; + } + ChannelId channelId = (ChannelId) other; + return shortChannelId == channelId.shortChannelId; + } + + @Override + public int hashCode() { + return Objects.hash(shortChannelId); + } + @Override public String toString() { + if (isUnresolved()) { + return "UNRESOLVED_CHANNEL_ID"; + } return String.valueOf(shortChannelId); } @Override - public int compareTo(ChannelId other) { + public int compareTo(@Nonnull ChannelId other) { + if ((isUnresolved() || other.isUnresolved()) && !this.equals(other)) { + throw new IllegalStateException("Cannot compare with unresolved channel ID"); + } return Long.compare(shortChannelId, other.shortChannelId); } } diff --git a/model/src/main/java/de/cotto/lndmanagej/model/ClosedChannel.java b/model/src/main/java/de/cotto/lndmanagej/model/ClosedChannel.java deleted file mode 100644 index 74426b8c..00000000 --- a/model/src/main/java/de/cotto/lndmanagej/model/ClosedChannel.java +++ /dev/null @@ -1,7 +0,0 @@ -package de.cotto.lndmanagej.model; - -public class ClosedChannel extends LocalChannel { - public ClosedChannel(Channel channel, Pubkey ownPubkey) { - super(channel, ownPubkey); - } -} diff --git a/model/src/main/java/de/cotto/lndmanagej/model/UnresolvedClosedChannel.java b/model/src/main/java/de/cotto/lndmanagej/model/UnresolvedClosedChannel.java new file mode 100644 index 00000000..6c316e08 --- /dev/null +++ b/model/src/main/java/de/cotto/lndmanagej/model/UnresolvedClosedChannel.java @@ -0,0 +1,7 @@ +package de.cotto.lndmanagej.model; + +public class UnresolvedClosedChannel extends LocalChannel { + public UnresolvedClosedChannel(Channel channel, Pubkey ownPubkey) { + super(channel, ownPubkey); + } +} diff --git a/model/src/test/java/de/cotto/lndmanagej/model/ChannelIdTest.java b/model/src/test/java/de/cotto/lndmanagej/model/ChannelIdTest.java index 59a72050..37f34b70 100644 --- a/model/src/test/java/de/cotto/lndmanagej/model/ChannelIdTest.java +++ b/model/src/test/java/de/cotto/lndmanagej/model/ChannelIdTest.java @@ -8,6 +8,7 @@ import static de.cotto.lndmanagej.model.ChannelIdFixtures.CHANNEL_ID; import static de.cotto.lndmanagej.model.ChannelIdFixtures.CHANNEL_ID_COMPACT; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; +import static org.assertj.core.api.Assertions.assertThatIllegalStateException; class ChannelIdTest { @@ -34,7 +35,7 @@ class ChannelIdTest { @Test void january_2016() { ChannelId channelId = ChannelId.fromCompactForm("391177:0:0"); - assertThat(channelId.shortChannelId()).isEqualTo(430_103_660_018_532_352L); + assertThat(channelId.getShortChannelId()).isEqualTo(430_103_660_018_532_352L); } @Test @@ -47,19 +48,19 @@ class ChannelIdTest { @Test void with_x() { ChannelId channelId = ChannelId.fromCompactForm("704776x2087x1"); - assertThat(channelId.shortChannelId()).isEqualTo(774_909_407_114_231_809L); + assertThat(channelId.getShortChannelId()).isEqualTo(774_909_407_114_231_809L); } @Test void large_output() { ChannelId channelId = ChannelId.fromCompactForm("704776x2087x123"); - assertThat(channelId.shortChannelId()).isEqualTo(774_909_407_114_231_931L); + assertThat(channelId.getShortChannelId()).isEqualTo(774_909_407_114_231_931L); } @Test void with_colon() { ChannelId channelId = ChannelId.fromCompactForm("704776:2087:1"); - assertThat(channelId.shortChannelId()).isEqualTo(774_909_407_114_231_809L); + assertThat(channelId.getShortChannelId()).isEqualTo(774_909_407_114_231_809L); } } @@ -90,19 +91,19 @@ class ChannelIdTest { @Test void january_2016() { ChannelId channelId = ChannelId.fromShortChannelId(774_909_407_114_231_809L); - assertThat(channelId.shortChannelId()).isEqualTo(774_909_407_114_231_809L); + assertThat(channelId.getShortChannelId()).isEqualTo(774_909_407_114_231_809L); } @Test void short_channel_id() { ChannelId channelId = ChannelId.fromShortChannelId(774_909_407_114_231_809L); - assertThat(channelId.shortChannelId()).isEqualTo(774_909_407_114_231_809L); + assertThat(channelId.getShortChannelId()).isEqualTo(774_909_407_114_231_809L); } @Test void large_output() { ChannelId channelId = ChannelId.fromShortChannelId(774_909_407_114_231_931L); - assertThat(channelId.shortChannelId()).isEqualTo(774_909_407_114_231_931L); + assertThat(channelId.getShortChannelId()).isEqualTo(774_909_407_114_231_931L); } } @@ -132,6 +133,56 @@ class ChannelIdTest { assertThat(CHANNEL_ID.getCompactForm()).isEqualTo(CHANNEL_ID_COMPACT); } + @Test + void isUnresolved_true() { + assertThat(ChannelId.UNRESOLVED.isUnresolved()).isTrue(); + } + + @Test + void isUnresolved_false() { + assertThat(CHANNEL_ID.isUnresolved()).isFalse(); + } + + @Test + void getCompactForm_unresolved() { + assertThatIllegalStateException() + .isThrownBy(ChannelId.UNRESOLVED::getCompactForm) + .withMessage("Channel ID must be resolved"); + } + + @Test + void shortChannelId_unresolved() { + assertThatIllegalStateException() + .isThrownBy(ChannelId.UNRESOLVED::getShortChannelId) + .withMessage("Channel ID must be resolved"); + } + + @Test + void compareTo_unresolved_to_other() { + assertThatIllegalStateException() + .isThrownBy(() -> ChannelId.UNRESOLVED.compareTo(CHANNEL_ID)) + .withMessage("Cannot compare with unresolved channel ID"); + } + + @Test + void compareTo_other_to_unresolved() { + assertThatIllegalStateException() + .isThrownBy(() -> CHANNEL_ID.compareTo(ChannelId.UNRESOLVED)) + .withMessage("Cannot compare with unresolved channel ID"); + } + + @Test + @SuppressWarnings("SelfComparison") + void compareTo_unresolved() { + //noinspection EqualsWithItself + assertThat(ChannelId.UNRESOLVED.compareTo(ChannelId.UNRESOLVED)).isEqualTo(0); + } + + @Test + void toString_unresolved() { + assertThat(ChannelId.UNRESOLVED).hasToString("UNRESOLVED_CHANNEL_ID"); + } + @Test void testEquals() { EqualsVerifier.forClass(ChannelId.class).verify(); @@ -139,7 +190,7 @@ class ChannelIdTest { @Test void testToString() { - String expectedString = String.valueOf(CHANNEL_ID.shortChannelId()); + String expectedString = String.valueOf(CHANNEL_ID.getShortChannelId()); assertThat(CHANNEL_ID).hasToString(expectedString); } } \ No newline at end of file diff --git a/model/src/test/java/de/cotto/lndmanagej/model/HtlcDetailsTest.java b/model/src/test/java/de/cotto/lndmanagej/model/HtlcDetailsTest.java index 2fba69e6..44d325c9 100644 --- a/model/src/test/java/de/cotto/lndmanagej/model/HtlcDetailsTest.java +++ b/model/src/test/java/de/cotto/lndmanagej/model/HtlcDetailsTest.java @@ -21,8 +21,8 @@ class HtlcDetailsTest { @Test void builder_with_all_arguments() { HtlcDetails htlcDetails = HtlcDetails.builder() - .withIncomingChannelId(CHANNEL_ID.shortChannelId()) - .withOutgoingChannelId(CHANNEL_ID_2.shortChannelId()) + .withIncomingChannelId(CHANNEL_ID.getShortChannelId()) + .withOutgoingChannelId(CHANNEL_ID_2.getShortChannelId()) .withTimestamp(789) .withIncomingHtlcId(1) .withOutgoingHtlcId(2) diff --git a/model/src/test/java/de/cotto/lndmanagej/model/ClosedChannelTest.java b/model/src/test/java/de/cotto/lndmanagej/model/UnresolvedClosedChannelTest.java similarity index 64% rename from model/src/test/java/de/cotto/lndmanagej/model/ClosedChannelTest.java rename to model/src/test/java/de/cotto/lndmanagej/model/UnresolvedClosedChannelTest.java index dbe67b90..d4d89bc2 100644 --- a/model/src/test/java/de/cotto/lndmanagej/model/ClosedChannelTest.java +++ b/model/src/test/java/de/cotto/lndmanagej/model/UnresolvedClosedChannelTest.java @@ -10,25 +10,27 @@ import static de.cotto.lndmanagej.model.PubkeyFixtures.PUBKEY_3; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; -class ClosedChannelTest { +class UnresolvedClosedChannelTest { @Test void getRemotePubkey() { Channel channel = ChannelFixtures.create(PUBKEY_2, PUBKEY, CHANNEL_ID); - ClosedChannel closedChannel = new ClosedChannel(channel, PUBKEY); - assertThat(closedChannel.getRemotePubkey()).isEqualTo(PUBKEY_2); + UnresolvedClosedChannel unresolvedClosedChannel = + new UnresolvedClosedChannel(channel, PUBKEY); + assertThat(unresolvedClosedChannel.getRemotePubkey()).isEqualTo(PUBKEY_2); } @Test void getRemotePubkey_swapped() { Channel channel = ChannelFixtures.create(PUBKEY_3, PUBKEY_2, CHANNEL_ID); - ClosedChannel closedChannel = new ClosedChannel(channel, PUBKEY_3); - assertThat(closedChannel.getRemotePubkey()).isEqualTo(PUBKEY_2); + UnresolvedClosedChannel unresolvedClosedChannel = + new UnresolvedClosedChannel(channel, PUBKEY_3); + assertThat(unresolvedClosedChannel.getRemotePubkey()).isEqualTo(PUBKEY_2); } @Test void ownPubkey_not_in_pubkey_set() { assertThatIllegalArgumentException() - .isThrownBy(() -> new ClosedChannel(CHANNEL_2, PUBKEY_3)) + .isThrownBy(() -> new UnresolvedClosedChannel(CHANNEL_2, PUBKEY_3)) .withMessage("Channel must have given pubkey as peer"); } } \ No newline at end of file diff --git a/model/src/testFixtures/java/de/cotto/lndmanagej/model/ChannelFixtures.java b/model/src/testFixtures/java/de/cotto/lndmanagej/model/ChannelFixtures.java index 4d832a5f..26c322a7 100644 --- a/model/src/testFixtures/java/de/cotto/lndmanagej/model/ChannelFixtures.java +++ b/model/src/testFixtures/java/de/cotto/lndmanagej/model/ChannelFixtures.java @@ -14,6 +14,7 @@ public final class ChannelFixtures { public static final Coins CAPACITY_2 = Coins.ofSatoshis(42_000_000L); public static final Channel CHANNEL = create(PUBKEY, PUBKEY_2, CHANNEL_ID); + public static final Channel CHANNEL_UNRESOLVED_ID = create(PUBKEY, PUBKEY_2, ChannelId.UNRESOLVED); public static final Channel CHANNEL_2 = create(PUBKEY, PUBKEY_2, CHANNEL_ID_2); public static final Channel CHANNEL_3 = create(PUBKEY, PUBKEY_2, CHANNEL_ID_3); public static final Channel CHANNEL_TO_NODE_3 = create(PUBKEY, PUBKEY_3, CHANNEL_ID_4, CAPACITY_2); diff --git a/model/src/testFixtures/java/de/cotto/lndmanagej/model/ClosedChannelFixtures.java b/model/src/testFixtures/java/de/cotto/lndmanagej/model/ClosedChannelFixtures.java deleted file mode 100644 index 6d2995df..00000000 --- a/model/src/testFixtures/java/de/cotto/lndmanagej/model/ClosedChannelFixtures.java +++ /dev/null @@ -1,14 +0,0 @@ -package de.cotto.lndmanagej.model; - -import static de.cotto.lndmanagej.model.ChannelFixtures.CHANNEL; -import static de.cotto.lndmanagej.model.ChannelFixtures.CHANNEL_2; -import static de.cotto.lndmanagej.model.ChannelFixtures.CHANNEL_3; -import static de.cotto.lndmanagej.model.ChannelFixtures.CHANNEL_TO_NODE_3; -import static de.cotto.lndmanagej.model.PubkeyFixtures.PUBKEY; - -public class ClosedChannelFixtures { - public static final ClosedChannel CLOSED_CHANNEL = new ClosedChannel(CHANNEL, PUBKEY); - public static final ClosedChannel CLOSED_CHANNEL_2 = new ClosedChannel(CHANNEL_2, PUBKEY); - public static final ClosedChannel CLOSED_CHANNEL_3 = new ClosedChannel(CHANNEL_3, PUBKEY); - public static final ClosedChannel CLOSED_CHANNEL_TO_NODE_3 = new ClosedChannel(CHANNEL_TO_NODE_3, PUBKEY); -} diff --git a/model/src/testFixtures/java/de/cotto/lndmanagej/model/HtlcDetailsFixtures.java b/model/src/testFixtures/java/de/cotto/lndmanagej/model/HtlcDetailsFixtures.java index ac5dec57..1101fe7f 100644 --- a/model/src/testFixtures/java/de/cotto/lndmanagej/model/HtlcDetailsFixtures.java +++ b/model/src/testFixtures/java/de/cotto/lndmanagej/model/HtlcDetailsFixtures.java @@ -5,8 +5,8 @@ import static de.cotto.lndmanagej.model.ChannelIdFixtures.CHANNEL_ID_2; public class HtlcDetailsFixtures { public static final HtlcDetails HTLC_DETAILS = HtlcDetails.builder() - .withIncomingChannelId(CHANNEL_ID.shortChannelId()) - .withOutgoingChannelId(CHANNEL_ID_2.shortChannelId()) + .withIncomingChannelId(CHANNEL_ID.getShortChannelId()) + .withOutgoingChannelId(CHANNEL_ID_2.getShortChannelId()) .withTimestamp(789) .withIncomingHtlcId(1) .withOutgoingHtlcId(2) diff --git a/model/src/testFixtures/java/de/cotto/lndmanagej/model/UnresolvedClosedChannelFixtures.java b/model/src/testFixtures/java/de/cotto/lndmanagej/model/UnresolvedClosedChannelFixtures.java new file mode 100644 index 00000000..fc769135 --- /dev/null +++ b/model/src/testFixtures/java/de/cotto/lndmanagej/model/UnresolvedClosedChannelFixtures.java @@ -0,0 +1,15 @@ +package de.cotto.lndmanagej.model; + +import static de.cotto.lndmanagej.model.ChannelFixtures.CHANNEL; +import static de.cotto.lndmanagej.model.ChannelFixtures.CHANNEL_2; +import static de.cotto.lndmanagej.model.ChannelFixtures.CHANNEL_3; +import static de.cotto.lndmanagej.model.ChannelFixtures.CHANNEL_UNRESOLVED_ID; +import static de.cotto.lndmanagej.model.PubkeyFixtures.PUBKEY; + +public class UnresolvedClosedChannelFixtures { + public static final UnresolvedClosedChannel CLOSED_CHANNEL = new UnresolvedClosedChannel(CHANNEL, PUBKEY); + public static final UnresolvedClosedChannel CLOSED_CHANNEL_UNRESOLVED_ID = + new UnresolvedClosedChannel(CHANNEL_UNRESOLVED_ID, PUBKEY); + public static final UnresolvedClosedChannel CLOSED_CHANNEL_2 = new UnresolvedClosedChannel(CHANNEL_2, PUBKEY); + public static final UnresolvedClosedChannel CLOSED_CHANNEL_3 = new UnresolvedClosedChannel(CHANNEL_3, PUBKEY); +}