diff --git a/model/src/main/java/de/cotto/lndmanagej/model/ClosedChannel.java b/model/src/main/java/de/cotto/lndmanagej/model/ClosedChannel.java index 26a4c46f..f9ce2d50 100644 --- a/model/src/main/java/de/cotto/lndmanagej/model/ClosedChannel.java +++ b/model/src/main/java/de/cotto/lndmanagej/model/ClosedChannel.java @@ -39,6 +39,11 @@ public abstract class ClosedChannel extends ClosedOrClosingChannel { return closeHeight; } + @Override + public ClosedChannel getAsClosedChannel() { + return this; + } + @Override public ChannelStatus getStatus() { return new ChannelStatus(isPrivateChannel(), false, true, CLOSED); diff --git a/model/src/main/java/de/cotto/lndmanagej/model/LocalChannel.java b/model/src/main/java/de/cotto/lndmanagej/model/LocalChannel.java index 48292aee..33fce251 100644 --- a/model/src/main/java/de/cotto/lndmanagej/model/LocalChannel.java +++ b/model/src/main/java/de/cotto/lndmanagej/model/LocalChannel.java @@ -38,6 +38,14 @@ public abstract class LocalChannel extends Channel { public abstract Coins getTotalReceived(); + public boolean isClosed() { + return getStatus().closed(); + } + + public ClosedChannel getAsClosedChannel() { + throw new IllegalStateException("Channel is not closed"); + } + @Override @SuppressWarnings("CPD-START") public boolean equals(Object other) { diff --git a/model/src/test/java/de/cotto/lndmanagej/model/BreachForceClosedChannelTest.java b/model/src/test/java/de/cotto/lndmanagej/model/BreachForceClosedChannelTest.java index 37406599..a8d24628 100644 --- a/model/src/test/java/de/cotto/lndmanagej/model/BreachForceClosedChannelTest.java +++ b/model/src/test/java/de/cotto/lndmanagej/model/BreachForceClosedChannelTest.java @@ -81,6 +81,16 @@ class BreachForceClosedChannelTest { .isEqualTo(new ChannelStatus(false, false, true, CLOSED)); } + @Test + void isClosed() { + assertThat(FORCE_CLOSED_CHANNEL_BREACH.isClosed()).isTrue(); + } + + @Test + void getAsClosedChannel() { + assertThat(FORCE_CLOSED_CHANNEL_BREACH.getAsClosedChannel()).isEqualTo(FORCE_CLOSED_CHANNEL_BREACH); + } + @Test void testEquals() { EqualsVerifier.forClass(BreachForceClosedChannel.class).usingGetClass().verify(); diff --git a/model/src/test/java/de/cotto/lndmanagej/model/CoopClosedChannelTest.java b/model/src/test/java/de/cotto/lndmanagej/model/CoopClosedChannelTest.java index 4ffd3926..cb3e538a 100644 --- a/model/src/test/java/de/cotto/lndmanagej/model/CoopClosedChannelTest.java +++ b/model/src/test/java/de/cotto/lndmanagej/model/CoopClosedChannelTest.java @@ -110,6 +110,16 @@ class CoopClosedChannelTest { .isEqualTo(new ChannelStatus(false, false, true, CLOSED)); } + @Test + void isClosed() { + assertThat(CLOSED_CHANNEL.isClosed()).isTrue(); + } + + @Test + void getAsClosedChannel() { + assertThat(CLOSED_CHANNEL.getAsClosedChannel()).isEqualTo(CLOSED_CHANNEL); + } + @Test void testEquals() { EqualsVerifier.forClass(CoopClosedChannel.class).usingGetClass().verify(); diff --git a/model/src/test/java/de/cotto/lndmanagej/model/ForceClosedChannelTest.java b/model/src/test/java/de/cotto/lndmanagej/model/ForceClosedChannelTest.java index 0678f766..7b426e5b 100644 --- a/model/src/test/java/de/cotto/lndmanagej/model/ForceClosedChannelTest.java +++ b/model/src/test/java/de/cotto/lndmanagej/model/ForceClosedChannelTest.java @@ -93,6 +93,16 @@ class ForceClosedChannelTest { .isEqualTo(new ChannelStatus(false, false, true, CLOSED)); } + @Test + void isClosed() { + assertThat(FORCE_CLOSED_CHANNEL_REMOTE.isClosed()).isTrue(); + } + + @Test + void getAsClosedChannel() { + assertThat(FORCE_CLOSED_CHANNEL_REMOTE.getAsClosedChannel()).isEqualTo(FORCE_CLOSED_CHANNEL_REMOTE); + } + @Test void testEquals() { EqualsVerifier.forClass(ForceClosedChannel.class).usingGetClass().verify(); diff --git a/model/src/test/java/de/cotto/lndmanagej/model/ForceClosingChannelTest.java b/model/src/test/java/de/cotto/lndmanagej/model/ForceClosingChannelTest.java index 5daea477..d2ecd58f 100644 --- a/model/src/test/java/de/cotto/lndmanagej/model/ForceClosingChannelTest.java +++ b/model/src/test/java/de/cotto/lndmanagej/model/ForceClosingChannelTest.java @@ -14,6 +14,7 @@ import static de.cotto.lndmanagej.model.OpenInitiator.LOCAL; import static de.cotto.lndmanagej.model.PubkeyFixtures.PUBKEY; import static de.cotto.lndmanagej.model.PubkeyFixtures.PUBKEY_2; import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatIllegalStateException; class ForceClosingChannelTest { @Test @@ -74,6 +75,18 @@ class ForceClosingChannelTest { .isEqualTo(new ChannelStatus(false, false, false, FORCE_CLOSING)); } + @Test + void isClosed() { + assertThat(FORCE_CLOSING_CHANNEL.isClosed()).isFalse(); + } + + @Test + void getAsClosedChannel() { + assertThatIllegalStateException() + .isThrownBy(FORCE_CLOSING_CHANNEL::getAsClosedChannel) + .withMessage("Channel is not closed"); + } + @Test void testEquals() { EqualsVerifier.forClass(ForceClosingChannel.class).usingGetClass().verify(); diff --git a/model/src/test/java/de/cotto/lndmanagej/model/LocalOpenChannelTest.java b/model/src/test/java/de/cotto/lndmanagej/model/LocalOpenChannelTest.java index 09d2ed4c..c033d3cb 100644 --- a/model/src/test/java/de/cotto/lndmanagej/model/LocalOpenChannelTest.java +++ b/model/src/test/java/de/cotto/lndmanagej/model/LocalOpenChannelTest.java @@ -21,6 +21,7 @@ import static de.cotto.lndmanagej.model.OpenInitiator.LOCAL; import static de.cotto.lndmanagej.model.PubkeyFixtures.PUBKEY; import static de.cotto.lndmanagej.model.PubkeyFixtures.PUBKEY_2; import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatIllegalStateException; class LocalOpenChannelTest { @Test @@ -102,6 +103,18 @@ class LocalOpenChannelTest { .isEqualTo(new ChannelStatus(false, false, false, OPEN)); } + @Test + void isClosed() { + assertThat(LOCAL_OPEN_CHANNEL_2.isClosed()).isFalse(); + } + + @Test + void getAsClosedChannel() { + assertThatIllegalStateException() + .isThrownBy(LOCAL_OPEN_CHANNEL_2::getAsClosedChannel) + .withMessage("Channel is not closed"); + } + @Test void testEquals() { EqualsVerifier.forClass(LocalOpenChannel.class).usingGetClass().verify(); diff --git a/model/src/test/java/de/cotto/lndmanagej/model/WaitingCloseChannelTest.java b/model/src/test/java/de/cotto/lndmanagej/model/WaitingCloseChannelTest.java index 85e53577..e0248582 100644 --- a/model/src/test/java/de/cotto/lndmanagej/model/WaitingCloseChannelTest.java +++ b/model/src/test/java/de/cotto/lndmanagej/model/WaitingCloseChannelTest.java @@ -11,6 +11,7 @@ import static de.cotto.lndmanagej.model.PubkeyFixtures.PUBKEY; import static de.cotto.lndmanagej.model.PubkeyFixtures.PUBKEY_2; import static de.cotto.lndmanagej.model.WaitingCloseChannelFixtures.WAITING_CLOSE_CHANNEL; import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatIllegalStateException; class WaitingCloseChannelTest { @Test @@ -64,6 +65,18 @@ class WaitingCloseChannelTest { .isEqualTo(new ChannelStatus(false, false, false, WAITING_CLOSE)); } + @Test + void isClosed() { + assertThat(WAITING_CLOSE_CHANNEL.isClosed()).isFalse(); + } + + @Test + void getAsClosedChannel() { + assertThatIllegalStateException() + .isThrownBy(WAITING_CLOSE_CHANNEL::getAsClosedChannel) + .withMessage("Channel is not closed"); + } + @Test void testEquals() { EqualsVerifier.forClass(WaitingCloseChannel.class).usingGetClass().verify(); diff --git a/web/src/integrationTest/java/de/cotto/lndmanagej/controller/ChannelControllerIT.java b/web/src/integrationTest/java/de/cotto/lndmanagej/controller/ChannelControllerIT.java index d480656c..5e267d96 100644 --- a/web/src/integrationTest/java/de/cotto/lndmanagej/controller/ChannelControllerIT.java +++ b/web/src/integrationTest/java/de/cotto/lndmanagej/controller/ChannelControllerIT.java @@ -40,6 +40,7 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers. @WebMvcTest(controllers = ChannelController.class) class ChannelControllerIT { private static final String CHANNEL_PREFIX = "/api/channel/" + CHANNEL_ID.getShortChannelId(); + private static final String DETAILS_PREFIX = CHANNEL_PREFIX + "/details"; @Autowired private MockMvc mockMvc; @@ -91,8 +92,7 @@ class ChannelControllerIT { @Test void getChannelDetails_not_found() throws Exception { - mockMvc.perform(get(CHANNEL_PREFIX + "/details")) - .andExpect(status().isNotFound()); + mockMvc.perform(get(DETAILS_PREFIX)).andExpect(status().isNotFound()); } @Test @@ -103,7 +103,7 @@ class ChannelControllerIT { when(onChainCostService.getOpenCosts(CHANNEL_ID)).thenReturn(Optional.of(Coins.ofSatoshis(1000))); when(onChainCostService.getCloseCosts(CHANNEL_ID)).thenReturn(Optional.of(Coins.ofSatoshis(2000))); when(balanceService.getBalanceInformation(CHANNEL_ID)).thenReturn(Optional.of(BALANCE_INFORMATION_2)); - mockMvc.perform(get(CHANNEL_PREFIX + "/details")) + mockMvc.perform(get(DETAILS_PREFIX)) .andExpect(jsonPath("$.channelIdShort", is(String.valueOf(CHANNEL_ID.getShortChannelId())))) .andExpect(jsonPath("$.channelIdCompact", is(CHANNEL_ID.getCompactForm()))) .andExpect(jsonPath("$.channelIdCompactLnd", is(CHANNEL_ID.getCompactFormLnd()))) @@ -135,10 +135,31 @@ class ChannelControllerIT { .andExpect(jsonPath("$.policies.remote.baseFeeMilliSat", is(0))); } + @Test + void getChannelDetails_closed_channel() throws Exception { + when(channelService.getLocalChannel(CHANNEL_ID)).thenReturn(Optional.of(CLOSED_CHANNEL)); + mockMvc.perform(get(DETAILS_PREFIX)) + .andExpect(jsonPath("$.totalSent", is("0"))) + .andExpect(jsonPath("$.totalReceived", is("0"))) + .andExpect(jsonPath("$.status.active", is(false))) + .andExpect(jsonPath("$.status.closed", is(true))) + .andExpect(jsonPath("$.status.openClosed", is("CLOSED"))) + .andExpect(jsonPath("$.balance.localBalance", is("0"))) + .andExpect(jsonPath("$.balance.localReserve", is("0"))) + .andExpect(jsonPath("$.balance.localAvailable", is("0"))) + .andExpect(jsonPath("$.balance.remoteBalance", is("0"))) + .andExpect(jsonPath("$.balance.remoteReserve", is("0"))) + .andExpect(jsonPath("$.balance.remoteAvailable", is("0"))) + .andExpect(jsonPath("$.policies.local.enabled", is(false))) + .andExpect(jsonPath("$.policies.remote.enabled", is(false))) + .andExpect(jsonPath("$.closeDetails.initiator", is("REMOTE"))) + .andExpect(jsonPath("$.closeDetails.height", is(987_654))); + } + @Test void getChannelDetails_channel_not_found() throws Exception { when(nodeService.getAlias(PUBKEY_2)).thenReturn(ALIAS_2); - mockMvc.perform(get(CHANNEL_PREFIX + "/details")) + mockMvc.perform(get(DETAILS_PREFIX)) .andExpect(status().isNotFound()); } diff --git a/web/src/main/java/de/cotto/lndmanagej/controller/ChannelController.java b/web/src/main/java/de/cotto/lndmanagej/controller/ChannelController.java index d0a4b8d6..a37a9737 100644 --- a/web/src/main/java/de/cotto/lndmanagej/controller/ChannelController.java +++ b/web/src/main/java/de/cotto/lndmanagej/controller/ChannelController.java @@ -81,7 +81,8 @@ public class ChannelController { remoteAlias, getBalanceInformation(channelId), getOnChainCosts(channelId), - getPoliciesForChannel(localChannel) + getPoliciesForChannel(localChannel), + getCloseDetailsForChannel(localChannel) ); } @@ -129,6 +130,15 @@ public class ChannelController { return new OnChainCostsDto(openCosts, closeCosts); } + private ClosedChannelDetailsDto getCloseDetailsForChannel(LocalChannel localChannel) { + if (localChannel.isClosed()) { + ClosedChannel closedChannel = localChannel.getAsClosedChannel(); + return new ClosedChannelDetailsDto(closedChannel.getCloseInitiator(), closedChannel.getCloseHeight()); + } else { + return new ClosedChannelDetailsDto("", 0); + } + } + private void mark(String name) { metrics.mark(MetricRegistry.name(getClass(), name)); } diff --git a/web/src/main/java/de/cotto/lndmanagej/controller/dto/ChannelDetailsDto.java b/web/src/main/java/de/cotto/lndmanagej/controller/dto/ChannelDetailsDto.java index eb603ee0..f42dd6a7 100644 --- a/web/src/main/java/de/cotto/lndmanagej/controller/dto/ChannelDetailsDto.java +++ b/web/src/main/java/de/cotto/lndmanagej/controller/dto/ChannelDetailsDto.java @@ -21,14 +21,16 @@ public record ChannelDetailsDto( ChannelStatusDto status, BalanceInformationDto balance, OnChainCostsDto onChainCosts, - PoliciesDto policies + PoliciesDto policies, + ClosedChannelDetailsDto closeDetails ) { public ChannelDetailsDto( ChannelDto channelDto, String remoteAlias, BalanceInformation balanceInformation, OnChainCostsDto onChainCosts, - PoliciesDto policies + PoliciesDto policies, + ClosedChannelDetailsDto closeDetails ) { this( channelDto.channelIdShort(), @@ -45,7 +47,8 @@ public record ChannelDetailsDto( channelDto.status(), BalanceInformationDto.createFrom(balanceInformation), onChainCosts, - policies + policies, + closeDetails ); } @@ -54,14 +57,16 @@ public record ChannelDetailsDto( String remoteAlias, BalanceInformation balanceInformation, OnChainCostsDto onChainCosts, - PoliciesDto policies + PoliciesDto policies, + ClosedChannelDetailsDto closeDetails ) { this( new ChannelDto(localChannel), remoteAlias, balanceInformation, onChainCosts, - policies + policies, + closeDetails ); } } diff --git a/web/src/test/java/de/cotto/lndmanagej/controller/ChannelControllerTest.java b/web/src/test/java/de/cotto/lndmanagej/controller/ChannelControllerTest.java index a6a9b874..8aaef876 100644 --- a/web/src/test/java/de/cotto/lndmanagej/controller/ChannelControllerTest.java +++ b/web/src/test/java/de/cotto/lndmanagej/controller/ChannelControllerTest.java @@ -105,7 +105,8 @@ class ChannelControllerTest { ALIAS_2, LOCAL_OPEN_CHANNEL.getBalanceInformation(), ON_CHAIN_COSTS, - FEE_CONFIGURATION_DTO + FEE_CONFIGURATION_DTO, + new ClosedChannelDetailsDto("", 0) ); when(nodeService.getAlias(PUBKEY_2)).thenReturn(ALIAS_2); when(channelService.getLocalChannel(CHANNEL_ID)).thenReturn(Optional.of(LOCAL_OPEN_CHANNEL)); @@ -123,7 +124,8 @@ class ChannelControllerTest { ALIAS_2, LOCAL_OPEN_CHANNEL_PRIVATE.getBalanceInformation(), ON_CHAIN_COSTS, - FEE_CONFIGURATION_DTO + FEE_CONFIGURATION_DTO, + new ClosedChannelDetailsDto("", 0) ); when(nodeService.getAlias(PUBKEY_2)).thenReturn(ALIAS_2); when(channelService.getLocalChannel(CHANNEL_ID)).thenReturn(Optional.of(LOCAL_OPEN_CHANNEL_PRIVATE)); @@ -135,13 +137,17 @@ class ChannelControllerTest { @Test void getDetails_closed() throws NotFoundException { - ChannelDetailsDto expectedDetails = mockForChannelWithoutPolicies(CLOSED_CHANNEL); + ChannelDetailsDto expectedDetails = mockForChannelWithoutPolicies( + CLOSED_CHANNEL, + CLOSED_CHANNEL.getCloseInitiator().toString(), + CLOSED_CHANNEL.getCloseHeight() + ); assertThat(channelController.getDetails(CHANNEL_ID)).isEqualTo(expectedDetails); } @Test void getDetails_waiting_close() throws NotFoundException { - ChannelDetailsDto expectedDetails = mockForChannelWithoutPolicies(WAITING_CLOSE_CHANNEL); + ChannelDetailsDto expectedDetails = mockForChannelWithoutPolicies(WAITING_CLOSE_CHANNEL, "", 0); assertThat(channelController.getDetails(CHANNEL_ID)).isEqualTo(expectedDetails); } @@ -187,7 +193,11 @@ class ChannelControllerTest { .isThrownBy(() -> channelController.getCloseDetails(CHANNEL_ID)); } - private ChannelDetailsDto mockForChannelWithoutPolicies(LocalChannel channel) { + private ChannelDetailsDto mockForChannelWithoutPolicies( + LocalChannel channel, + String closeInitiator, + int closeHeight + ) { when(nodeService.getAlias(PUBKEY_2)).thenReturn(ALIAS_2); when(channelService.getLocalChannel(CHANNEL_ID)).thenReturn(Optional.of(channel)); when(balanceService.getBalanceInformation(CHANNEL_ID)).thenReturn(Optional.empty()); @@ -196,7 +206,8 @@ class ChannelControllerTest { ALIAS_2, BalanceInformation.EMPTY, ON_CHAIN_COSTS, - PoliciesDto.EMPTY + PoliciesDto.EMPTY, + new ClosedChannelDetailsDto(closeInitiator, closeHeight) ); } } \ No newline at end of file diff --git a/web/src/test/java/de/cotto/lndmanagej/controller/dto/ChannelDetailsDtoTest.java b/web/src/test/java/de/cotto/lndmanagej/controller/dto/ChannelDetailsDtoTest.java index 5934dd54..89ffba30 100644 --- a/web/src/test/java/de/cotto/lndmanagej/controller/dto/ChannelDetailsDtoTest.java +++ b/web/src/test/java/de/cotto/lndmanagej/controller/dto/ChannelDetailsDtoTest.java @@ -24,7 +24,8 @@ class ChannelDetailsDtoTest { ALIAS, BALANCE_INFORMATION, ON_CHAIN_COSTS, - PoliciesDto.EMPTY + PoliciesDto.EMPTY, + new ClosedChannelDetailsDto("", 0) ); @Test @@ -79,7 +80,8 @@ class ChannelDetailsDtoTest { ALIAS, BALANCE_INFORMATION, ON_CHAIN_COSTS, - PoliciesDto.EMPTY + PoliciesDto.EMPTY, + new ClosedChannelDetailsDto("", 0) ); ChannelStatusDto channelStatusDto = ChannelStatusDto.createFrom(new ChannelStatus(false, true, false, OPEN));