From 67e8bc6637701f7bead7634fe65d5fa4567eb450 Mon Sep 17 00:00:00 2001 From: Carsten Otto Date: Sun, 12 Dec 2021 21:02:17 +0100 Subject: [PATCH] allow users to specify resolutions manually --- .../src/main/resources/application.properties | 3 +- .../service/ChannelIdResolverImpl.java | 3 +- .../service/OnChainCostService.java | 7 +- .../service/OpenInitiatorResolverImpl.java | 3 +- .../service/TransactionBackgroundLoader.java | 15 +-- .../TransactionBackgroundLoaderTest.java | 19 ++-- grpc-adapter/build.gradle | 5 +- .../cotto/lndmanagej/grpc/GrpcChannels.java | 3 +- .../lndmanagej/grpc/GrpcClosedChannels.java | 28 ++++-- .../lndmanagej/grpc/GrpcTransactions.java | 11 ++- .../lndmanagej/grpc/GrpcChannelsTest.java | 2 +- .../grpc/GrpcClosedChannelsTest.java | 21 +++- .../lndmanagej/grpc/GrpcTransactionsTest.java | 14 ++- hardcoded/build.gradle | 10 ++ .../hardcoded/HardcodedService.java | 55 +++++++++++ .../lndmanagej/hardcoded/IniFileReader.java | 57 +++++++++++ .../hardcoded/HardcodedServiceTest.java | 77 +++++++++++++++ .../hardcoded/IniFileReaderTest.java | 96 +++++++++++++++++++ .../model/BreachForceClosedChannel.java | 2 +- .../cotto/lndmanagej/model/ChannelPoint.java | 10 +- .../cotto/lndmanagej/model/ClosedChannel.java | 2 +- .../model/ClosedChannelBuilder.java | 4 +- .../model/ClosedOrClosingChannel.java | 6 +- .../lndmanagej/model/CoopClosedChannel.java | 2 +- .../lndmanagej/model/ForceClosedChannel.java | 2 +- .../lndmanagej/model/ForceClosingChannel.java | 2 +- .../model/OpenInitiatorResolver.java | 2 +- .../de/cotto/lndmanagej/model/Resolution.java | 2 +- .../lndmanagej/model/TransactionHash.java | 48 ++++++++++ .../lndmanagej/model/TransactionHashTest.java | 41 ++++++++ .../model/ChannelPointFixtures.java | 9 +- settings.gradle | 1 + .../transactions/TransactionDao.java | 3 +- .../download/BitapsTransactionDto.java | 9 +- .../download/BlockcypherTransactionDto.java | 9 +- .../transactions/download/TransactionDto.java | 5 +- .../download/TransactionProvider.java | 12 ++- .../transactions/model/Transaction.java | 3 +- .../persistence/TransactionDaoImpl.java | 5 +- .../persistence/TransactionJpaDto.java | 5 +- .../service/TransactionService.java | 17 ++-- .../download/TransactionProviderTest.java | 5 +- .../persistence/TransactionDaoImplTest.java | 4 +- .../TransactionJpaDtoFixtures.java | 2 +- 44 files changed, 541 insertions(+), 100 deletions(-) create mode 100644 hardcoded/build.gradle create mode 100644 hardcoded/src/main/java/de/cotto/lndmanagej/hardcoded/HardcodedService.java create mode 100644 hardcoded/src/main/java/de/cotto/lndmanagej/hardcoded/IniFileReader.java create mode 100644 hardcoded/src/test/java/de/cotto/lndmanagej/hardcoded/HardcodedServiceTest.java create mode 100644 hardcoded/src/test/java/de/cotto/lndmanagej/hardcoded/IniFileReaderTest.java create mode 100644 model/src/main/java/de/cotto/lndmanagej/model/TransactionHash.java create mode 100644 model/src/test/java/de/cotto/lndmanagej/model/TransactionHashTest.java diff --git a/application/src/main/resources/application.properties b/application/src/main/resources/application.properties index e387710d..a599f1df 100644 --- a/application/src/main/resources/application.properties +++ b/application/src/main/resources/application.properties @@ -37,4 +37,5 @@ feign.client.config.default.readTimeout=10000 lndmanagej.macaroon-file=${user.home}/.lnd/data/chain/bitcoin/mainnet/admin.macaroon lndmanagej.cert-file=${user.home}/.lnd/tls.cert lndmanagej.host=localhost -lndmanagej.port=10009 \ No newline at end of file +lndmanagej.port=10009 +lndmanagej.hardcoded-path=${user.home}/.config/lnd-manageJ.conf \ No newline at end of file diff --git a/backend/src/main/java/de/cotto/lndmanagej/service/ChannelIdResolverImpl.java b/backend/src/main/java/de/cotto/lndmanagej/service/ChannelIdResolverImpl.java index 9eebc1bd..ff13eee2 100644 --- a/backend/src/main/java/de/cotto/lndmanagej/service/ChannelIdResolverImpl.java +++ b/backend/src/main/java/de/cotto/lndmanagej/service/ChannelIdResolverImpl.java @@ -4,6 +4,7 @@ import com.codahale.metrics.annotation.Timed; import de.cotto.lndmanagej.model.ChannelId; import de.cotto.lndmanagej.model.ChannelIdResolver; import de.cotto.lndmanagej.model.ChannelPoint; +import de.cotto.lndmanagej.model.TransactionHash; import de.cotto.lndmanagej.transactions.model.Transaction; import de.cotto.lndmanagej.transactions.service.TransactionService; import org.slf4j.Logger; @@ -24,7 +25,7 @@ public class ChannelIdResolverImpl implements ChannelIdResolver { @Timed @Override public Optional resolveFromChannelPoint(ChannelPoint channelPoint) { - String transactionHash = channelPoint.getTransactionHash(); + TransactionHash transactionHash = channelPoint.getTransactionHash(); Optional transactionOptional = transactionService.getTransaction(transactionHash); if (transactionOptional.isEmpty()) { logger.warn("Unable resolve transaction ID for {}", transactionHash); diff --git a/backend/src/main/java/de/cotto/lndmanagej/service/OnChainCostService.java b/backend/src/main/java/de/cotto/lndmanagej/service/OnChainCostService.java index add9ad88..6fc710c6 100644 --- a/backend/src/main/java/de/cotto/lndmanagej/service/OnChainCostService.java +++ b/backend/src/main/java/de/cotto/lndmanagej/service/OnChainCostService.java @@ -11,6 +11,7 @@ import de.cotto.lndmanagej.model.OnChainCosts; import de.cotto.lndmanagej.model.OpenInitiator; import de.cotto.lndmanagej.model.Pubkey; import de.cotto.lndmanagej.model.Resolution; +import de.cotto.lndmanagej.model.TransactionHash; import de.cotto.lndmanagej.transactions.model.Transaction; import de.cotto.lndmanagej.transactions.service.TransactionService; import org.springframework.stereotype.Component; @@ -58,7 +59,7 @@ public class OnChainCostService { @Timed public Optional getOpenCostsForChannel(LocalChannel localChannel) { if (localChannel.getOpenInitiator().equals(OpenInitiator.LOCAL)) { - String openTransactionHash = localChannel.getChannelPoint().getTransactionHash(); + TransactionHash openTransactionHash = localChannel.getChannelPoint().getTransactionHash(); return transactionService.getTransaction(openTransactionHash) .map(Transaction::fees) .map(Coins::satoshis) @@ -114,11 +115,11 @@ public class OnChainCostService { .reduce(Coins.NONE, Coins::add); } - private long getNumberOfChannelsWithOpenTransactionHash(String openTransactionHash) { + private long getNumberOfChannelsWithOpenTransactionHash(TransactionHash openTransactionHash) { return channelService.getAllLocalChannels() .map(LocalChannel::getChannelPoint) .map(ChannelPoint::getTransactionHash) - .filter(x -> x.equals(openTransactionHash)) + .filter(hash -> hash.equals(openTransactionHash)) .count(); } } diff --git a/backend/src/main/java/de/cotto/lndmanagej/service/OpenInitiatorResolverImpl.java b/backend/src/main/java/de/cotto/lndmanagej/service/OpenInitiatorResolverImpl.java index 49195d4f..7cf8e591 100644 --- a/backend/src/main/java/de/cotto/lndmanagej/service/OpenInitiatorResolverImpl.java +++ b/backend/src/main/java/de/cotto/lndmanagej/service/OpenInitiatorResolverImpl.java @@ -3,6 +3,7 @@ package de.cotto.lndmanagej.service; import com.codahale.metrics.annotation.Timed; import de.cotto.lndmanagej.model.OpenInitiator; import de.cotto.lndmanagej.model.OpenInitiatorResolver; +import de.cotto.lndmanagej.model.TransactionHash; import de.cotto.lndmanagej.transactions.service.TransactionService; import org.springframework.stereotype.Component; @@ -16,7 +17,7 @@ public class OpenInitiatorResolverImpl implements OpenInitiatorResolver { @Timed @Override - public OpenInitiator resolveFromOpenTransactionHash(String transactionHash) { + public OpenInitiator resolveFromOpenTransactionHash(TransactionHash transactionHash) { Boolean knownByLnd = transactionService.isKnownByLnd(transactionHash).orElse(null); if (knownByLnd == null) { return OpenInitiator.UNKNOWN; diff --git a/backend/src/main/java/de/cotto/lndmanagej/service/TransactionBackgroundLoader.java b/backend/src/main/java/de/cotto/lndmanagej/service/TransactionBackgroundLoader.java index ce5148b6..1f6d3df5 100644 --- a/backend/src/main/java/de/cotto/lndmanagej/service/TransactionBackgroundLoader.java +++ b/backend/src/main/java/de/cotto/lndmanagej/service/TransactionBackgroundLoader.java @@ -6,6 +6,7 @@ import de.cotto.lndmanagej.model.ClosedChannel; import de.cotto.lndmanagej.model.ClosedOrClosingChannel; import de.cotto.lndmanagej.model.ForceClosedChannel; import de.cotto.lndmanagej.model.Resolution; +import de.cotto.lndmanagej.model.TransactionHash; import de.cotto.lndmanagej.transactions.service.TransactionService; import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Component; @@ -34,15 +35,15 @@ public class TransactionBackgroundLoader { .ifPresent(transactionService::getTransaction); } - private Stream getTransactionHashes() { - Stream openTransactionHashes = getOpenTransactionHashes(); - Stream closeTransactionHashes = getCloseTransactionHashes(); - Stream sweepTransactionHashes = getSweepTransactionHashes(); + private Stream getTransactionHashes() { + Stream openTransactionHashes = getOpenTransactionHashes(); + Stream closeTransactionHashes = getCloseTransactionHashes(); + Stream sweepTransactionHashes = getSweepTransactionHashes(); return Stream.of(openTransactionHashes, closeTransactionHashes, sweepTransactionHashes) .flatMap(s -> s); } - private Stream getOpenTransactionHashes() { + private Stream getOpenTransactionHashes() { return Stream.of( channelService.getOpenChannels(), channelService.getClosedChannels(), @@ -54,13 +55,13 @@ public class TransactionBackgroundLoader { .map(ChannelPoint::getTransactionHash); } - private Stream getCloseTransactionHashes() { + private Stream getCloseTransactionHashes() { return Stream.of(channelService.getClosedChannels(), channelService.getForceClosingChannels()) .flatMap(Collection::stream) .map(ClosedOrClosingChannel::getCloseTransactionHash); } - private Stream getSweepTransactionHashes() { + private Stream getSweepTransactionHashes() { return channelService.getClosedChannels().stream() .filter(ClosedChannel::isForceClosed) .map(ClosedChannel::getAsForceClosedChannel) diff --git a/backend/src/test/java/de/cotto/lndmanagej/service/TransactionBackgroundLoaderTest.java b/backend/src/test/java/de/cotto/lndmanagej/service/TransactionBackgroundLoaderTest.java index 5775526c..5516c0ca 100644 --- a/backend/src/test/java/de/cotto/lndmanagej/service/TransactionBackgroundLoaderTest.java +++ b/backend/src/test/java/de/cotto/lndmanagej/service/TransactionBackgroundLoaderTest.java @@ -3,6 +3,7 @@ package de.cotto.lndmanagej.service; import de.cotto.lndmanagej.model.ChannelCoreInformation; import de.cotto.lndmanagej.model.ChannelPoint; import de.cotto.lndmanagej.model.LocalOpenChannel; +import de.cotto.lndmanagej.model.TransactionHash; import de.cotto.lndmanagej.transactions.service.TransactionService; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; @@ -76,7 +77,7 @@ class TransactionBackgroundLoaderTest { @Test void update_from_closed_channels() { - String transactionHash = CLOSED_CHANNEL.getChannelPoint().getTransactionHash(); + TransactionHash transactionHash = CLOSED_CHANNEL.getChannelPoint().getTransactionHash(); when(channelService.getClosedChannels()).thenReturn(Set.of(CLOSED_CHANNEL)); when(transactionService.isUnknown(transactionHash)).thenReturn(true); transactionBackgroundLoader.loadTransactionForOneChannel(); @@ -85,7 +86,7 @@ class TransactionBackgroundLoaderTest { @Test void update_from_closed_channels_close_transaction() { - String closeTransactionHash = CLOSED_CHANNEL.getCloseTransactionHash(); + TransactionHash closeTransactionHash = CLOSED_CHANNEL.getCloseTransactionHash(); when(channelService.getClosedChannels()).thenReturn(Set.of(CLOSED_CHANNEL)); when(transactionService.isUnknown(CLOSED_CHANNEL.getChannelPoint().getTransactionHash())).thenReturn(false); when(transactionService.isUnknown(closeTransactionHash)).thenReturn(true); @@ -95,7 +96,7 @@ class TransactionBackgroundLoaderTest { @Test void update_from_waiting_close_channels() { - String transactionHash = WAITING_CLOSE_CHANNEL.getChannelPoint().getTransactionHash(); + TransactionHash transactionHash = WAITING_CLOSE_CHANNEL.getChannelPoint().getTransactionHash(); when(channelService.getWaitingCloseChannels()).thenReturn(Set.of(WAITING_CLOSE_CHANNEL)); when(transactionService.isUnknown(transactionHash)).thenReturn(true); transactionBackgroundLoader.loadTransactionForOneChannel(); @@ -104,7 +105,7 @@ class TransactionBackgroundLoaderTest { @Test void update_from_force_closing_channels() { - String transactionHash = FORCE_CLOSING_CHANNEL.getChannelPoint().getTransactionHash(); + TransactionHash transactionHash = FORCE_CLOSING_CHANNEL.getChannelPoint().getTransactionHash(); when(transactionService.isUnknown(transactionHash)).thenReturn(true); when(channelService.getForceClosingChannels()).thenReturn(Set.of(FORCE_CLOSING_CHANNEL)); @@ -114,10 +115,10 @@ class TransactionBackgroundLoaderTest { @Test void update_from_force_closing_channels_close_transaction() { - String closeTransactionHash = FORCE_CLOSING_CHANNEL.getCloseTransactionHash(); + TransactionHash closeTransactionHash = FORCE_CLOSING_CHANNEL.getCloseTransactionHash(); when(transactionService.isUnknown(closeTransactionHash)).thenReturn(true); - String openTransactionHash = FORCE_CLOSING_CHANNEL.getChannelPoint().getTransactionHash(); + TransactionHash openTransactionHash = FORCE_CLOSING_CHANNEL.getChannelPoint().getTransactionHash(); when(transactionService.isUnknown(openTransactionHash)).thenReturn(false); when(channelService.getForceClosingChannels()).thenReturn(Set.of(FORCE_CLOSING_CHANNEL)); @@ -127,12 +128,12 @@ class TransactionBackgroundLoaderTest { @Test void update_from_force_closing_channels_ignores_pending_htlc_output() { - String htlcOutpointHash = FORCE_CLOSING_CHANNEL.getHtlcOutpoints().stream() + TransactionHash htlcOutpointHash = FORCE_CLOSING_CHANNEL.getHtlcOutpoints().stream() .map(ChannelPoint::getTransactionHash) .findFirst() .orElseThrow(); - String openTransactionHash = FORCE_CLOSING_CHANNEL.getChannelPoint().getTransactionHash(); + TransactionHash openTransactionHash = FORCE_CLOSING_CHANNEL.getChannelPoint().getTransactionHash(); when(transactionService.isUnknown(openTransactionHash)).thenReturn(false); when(transactionService.isUnknown(FORCE_CLOSING_CHANNEL.getCloseTransactionHash())).thenReturn(false); @@ -196,7 +197,7 @@ class TransactionBackgroundLoaderTest { true ); when(channelService.getOpenChannels()).thenReturn(Set.of(channel1, channel2, channel3)); - String unknownHash = CHANNEL_POINT_3.getTransactionHash(); + TransactionHash unknownHash = CHANNEL_POINT_3.getTransactionHash(); when(transactionService.isUnknown(any())).thenReturn(false); when(transactionService.isUnknown(unknownHash)).thenReturn(true); diff --git a/grpc-adapter/build.gradle b/grpc-adapter/build.gradle index d23441dc..9c129e80 100644 --- a/grpc-adapter/build.gradle +++ b/grpc-adapter/build.gradle @@ -3,9 +3,10 @@ plugins { } dependencies { - implementation project(':grpc-client') - implementation project(':model') implementation project(':caching') + implementation project(':grpc-client') + implementation project(':hardcoded') + implementation project(':model') testImplementation testFixtures(project(':model')) } 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 88141127..c1b82e5b 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 @@ -10,6 +10,7 @@ import de.cotto.lndmanagej.model.ForceClosingChannel; import de.cotto.lndmanagej.model.LocalOpenChannel; import de.cotto.lndmanagej.model.OpenInitiator; import de.cotto.lndmanagej.model.Pubkey; +import de.cotto.lndmanagej.model.TransactionHash; import de.cotto.lndmanagej.model.WaitingCloseChannel; import lnrpc.Channel; import lnrpc.PendingChannelsResponse; @@ -92,7 +93,7 @@ public class GrpcChannels extends GrpcChannelsBase { new ChannelCoreInformation(id, channelPoint, Coins.ofSatoshis(pendingChannel.getCapacity())), ownPubkey, Pubkey.create(pendingChannel.getRemoteNodePub()), - forceClosedChannel.getClosingTxid(), + TransactionHash.create(forceClosedChannel.getClosingTxid()), getHtlcOutpoints(forceClosedChannel), getOpenInitiator(pendingChannel.getInitiator()) )); diff --git a/grpc-adapter/src/main/java/de/cotto/lndmanagej/grpc/GrpcClosedChannels.java b/grpc-adapter/src/main/java/de/cotto/lndmanagej/grpc/GrpcClosedChannels.java index 2d355a42..ee6d0be4 100644 --- a/grpc-adapter/src/main/java/de/cotto/lndmanagej/grpc/GrpcClosedChannels.java +++ b/grpc-adapter/src/main/java/de/cotto/lndmanagej/grpc/GrpcClosedChannels.java @@ -1,6 +1,8 @@ package de.cotto.lndmanagej.grpc; +import de.cotto.lndmanagej.hardcoded.HardcodedService; import de.cotto.lndmanagej.model.BreachForceClosedChannelBuilder; +import de.cotto.lndmanagej.model.ChannelId; import de.cotto.lndmanagej.model.ChannelIdResolver; import de.cotto.lndmanagej.model.ChannelPoint; import de.cotto.lndmanagej.model.CloseInitiator; @@ -13,6 +15,7 @@ import de.cotto.lndmanagej.model.OpenInitiator; import de.cotto.lndmanagej.model.OpenInitiatorResolver; import de.cotto.lndmanagej.model.Pubkey; import de.cotto.lndmanagej.model.Resolution; +import de.cotto.lndmanagej.model.TransactionHash; import lnrpc.ChannelCloseSummary; import lnrpc.ChannelCloseSummary.ClosureType; import lnrpc.Initiator; @@ -20,6 +23,7 @@ import org.springframework.stereotype.Component; import java.util.Optional; import java.util.Set; +import java.util.stream.Stream; import static java.util.stream.Collectors.toSet; import static lnrpc.ChannelCloseSummary.ClosureType.LOCAL_FORCE_CLOSE; @@ -33,17 +37,20 @@ public class GrpcClosedChannels extends GrpcChannelsBase { private final GrpcService grpcService; private final GrpcGetInfo grpcGetInfo; private final OpenInitiatorResolver openInitiatorResolver; + private final HardcodedService hardcodedService; public GrpcClosedChannels( GrpcService grpcService, GrpcGetInfo grpcGetInfo, ChannelIdResolver channelIdResolver, - OpenInitiatorResolver openInitiatorResolver + OpenInitiatorResolver openInitiatorResolver, + HardcodedService hardcodedService ) { super(channelIdResolver); this.grpcService = grpcService; this.grpcGetInfo = grpcGetInfo; this.openInitiatorResolver = openInitiatorResolver; + this.hardcodedService = hardcodedService; } public Set getClosedChannels() { @@ -86,33 +93,34 @@ public class GrpcClosedChannels extends GrpcChannelsBase { .withCapacity(Coins.ofSatoshis(channelCloseSummary.getCapacity())) .withOwnPubkey(ownPubkey) .withRemotePubkey(Pubkey.create(channelCloseSummary.getRemotePubkey())) - .withCloseTransactionHash(channelCloseSummary.getClosingTxHash()) + .withCloseTransactionHash(TransactionHash.create(channelCloseSummary.getClosingTxHash())) .withOpenInitiator(openInitiator) .withCloseHeight(channelCloseSummary.getCloseHeight()) - .withResolutions(getResolutions(channelCloseSummary)) + .withResolutions(getResolutions(channelId, channelCloseSummary)) .build() ); } - private Set getResolutions(ChannelCloseSummary channelCloseSummary) { - return channelCloseSummary.getResolutionsList().stream() + private Set getResolutions(ChannelId channelId, ChannelCloseSummary channelCloseSummary) { + Stream hardcodedResolutions = hardcodedService.getResolutions(channelId).stream(); + Stream resolutions = channelCloseSummary.getResolutionsList().stream() .map(lndResolution -> { - Optional sweepTransaction; + Optional sweepTransaction; if (lndResolution.getSweepTxid().isBlank()) { sweepTransaction = Optional.empty(); } else { - sweepTransaction = Optional.of(lndResolution.getSweepTxid()); + sweepTransaction = Optional.of(TransactionHash.create(lndResolution.getSweepTxid())); } return new Resolution( sweepTransaction, lndResolution.getResolutionType().name(), lndResolution.getOutcome().name() ); - }) - .collect(toSet()); + }); + return Stream.concat(hardcodedResolutions, resolutions).collect(toSet()); } - private OpenInitiator getOpenInitiator(Initiator initiator, String transactionHash) { + private OpenInitiator getOpenInitiator(Initiator initiator, TransactionHash transactionHash) { OpenInitiator openInitiator = getOpenInitiator(initiator); if (openInitiator.equals(OpenInitiator.UNKNOWN)) { return openInitiatorResolver.resolveFromOpenTransactionHash(transactionHash); diff --git a/grpc-adapter/src/main/java/de/cotto/lndmanagej/grpc/GrpcTransactions.java b/grpc-adapter/src/main/java/de/cotto/lndmanagej/grpc/GrpcTransactions.java index 82bf7914..84b2f49d 100644 --- a/grpc-adapter/src/main/java/de/cotto/lndmanagej/grpc/GrpcTransactions.java +++ b/grpc-adapter/src/main/java/de/cotto/lndmanagej/grpc/GrpcTransactions.java @@ -1,12 +1,14 @@ package de.cotto.lndmanagej.grpc; +import de.cotto.lndmanagej.model.TransactionHash; import lnrpc.Transaction; import org.springframework.stereotype.Component; import java.util.List; import java.util.Optional; import java.util.Set; -import java.util.stream.Collectors; + +import static java.util.stream.Collectors.toSet; @Component public class GrpcTransactions { @@ -16,14 +18,15 @@ public class GrpcTransactions { this.grpcService = grpcService; } - public Optional> getKnownTransactionHashesInBlock(int blockHeight) { + public Optional> getKnownTransactionHashesInBlock(int blockHeight) { List transactionsInBlock = getTransactionsInBlock(blockHeight).orElse(null); if (transactionsInBlock == null) { return Optional.empty(); } - Set hashes = transactionsInBlock.stream() + Set hashes = transactionsInBlock.stream() .map(Transaction::getTxHash) - .collect(Collectors.toSet()); + .map(TransactionHash::create) + .collect(toSet()); return Optional.of(hashes); } 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 5e3708e3..aa3d5d50 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 @@ -178,7 +178,7 @@ class GrpcChannelsTest { private ForceClosedChannel forceClosingChannel(ChannelPoint channelPoint, Initiator initiator) { return ForceClosedChannel.newBuilder() .setChannel(pendingChannel(channelPoint, initiator)) - .setClosingTxid(TRANSACTION_HASH_3) + .setClosingTxid(TRANSACTION_HASH_3.getHash()) .addPendingHtlcs(PendingHTLC.newBuilder().setOutpoint(HTLC_OUTPOINT.toString()).build()) .build(); } diff --git a/grpc-adapter/src/test/java/de/cotto/lndmanagej/grpc/GrpcClosedChannelsTest.java b/grpc-adapter/src/test/java/de/cotto/lndmanagej/grpc/GrpcClosedChannelsTest.java index 1ea2b2dd..b549fc2c 100644 --- a/grpc-adapter/src/test/java/de/cotto/lndmanagej/grpc/GrpcClosedChannelsTest.java +++ b/grpc-adapter/src/test/java/de/cotto/lndmanagej/grpc/GrpcClosedChannelsTest.java @@ -1,5 +1,6 @@ package de.cotto.lndmanagej.grpc; +import de.cotto.lndmanagej.hardcoded.HardcodedService; import de.cotto.lndmanagej.model.ChannelIdResolver; import de.cotto.lndmanagej.model.CloseInitiator; import de.cotto.lndmanagej.model.ClosedChannelFixtures; @@ -7,6 +8,7 @@ import de.cotto.lndmanagej.model.ForceClosedChannelBuilder; import de.cotto.lndmanagej.model.OpenInitiator; import de.cotto.lndmanagej.model.OpenInitiatorResolver; import de.cotto.lndmanagej.model.Resolution; +import de.cotto.lndmanagej.model.TransactionHash; import lnrpc.ChannelCloseSummary; import lnrpc.Initiator; import lnrpc.ResolutionOutcome; @@ -23,6 +25,7 @@ import java.util.Optional; import java.util.Set; 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.ChannelIdFixtures.CHANNEL_ID_2_SHORT; import static de.cotto.lndmanagej.model.ChannelIdFixtures.CHANNEL_ID_SHORT; @@ -73,6 +76,9 @@ class GrpcClosedChannelsTest { @Mock private OpenInitiatorResolver openInitiatorResolver; + @Mock + private HardcodedService hardcodedService; + @BeforeEach void setUp() { when(grpcGetInfo.getPubkey()).thenReturn(PUBKEY); @@ -166,6 +172,17 @@ class GrpcClosedChannelsTest { .containsExactlyInAnyOrder(FORCE_CLOSED_CHANNEL); } + @Test + void getClosedChannels_force_close_with_hardcoded_resolutions() { + when(hardcodedService.getResolutions(CHANNEL_ID)).thenReturn(Set.of(COMMIT_CLAIMED)); + Set resolutions = Set.of(INCOMING_HTLC_CLAIMED); + when(grpcService.getClosedChannels()).thenReturn(List.of( + closedChannel(CHANNEL_ID_SHORT, REMOTE_FORCE_CLOSE, INITIATOR_LOCAL, INITIATOR_REMOTE, resolutions) + )); + assertThat(grpcClosedChannels.getClosedChannels()) + .containsExactlyInAnyOrder(FORCE_CLOSED_CHANNEL); + } + @Test void getClosedChannels_with_zero_channel_id_not_resolved() { when(grpcService.getClosedChannels()).thenReturn(List.of( @@ -226,7 +243,7 @@ class GrpcClosedChannelsTest { .setRemotePubkey(PUBKEY_2.toString()) .setCapacity(CAPACITY.satoshis()) .setChannelPoint(CHANNEL_POINT.toString()) - .setClosingTxHash(TRANSACTION_HASH_2) + .setClosingTxHash(TRANSACTION_HASH_2.getHash()) .setCloseType(closeType) .setOpenInitiator(openInitiator) .setCloseInitiator(closeInitiator) @@ -238,7 +255,7 @@ class GrpcClosedChannelsTest { private void addResolutions(ChannelCloseSummary.Builder builder, Set resolutions) { resolutions.stream().map( resolution -> lnrpc.Resolution.newBuilder() - .setSweepTxid(resolution.sweepTransaction().orElse("")) + .setSweepTxid(resolution.sweepTransaction().map(TransactionHash::getHash).orElse("")) .setOutcome(ResolutionOutcome.valueOf(resolution.outcome())) .setResolutionType(ResolutionType.valueOf(resolution.resolutionType())) .build() diff --git a/grpc-adapter/src/test/java/de/cotto/lndmanagej/grpc/GrpcTransactionsTest.java b/grpc-adapter/src/test/java/de/cotto/lndmanagej/grpc/GrpcTransactionsTest.java index 61e0baa6..a47531dd 100644 --- a/grpc-adapter/src/test/java/de/cotto/lndmanagej/grpc/GrpcTransactionsTest.java +++ b/grpc-adapter/src/test/java/de/cotto/lndmanagej/grpc/GrpcTransactionsTest.java @@ -1,5 +1,6 @@ package de.cotto.lndmanagej.grpc; +import de.cotto.lndmanagej.model.TransactionHash; import lnrpc.Transaction; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; @@ -17,9 +18,12 @@ import static org.mockito.Mockito.when; @ExtendWith(MockitoExtension.class) class GrpcTransactionsTest { private static final int BLOCK_HEIGHT = 123_456; - private static final String HASH = "abc"; - private static final String HASH_2 = "def"; - private static final String HASH_3 = "ghi"; + private static final TransactionHash HASH = + TransactionHash.create("abcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabca"); + private static final TransactionHash HASH_2 = + TransactionHash.create("00000000000cabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabca"); + private static final TransactionHash HASH_3 = + TransactionHash.create("11111111111cabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabca"); private static final Transaction LND_TRANSACTION = transaction(HASH, BLOCK_HEIGHT); private static final Transaction LND_TRANSACTION_2 = transaction(HASH_2, BLOCK_HEIGHT); private static final Transaction LND_TRANSACTION_WRONG_BLOCK = transaction(HASH_3, BLOCK_HEIGHT + 1); @@ -49,7 +53,7 @@ class GrpcTransactionsTest { assertThat(grpcTransactions.getKnownTransactionHashesInBlock(BLOCK_HEIGHT)).contains(Set.of(HASH, HASH_2)); } - private static Transaction transaction(String hash, int blockHeight) { - return Transaction.newBuilder().setTxHash(hash).setBlockHeight(blockHeight).build(); + private static Transaction transaction(TransactionHash hash, int blockHeight) { + return Transaction.newBuilder().setTxHash(hash.getHash()).setBlockHeight(blockHeight).build(); } } \ No newline at end of file diff --git a/hardcoded/build.gradle b/hardcoded/build.gradle new file mode 100644 index 00000000..0b4af5ff --- /dev/null +++ b/hardcoded/build.gradle @@ -0,0 +1,10 @@ +plugins { + id 'lnd-manageJ.java-library-conventions' +} + +dependencies { + implementation project(':model') + implementation project(':caching') + implementation 'org.ini4j:ini4j:0.5.4' + testImplementation testFixtures(project(':model')) +} \ No newline at end of file diff --git a/hardcoded/src/main/java/de/cotto/lndmanagej/hardcoded/HardcodedService.java b/hardcoded/src/main/java/de/cotto/lndmanagej/hardcoded/HardcodedService.java new file mode 100644 index 00000000..4e171c85 --- /dev/null +++ b/hardcoded/src/main/java/de/cotto/lndmanagej/hardcoded/HardcodedService.java @@ -0,0 +1,55 @@ +package de.cotto.lndmanagej.hardcoded; + +import com.google.common.base.Splitter; +import de.cotto.lndmanagej.model.ChannelId; +import de.cotto.lndmanagej.model.Resolution; +import de.cotto.lndmanagej.model.TransactionHash; +import org.springframework.stereotype.Component; + +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.Set; +import java.util.stream.Stream; + +import static java.util.stream.Collectors.toSet; + +@Component +public class HardcodedService { + private static final int EXPECTED_NUMBER_OF_COMPONENTS = 3; + private static final String RESOLUTIONS_SECTION = "resolutions"; + private static final Splitter SPLITTER = Splitter.on(":"); + + private final IniFileReader iniFileReader; + + public HardcodedService(IniFileReader iniFileReader) { + this.iniFileReader = iniFileReader; + } + + public Set getResolutions(ChannelId channelId) { + Map> values = iniFileReader.getValues(RESOLUTIONS_SECTION); + Set forShortChannelId = values.getOrDefault(String.valueOf(channelId.getShortChannelId()), Set.of()); + Set forCompactForm = values.getOrDefault(channelId.getCompactForm(), Set.of()); + Set forCompactFormLnd = values.getOrDefault(channelId.getCompactFormLnd(), Set.of()); + return Stream.of(forShortChannelId, forCompactForm, forCompactFormLnd) + .flatMap(Set::stream) + .map(this::parseResolution) + .flatMap(Optional::stream) + .collect(toSet()); + } + + private Optional parseResolution(String encodedResolution) { + try { + List split = SPLITTER.splitToList(encodedResolution); + if (split.size() != EXPECTED_NUMBER_OF_COMPONENTS) { + return Optional.empty(); + } + String resolutionType = split.get(0); + String outcome = split.get(1); + TransactionHash sweepTransaction = TransactionHash.create(split.get(2)); + return Optional.of(new Resolution(Optional.of(sweepTransaction), resolutionType, outcome)); + } catch (IllegalArgumentException exception) { + return Optional.empty(); + } + } +} diff --git a/hardcoded/src/main/java/de/cotto/lndmanagej/hardcoded/IniFileReader.java b/hardcoded/src/main/java/de/cotto/lndmanagej/hardcoded/IniFileReader.java new file mode 100644 index 00000000..c35b56a6 --- /dev/null +++ b/hardcoded/src/main/java/de/cotto/lndmanagej/hardcoded/IniFileReader.java @@ -0,0 +1,57 @@ +package de.cotto.lndmanagej.hardcoded; + +import com.github.benmanes.caffeine.cache.LoadingCache; +import de.cotto.lndmanagej.caching.CacheBuilder; +import org.ini4j.Ini; +import org.ini4j.Profile; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; + +import java.io.File; +import java.io.IOException; +import java.time.Duration; +import java.util.HashSet; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.Optional; +import java.util.Set; + +@Component +public class IniFileReader { + private final String path; + private final LoadingCache>> cache; + + public IniFileReader(@Value("${lndmanagej.hardcoded-path:}") String path) { + this.path = path; + cache = new CacheBuilder() + .withRefresh(Duration.ofSeconds(5)) + .withExpiry(Duration.ofSeconds(10)) + .build(this::getValuesWithoutCache); + } + + public Map> getValues(String sectionName) { + return cache.get(sectionName); + } + + private Map> getValuesWithoutCache(String sectionName) { + return getIni().map(ini -> ini.get(sectionName)) + .map(this::toMultiValueMap) + .orElse(Map.of()); + } + + private Map> toMultiValueMap(Profile.Section section) { + LinkedHashMap> result = new LinkedHashMap<>(); + for (String key : section.keySet()) { + result.put(key, new HashSet<>(section.getAll(key))); + } + return result; + } + + private Optional getIni() { + try { + return Optional.of(new Ini(new File(path))); + } catch (IOException e) { + return Optional.empty(); + } + } +} diff --git a/hardcoded/src/test/java/de/cotto/lndmanagej/hardcoded/HardcodedServiceTest.java b/hardcoded/src/test/java/de/cotto/lndmanagej/hardcoded/HardcodedServiceTest.java new file mode 100644 index 00000000..23f82f88 --- /dev/null +++ b/hardcoded/src/test/java/de/cotto/lndmanagej/hardcoded/HardcodedServiceTest.java @@ -0,0 +1,77 @@ +package de.cotto.lndmanagej.hardcoded; + +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.Map; +import java.util.Set; + +import static de.cotto.lndmanagej.model.ChannelIdFixtures.CHANNEL_ID; +import static de.cotto.lndmanagej.model.ResolutionFixtures.ANCHOR_CLAIMED; +import static de.cotto.lndmanagej.model.ResolutionFixtures.COMMIT_CLAIMED; +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.when; + +@ExtendWith(MockitoExtension.class) +class HardcodedServiceTest { + private static final String SECTION = "resolutions"; + private static final String COMMIT_CLAIMED_STRING + = "COMMIT:CLAIMED:abc222abc000abc000abc000abc000abc000abc000abc000abc000abc000abc0"; + private static final String ANCHOR_CLAIMED_STRING + = "ANCHOR:CLAIMED:abc222abc000abc000abc000abc000abc000abc000abc000abc000abc000abc0"; + + @InjectMocks + private HardcodedService hardcodedService; + + @Mock + private IniFileReader iniFileReader; + + @Test + void getResolutions_empty() { + when(iniFileReader.getValues(SECTION)).thenReturn(Map.of()); + assertThat(hardcodedService.getResolutions(CHANNEL_ID)).isEmpty(); + } + + @Test + void getResolutions_one_resolution_short_channel_id() { + when(iniFileReader.getValues(SECTION)) + .thenReturn(Map.of(String.valueOf(CHANNEL_ID.getShortChannelId()), Set.of(COMMIT_CLAIMED_STRING))); + assertThat(hardcodedService.getResolutions(CHANNEL_ID)).containsExactly(COMMIT_CLAIMED); + } + + @Test + void getResolutions_one_resolution_compact_form() { + when(iniFileReader.getValues(SECTION)) + .thenReturn(Map.of(CHANNEL_ID.getCompactForm(), Set.of(COMMIT_CLAIMED_STRING))); + assertThat(hardcodedService.getResolutions(CHANNEL_ID)).containsExactly(COMMIT_CLAIMED); + } + + @Test + void getResolutions_one_resolution_compact_form_lnd() { + when(iniFileReader.getValues(SECTION)) + .thenReturn(Map.of(CHANNEL_ID.getCompactFormLnd(), Set.of(COMMIT_CLAIMED_STRING))); + assertThat(hardcodedService.getResolutions(CHANNEL_ID)).containsExactly(COMMIT_CLAIMED); + } + + @Test + void getResolutions_two_resolutions() { + when(iniFileReader.getValues(SECTION)).thenReturn(Map.of( + CHANNEL_ID.getCompactFormLnd(), + Set.of(COMMIT_CLAIMED_STRING, ANCHOR_CLAIMED_STRING) + )); + assertThat(hardcodedService.getResolutions(CHANNEL_ID)) + .containsExactlyInAnyOrder(COMMIT_CLAIMED, ANCHOR_CLAIMED); + } + + @Test + void getResolutions_bogus_string() { + when(iniFileReader.getValues(SECTION)).thenReturn(Map.of( + CHANNEL_ID.getCompactFormLnd(), + Set.of("hello", "hello:peter", "a:b:c") + )); + assertThat(hardcodedService.getResolutions(CHANNEL_ID)).isEmpty(); + } +} \ No newline at end of file diff --git a/hardcoded/src/test/java/de/cotto/lndmanagej/hardcoded/IniFileReaderTest.java b/hardcoded/src/test/java/de/cotto/lndmanagej/hardcoded/IniFileReaderTest.java new file mode 100644 index 00000000..39dd5bd6 --- /dev/null +++ b/hardcoded/src/test/java/de/cotto/lndmanagej/hardcoded/IniFileReaderTest.java @@ -0,0 +1,96 @@ +package de.cotto.lndmanagej.hardcoded; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.junit.jupiter.MockitoExtension; + +import java.io.BufferedWriter; +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.util.Map; +import java.util.Set; + +import static org.assertj.core.api.Assertions.assertThat; + +@ExtendWith(MockitoExtension.class) +class IniFileReaderTest { + + private static final String SECTION = "section"; + private static final String SECTION_2 = "another-section"; + + @Test + void file_does_not_exist() throws IOException { + File file = createTempFile(); + //noinspection ResultOfMethodCallIgnored + file.delete(); + IniFileReader iniFileReader = new IniFileReader(file.getPath()); + assertThat(iniFileReader.getValues(SECTION)).isEmpty(); + } + + @Test + void path_does_not_exist() { + IniFileReader iniFileReader = new IniFileReader("/blabla/this/does/not/exist/foo.conf"); + assertThat(iniFileReader.getValues(SECTION)).isEmpty(); + } + + @Test + void empty_file() throws IOException { + File file = createTempFile(); + IniFileReader iniFileReader = new IniFileReader(file.getPath()); + assertThat(iniFileReader.getValues(SECTION)).isEmpty(); + } + + @Test + void section_without_values() throws IOException { + File file = createTempFile(); + addLineToFile(file, "[" + SECTION + "]"); + IniFileReader iniFileReader = new IniFileReader(file.getPath()); + assertThat(iniFileReader.getValues(SECTION)).isEmpty(); + } + + @Test + void section_with_value() throws IOException { + File file = createTempFile(); + addLineToFile(file, "[" + SECTION + "]", "x=y"); + IniFileReader iniFileReader = new IniFileReader(file.getPath()); + assertThat(iniFileReader.getValues(SECTION)).isEqualTo(Map.of("x", Set.of("y"))); + } + + @Test + void section_with_two_values() throws IOException { + File file = createTempFile(); + addLineToFile(file, "[" + SECTION + "]", "x=y", "a=b"); + IniFileReader iniFileReader = new IniFileReader(file.getPath()); + assertThat(iniFileReader.getValues(SECTION)).isEqualTo(Map.of("x", Set.of("y"), "a", Set.of("b"))); + } + + @Test + void two_sections_with_two_values() throws IOException { + File file = createTempFile(); + addLineToFile(file, "[" + SECTION + "]", "x=y", "a=b", "[" + SECTION_2 + "]", "x=1", "a=2"); + IniFileReader iniFileReader = new IniFileReader(file.getPath()); + assertThat(iniFileReader.getValues(SECTION)).isEqualTo(Map.of("x", Set.of("y"), "a", Set.of("b"))); + assertThat(iniFileReader.getValues(SECTION_2)).isEqualTo(Map.of("x", Set.of("1"), "a", Set.of("2"))); + } + + @Test + void section_with_two_values_for_key() throws IOException { + File file = createTempFile(); + addLineToFile(file, "[" + SECTION + "]", "a=y", "a=b"); + IniFileReader iniFileReader = new IniFileReader(file.getPath()); + assertThat(iniFileReader.getValues(SECTION)).isEqualTo(Map.of("a", Set.of("y", "b"))); + } + + private void addLineToFile(File file, String... lines) throws IOException { + try (BufferedWriter writer = Files.newBufferedWriter(file.toPath())) { + for (String line : lines) { + writer.write(line + "\n"); + } + } + } + + private File createTempFile() throws IOException { + return File.createTempFile("hardcoded", "temp"); + } +} \ No newline at end of file diff --git a/model/src/main/java/de/cotto/lndmanagej/model/BreachForceClosedChannel.java b/model/src/main/java/de/cotto/lndmanagej/model/BreachForceClosedChannel.java index e4da7bc2..c5b12055 100644 --- a/model/src/main/java/de/cotto/lndmanagej/model/BreachForceClosedChannel.java +++ b/model/src/main/java/de/cotto/lndmanagej/model/BreachForceClosedChannel.java @@ -7,7 +7,7 @@ public class BreachForceClosedChannel extends ForceClosedChannel { ChannelCoreInformation channelCoreInformation, Pubkey ownPubkey, Pubkey remotePubkey, - String closeTransactionHash, + TransactionHash closeTransactionHash, OpenInitiator openInitiator, int closeHeight, Set resolutions diff --git a/model/src/main/java/de/cotto/lndmanagej/model/ChannelPoint.java b/model/src/main/java/de/cotto/lndmanagej/model/ChannelPoint.java index dcd58aff..54661b90 100644 --- a/model/src/main/java/de/cotto/lndmanagej/model/ChannelPoint.java +++ b/model/src/main/java/de/cotto/lndmanagej/model/ChannelPoint.java @@ -8,20 +8,20 @@ import java.util.Objects; public final class ChannelPoint { private static final Splitter SPLITTER = Splitter.on(":"); - private final String transactionHash; + private final TransactionHash transactionHash; private final int output; - private ChannelPoint(String transactionHash, Integer output) { + private ChannelPoint(TransactionHash transactionHash, Integer output) { this.transactionHash = transactionHash; this.output = output; } public static ChannelPoint create(String channelPoint) { List split = SPLITTER.splitToList(channelPoint); - return new ChannelPoint(split.get(0), Integer.valueOf(split.get(1))); + return new ChannelPoint(TransactionHash.create(split.get(0)), Integer.valueOf(split.get(1))); } - public String getTransactionHash() { + public TransactionHash getTransactionHash() { return transactionHash; } @@ -48,6 +48,6 @@ public final class ChannelPoint { @Override public String toString() { - return transactionHash + ':' + output; + return transactionHash.toString() + ':' + output; } } 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 1fb95d9b..8e3c34a6 100644 --- a/model/src/main/java/de/cotto/lndmanagej/model/ClosedChannel.java +++ b/model/src/main/java/de/cotto/lndmanagej/model/ClosedChannel.java @@ -12,7 +12,7 @@ public abstract class ClosedChannel extends ClosedOrClosingChannel { ChannelCoreInformation channelCoreInformation, Pubkey ownPubkey, Pubkey remotePubkey, - String closeTransactionHash, + TransactionHash closeTransactionHash, OpenInitiator openInitiator, CloseInitiator closeInitiator, int closeHeight diff --git a/model/src/main/java/de/cotto/lndmanagej/model/ClosedChannelBuilder.java b/model/src/main/java/de/cotto/lndmanagej/model/ClosedChannelBuilder.java index 57bfe0f5..c36aff28 100644 --- a/model/src/main/java/de/cotto/lndmanagej/model/ClosedChannelBuilder.java +++ b/model/src/main/java/de/cotto/lndmanagej/model/ClosedChannelBuilder.java @@ -23,7 +23,7 @@ public abstract class ClosedChannelBuilder { Pubkey remotePubkey; @Nullable - String closeTransactionHash; + TransactionHash closeTransactionHash; @Nullable OpenInitiator openInitiator; @@ -64,7 +64,7 @@ public abstract class ClosedChannelBuilder { return this; } - public ClosedChannelBuilder withCloseTransactionHash(String closeTransactionHash) { + public ClosedChannelBuilder withCloseTransactionHash(TransactionHash closeTransactionHash) { this.closeTransactionHash = closeTransactionHash; return this; } diff --git a/model/src/main/java/de/cotto/lndmanagej/model/ClosedOrClosingChannel.java b/model/src/main/java/de/cotto/lndmanagej/model/ClosedOrClosingChannel.java index 9287140e..fddb9767 100644 --- a/model/src/main/java/de/cotto/lndmanagej/model/ClosedOrClosingChannel.java +++ b/model/src/main/java/de/cotto/lndmanagej/model/ClosedOrClosingChannel.java @@ -3,20 +3,20 @@ package de.cotto.lndmanagej.model; import java.util.Objects; public abstract class ClosedOrClosingChannel extends LocalChannel { - private final String closeTransactionHash; + private final TransactionHash closeTransactionHash; protected ClosedOrClosingChannel( ChannelCoreInformation channelCoreInformation, Pubkey ownPubkey, Pubkey remotePubkey, - String closeTransactionHash, + TransactionHash closeTransactionHash, OpenInitiator openInitiator ) { super(channelCoreInformation, ownPubkey, remotePubkey, openInitiator, false); this.closeTransactionHash = closeTransactionHash; } - public String getCloseTransactionHash() { + public TransactionHash getCloseTransactionHash() { return closeTransactionHash; } diff --git a/model/src/main/java/de/cotto/lndmanagej/model/CoopClosedChannel.java b/model/src/main/java/de/cotto/lndmanagej/model/CoopClosedChannel.java index f496b834..8fad1a8d 100644 --- a/model/src/main/java/de/cotto/lndmanagej/model/CoopClosedChannel.java +++ b/model/src/main/java/de/cotto/lndmanagej/model/CoopClosedChannel.java @@ -5,7 +5,7 @@ public class CoopClosedChannel extends ClosedChannel { ChannelCoreInformation channelCoreInformation, Pubkey ownPubkey, Pubkey remotePubkey, - String closeTransactionHash, + TransactionHash closeTransactionHash, OpenInitiator openInitiator, CloseInitiator closeInitiator, int closeHeight diff --git a/model/src/main/java/de/cotto/lndmanagej/model/ForceClosedChannel.java b/model/src/main/java/de/cotto/lndmanagej/model/ForceClosedChannel.java index be03258b..87ed82ad 100644 --- a/model/src/main/java/de/cotto/lndmanagej/model/ForceClosedChannel.java +++ b/model/src/main/java/de/cotto/lndmanagej/model/ForceClosedChannel.java @@ -10,7 +10,7 @@ public class ForceClosedChannel extends ClosedChannel { ChannelCoreInformation channelCoreInformation, Pubkey ownPubkey, Pubkey remotePubkey, - String closeTransactionHash, + TransactionHash closeTransactionHash, OpenInitiator openInitiator, CloseInitiator closeInitiator, int closeHeight, diff --git a/model/src/main/java/de/cotto/lndmanagej/model/ForceClosingChannel.java b/model/src/main/java/de/cotto/lndmanagej/model/ForceClosingChannel.java index b6aa2d72..19ce9e8f 100644 --- a/model/src/main/java/de/cotto/lndmanagej/model/ForceClosingChannel.java +++ b/model/src/main/java/de/cotto/lndmanagej/model/ForceClosingChannel.java @@ -12,7 +12,7 @@ public final class ForceClosingChannel extends ClosedOrClosingChannel { ChannelCoreInformation channelCoreInformation, Pubkey ownPubkey, Pubkey remotePubkey, - String closeTransactionHash, + TransactionHash closeTransactionHash, Set htlcOutpoints, OpenInitiator openInitiator ) { diff --git a/model/src/main/java/de/cotto/lndmanagej/model/OpenInitiatorResolver.java b/model/src/main/java/de/cotto/lndmanagej/model/OpenInitiatorResolver.java index 23925f7f..3257da68 100644 --- a/model/src/main/java/de/cotto/lndmanagej/model/OpenInitiatorResolver.java +++ b/model/src/main/java/de/cotto/lndmanagej/model/OpenInitiatorResolver.java @@ -1,5 +1,5 @@ package de.cotto.lndmanagej.model; public interface OpenInitiatorResolver { - OpenInitiator resolveFromOpenTransactionHash(String transactionHash); + OpenInitiator resolveFromOpenTransactionHash(TransactionHash transactionHash); } diff --git a/model/src/main/java/de/cotto/lndmanagej/model/Resolution.java b/model/src/main/java/de/cotto/lndmanagej/model/Resolution.java index 0270a440..a67087dc 100644 --- a/model/src/main/java/de/cotto/lndmanagej/model/Resolution.java +++ b/model/src/main/java/de/cotto/lndmanagej/model/Resolution.java @@ -2,7 +2,7 @@ package de.cotto.lndmanagej.model; import java.util.Optional; -public record Resolution(Optional sweepTransaction, String resolutionType, String outcome) { +public record Resolution(Optional sweepTransaction, String resolutionType, String outcome) { private static final String FIRST_STAGE = "FIRST_STAGE"; private static final String TIMEOUT = "TIMEOUT"; diff --git a/model/src/main/java/de/cotto/lndmanagej/model/TransactionHash.java b/model/src/main/java/de/cotto/lndmanagej/model/TransactionHash.java new file mode 100644 index 00000000..51f2e1e1 --- /dev/null +++ b/model/src/main/java/de/cotto/lndmanagej/model/TransactionHash.java @@ -0,0 +1,48 @@ +package de.cotto.lndmanagej.model; + +import java.util.Locale; +import java.util.Objects; +import java.util.regex.Pattern; + +public final class TransactionHash { + private static final Pattern PATTERN = Pattern.compile("[0-9a-fA-F]{64}"); + private final String hash; + + private TransactionHash(String hash) { + this.hash = hash; + } + + public static TransactionHash create(String string) { + if (!PATTERN.matcher(string).matches()) { + throw new IllegalArgumentException("Transaction hash must have 64 hex characters"); + } + return new TransactionHash(string.toLowerCase(Locale.US)); + } + + public String getHash() { + return hash; + } + + @Override + public boolean equals(Object other) { + if (this == other) { + return true; + } + if (other == null || getClass() != other.getClass()) { + return false; + } + TransactionHash that = (TransactionHash) other; + return Objects.equals(hash, that.hash); + } + + @Override + public int hashCode() { + return Objects.hash(hash); + } + + @Override + public String toString() { + return hash; + } + +} diff --git a/model/src/test/java/de/cotto/lndmanagej/model/TransactionHashTest.java b/model/src/test/java/de/cotto/lndmanagej/model/TransactionHashTest.java new file mode 100644 index 00000000..7cae047c --- /dev/null +++ b/model/src/test/java/de/cotto/lndmanagej/model/TransactionHashTest.java @@ -0,0 +1,41 @@ +package de.cotto.lndmanagej.model; + +import nl.jqno.equalsverifier.EqualsVerifier; +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; + +class TransactionHashTest { + + private static final String VALID_HASH = "abc222abc000abc000abc000abc000abc000abc000abc000abc000abc000abc0"; + + @Test + void create_not_hex() { + assertThatIllegalArgumentException().isThrownBy( + () -> TransactionHash.create("abc222abc000abc000abc000abc000abc000abc000abc000abc000abc000abcx") + ); + } + + @Test + void create_wrong_length() { + assertThatIllegalArgumentException().isThrownBy( + () -> TransactionHash.create("abc222abc000abc000abc000abc000abc000abc000abc000abc000abc000abc00") + ); + } + + @Test + void create() { + assertThat(TransactionHash.create(VALID_HASH).getHash()).isEqualTo(VALID_HASH); + } + + @Test + void testToString() { + assertThat(TransactionHash.create(VALID_HASH)).hasToString(VALID_HASH); + } + + @Test + void testEquals() { + EqualsVerifier.forClass(TransactionHash.class).usingGetClass().verify(); + } +} \ No newline at end of file diff --git a/model/src/testFixtures/java/de/cotto/lndmanagej/model/ChannelPointFixtures.java b/model/src/testFixtures/java/de/cotto/lndmanagej/model/ChannelPointFixtures.java index fe3434d3..09cf23d8 100644 --- a/model/src/testFixtures/java/de/cotto/lndmanagej/model/ChannelPointFixtures.java +++ b/model/src/testFixtures/java/de/cotto/lndmanagej/model/ChannelPointFixtures.java @@ -1,9 +1,12 @@ package de.cotto.lndmanagej.model; public class ChannelPointFixtures { - public static final String TRANSACTION_HASH = "abc000abc000abc000abc000abc000abc000abc000abc000abc000abc000abc0"; - public static final String TRANSACTION_HASH_2 = "abc111abc000abc000abc000abc000abc000abc000abc000abc000abc000abc0"; - public static final String TRANSACTION_HASH_3 = "abc222abc000abc000abc000abc000abc000abc000abc000abc000abc000abc0"; + public static final TransactionHash TRANSACTION_HASH = + TransactionHash.create("abc000abc000abc000abc000abc000abc000abc000abc000abc000abc000abc0"); + public static final TransactionHash TRANSACTION_HASH_2 = + TransactionHash.create("abc111abc000abc000abc000abc000abc000abc000abc000abc000abc000abc0"); + public static final TransactionHash TRANSACTION_HASH_3 = + TransactionHash.create("abc222abc000abc000abc000abc000abc000abc000abc000abc000abc000abc0"); public static final int OUTPUT = 1; public static final int OUTPUT_2 = 123; public static final ChannelPoint CHANNEL_POINT = ChannelPoint.create(TRANSACTION_HASH + ":" + OUTPUT); diff --git a/settings.gradle b/settings.gradle index 5dc5f26c..552e72c5 100644 --- a/settings.gradle +++ b/settings.gradle @@ -5,6 +5,7 @@ include 'caching' include 'forwarding-history' include 'grpc-adapter' include 'grpc-client' +include 'hardcoded' include 'invoices' include 'model' include 'payments' diff --git a/transactions/src/main/java/de/cotto/lndmanagej/transactions/TransactionDao.java b/transactions/src/main/java/de/cotto/lndmanagej/transactions/TransactionDao.java index 10975219..e31e359b 100644 --- a/transactions/src/main/java/de/cotto/lndmanagej/transactions/TransactionDao.java +++ b/transactions/src/main/java/de/cotto/lndmanagej/transactions/TransactionDao.java @@ -1,11 +1,12 @@ package de.cotto.lndmanagej.transactions; +import de.cotto.lndmanagej.model.TransactionHash; import de.cotto.lndmanagej.transactions.model.Transaction; import java.util.Optional; public interface TransactionDao { - Optional getTransaction(String transactionHash); + Optional getTransaction(TransactionHash transactionHash); void saveTransaction(Transaction transaction); } diff --git a/transactions/src/main/java/de/cotto/lndmanagej/transactions/download/BitapsTransactionDto.java b/transactions/src/main/java/de/cotto/lndmanagej/transactions/download/BitapsTransactionDto.java index 40c194b2..9bda8886 100644 --- a/transactions/src/main/java/de/cotto/lndmanagej/transactions/download/BitapsTransactionDto.java +++ b/transactions/src/main/java/de/cotto/lndmanagej/transactions/download/BitapsTransactionDto.java @@ -6,12 +6,13 @@ import com.fasterxml.jackson.databind.JsonDeserializer; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; import de.cotto.lndmanagej.model.Coins; +import de.cotto.lndmanagej.model.TransactionHash; import java.io.IOException; @JsonDeserialize(using = BitapsTransactionDto.Deserializer.class) public class BitapsTransactionDto extends TransactionDto { - public BitapsTransactionDto(String hash, int blockHeight, int positionInBlock, Coins fees) { + public BitapsTransactionDto(TransactionHash hash, int blockHeight, int positionInBlock, Coins fees) { super(hash, blockHeight, positionInBlock, fees); } @@ -22,11 +23,11 @@ public class BitapsTransactionDto extends TransactionDto { DeserializationContext deserializationContext ) throws IOException { JsonNode transactionDetailsNode = jsonParser.getCodec().readTree(jsonParser).get("data"); - String hash = transactionDetailsNode.get("txId").textValue(); + TransactionHash hash = TransactionHash.create(transactionDetailsNode.get("txId").textValue()); int blockHeight = transactionDetailsNode.get("blockHeight").asInt(); - long fees = transactionDetailsNode.get("fee").asLong(); + Coins fees = Coins.ofSatoshis(transactionDetailsNode.get("fee").asLong()); int positionInBlock = transactionDetailsNode.get("blockIndex").asInt(); - return new BitapsTransactionDto(hash, blockHeight, positionInBlock, Coins.ofSatoshis(fees)); + return new BitapsTransactionDto(hash, blockHeight, positionInBlock, fees); } } } diff --git a/transactions/src/main/java/de/cotto/lndmanagej/transactions/download/BlockcypherTransactionDto.java b/transactions/src/main/java/de/cotto/lndmanagej/transactions/download/BlockcypherTransactionDto.java index 3a788c09..b032b5a8 100644 --- a/transactions/src/main/java/de/cotto/lndmanagej/transactions/download/BlockcypherTransactionDto.java +++ b/transactions/src/main/java/de/cotto/lndmanagej/transactions/download/BlockcypherTransactionDto.java @@ -6,12 +6,13 @@ import com.fasterxml.jackson.databind.JsonDeserializer; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; import de.cotto.lndmanagej.model.Coins; +import de.cotto.lndmanagej.model.TransactionHash; import java.io.IOException; @JsonDeserialize(using = BlockcypherTransactionDto.Deserializer.class) public final class BlockcypherTransactionDto extends TransactionDto { - public BlockcypherTransactionDto(String hash, int blockHeight, int positionInBlock, Coins fees) { + public BlockcypherTransactionDto(TransactionHash hash, int blockHeight, int positionInBlock, Coins fees) { super(hash, blockHeight, positionInBlock, fees); } @@ -22,11 +23,11 @@ public final class BlockcypherTransactionDto extends TransactionDto { DeserializationContext context ) throws IOException { JsonNode transactionDetailsNode = jsonParser.getCodec().readTree(jsonParser); - String hash = transactionDetailsNode.get("hash").textValue(); + TransactionHash hash = TransactionHash.create(transactionDetailsNode.get("hash").textValue()); int blockHeight = transactionDetailsNode.get("block_height").asInt(); - long fees = transactionDetailsNode.get("fees").asLong(); + Coins fees2 = Coins.ofSatoshis(transactionDetailsNode.get("fees").asLong()); int positionInBlock = transactionDetailsNode.get("block_index").asInt(); - return new BlockcypherTransactionDto(hash, blockHeight, positionInBlock, Coins.ofSatoshis(fees)); + return new BlockcypherTransactionDto(hash, blockHeight, positionInBlock, fees2); } } } diff --git a/transactions/src/main/java/de/cotto/lndmanagej/transactions/download/TransactionDto.java b/transactions/src/main/java/de/cotto/lndmanagej/transactions/download/TransactionDto.java index a38f51ed..3782f9a7 100644 --- a/transactions/src/main/java/de/cotto/lndmanagej/transactions/download/TransactionDto.java +++ b/transactions/src/main/java/de/cotto/lndmanagej/transactions/download/TransactionDto.java @@ -1,16 +1,17 @@ package de.cotto.lndmanagej.transactions.download; import de.cotto.lndmanagej.model.Coins; +import de.cotto.lndmanagej.model.TransactionHash; import de.cotto.lndmanagej.transactions.model.Transaction; public class TransactionDto { - private final String hash; + private final TransactionHash hash; private final int blockHeight; private final int positionInBlock; private final Coins fees; public TransactionDto( - String hash, + TransactionHash hash, int blockHeight, int positionInBlock, Coins fees diff --git a/transactions/src/main/java/de/cotto/lndmanagej/transactions/download/TransactionProvider.java b/transactions/src/main/java/de/cotto/lndmanagej/transactions/download/TransactionProvider.java index c73613c8..8cbca478 100644 --- a/transactions/src/main/java/de/cotto/lndmanagej/transactions/download/TransactionProvider.java +++ b/transactions/src/main/java/de/cotto/lndmanagej/transactions/download/TransactionProvider.java @@ -1,5 +1,6 @@ package de.cotto.lndmanagej.transactions.download; +import de.cotto.lndmanagej.model.TransactionHash; import de.cotto.lndmanagej.transactions.model.Transaction; import feign.FeignException; import io.github.resilience4j.ratelimiter.RequestNotPermitted; @@ -21,11 +22,11 @@ public class TransactionProvider { this.clients = clients; } - public Optional get(String transactionHash) { + public Optional get(TransactionHash transactionHash) { return getAndHandleExceptions(transactionHash).map(TransactionDto::toModel); } - private Optional getAndHandleExceptions(String transactionHash) { + private Optional getAndHandleExceptions(TransactionHash transactionHash) { List randomizedClients = new ArrayList<>(clients); Collections.shuffle(randomizedClients); return randomizedClients.stream() @@ -34,9 +35,12 @@ public class TransactionProvider { .findFirst(); } - private Optional getWithClient(String transactionHash, TransactionDetailsClient client) { + private Optional getWithClient( + TransactionHash transactionHash, + TransactionDetailsClient client + ) { try { - return client.getTransaction(transactionHash); + return client.getTransaction(transactionHash.getHash()); } catch (FeignException feignException) { logger.warn("Feign exception: ", feignException); return Optional.empty(); diff --git a/transactions/src/main/java/de/cotto/lndmanagej/transactions/model/Transaction.java b/transactions/src/main/java/de/cotto/lndmanagej/transactions/model/Transaction.java index 5222bb0a..98a2520d 100644 --- a/transactions/src/main/java/de/cotto/lndmanagej/transactions/model/Transaction.java +++ b/transactions/src/main/java/de/cotto/lndmanagej/transactions/model/Transaction.java @@ -1,9 +1,10 @@ package de.cotto.lndmanagej.transactions.model; import de.cotto.lndmanagej.model.Coins; +import de.cotto.lndmanagej.model.TransactionHash; public record Transaction( - String hash, + TransactionHash hash, int blockHeight, int positionInBlock, Coins fees diff --git a/transactions/src/main/java/de/cotto/lndmanagej/transactions/persistence/TransactionDaoImpl.java b/transactions/src/main/java/de/cotto/lndmanagej/transactions/persistence/TransactionDaoImpl.java index 95fbe6c7..060b3135 100644 --- a/transactions/src/main/java/de/cotto/lndmanagej/transactions/persistence/TransactionDaoImpl.java +++ b/transactions/src/main/java/de/cotto/lndmanagej/transactions/persistence/TransactionDaoImpl.java @@ -1,5 +1,6 @@ package de.cotto.lndmanagej.transactions.persistence; +import de.cotto.lndmanagej.model.TransactionHash; import de.cotto.lndmanagej.transactions.TransactionDao; import de.cotto.lndmanagej.transactions.model.Transaction; import org.springframework.stereotype.Component; @@ -17,8 +18,8 @@ public class TransactionDaoImpl implements TransactionDao { } @Override - public Optional getTransaction(String transactionHash) { - return transactionRepository.findById(transactionHash) + public Optional getTransaction(TransactionHash transactionHash) { + return transactionRepository.findById(transactionHash.getHash()) .flatMap(TransactionJpaDto::toModel); } diff --git a/transactions/src/main/java/de/cotto/lndmanagej/transactions/persistence/TransactionJpaDto.java b/transactions/src/main/java/de/cotto/lndmanagej/transactions/persistence/TransactionJpaDto.java index 6784ffcf..adae3592 100644 --- a/transactions/src/main/java/de/cotto/lndmanagej/transactions/persistence/TransactionJpaDto.java +++ b/transactions/src/main/java/de/cotto/lndmanagej/transactions/persistence/TransactionJpaDto.java @@ -2,6 +2,7 @@ package de.cotto.lndmanagej.transactions.persistence; import com.google.common.annotations.VisibleForTesting; import de.cotto.lndmanagej.model.Coins; +import de.cotto.lndmanagej.model.TransactionHash; import de.cotto.lndmanagej.transactions.model.Transaction; import javax.annotation.CheckForNull; @@ -31,7 +32,7 @@ class TransactionJpaDto { protected static TransactionJpaDto fromModel(Transaction transaction) { TransactionJpaDto dto = new TransactionJpaDto(); - dto.setHash(transaction.hash()); + dto.setHash(transaction.hash().getHash()); dto.setBlockHeight(transaction.blockHeight()); dto.setFees(transaction.fees().satoshis()); dto.setPositionInBlock(transaction.positionInBlock()); @@ -43,7 +44,7 @@ class TransactionJpaDto { return Optional.empty(); } return Optional.of(new Transaction( - hash, + TransactionHash.create(hash), blockHeight, positionInBlock, Coins.ofSatoshis(fees) diff --git a/transactions/src/main/java/de/cotto/lndmanagej/transactions/service/TransactionService.java b/transactions/src/main/java/de/cotto/lndmanagej/transactions/service/TransactionService.java index b59d9b7b..ad4629b2 100644 --- a/transactions/src/main/java/de/cotto/lndmanagej/transactions/service/TransactionService.java +++ b/transactions/src/main/java/de/cotto/lndmanagej/transactions/service/TransactionService.java @@ -3,6 +3,7 @@ package de.cotto.lndmanagej.transactions.service; import com.github.benmanes.caffeine.cache.LoadingCache; import de.cotto.lndmanagej.caching.CacheBuilder; import de.cotto.lndmanagej.grpc.GrpcTransactions; +import de.cotto.lndmanagej.model.TransactionHash; import de.cotto.lndmanagej.transactions.TransactionDao; import de.cotto.lndmanagej.transactions.download.TransactionProvider; import de.cotto.lndmanagej.transactions.model.Transaction; @@ -18,7 +19,7 @@ public class TransactionService { private final TransactionDao transactionDao; private final TransactionProvider transactionProvider; private final GrpcTransactions grpcTransactions; - private final LoadingCache> hashIsKnownCache; + private final LoadingCache> hashIsKnownCache; public TransactionService( TransactionDao transactionDao, @@ -35,18 +36,18 @@ public class TransactionService { } @SuppressWarnings("PMD.LinguisticNaming") - public Optional isKnownByLnd(String transactionHash) { + public Optional isKnownByLnd(TransactionHash transactionHash) { return hashIsKnownCache.get(transactionHash); } @SuppressWarnings("PMD.LinguisticNaming") - public Optional isKnownByLndWithoutCache(String transactionHash) { + public Optional isKnownByLndWithoutCache(TransactionHash transactionHash) { Transaction transaction = getTransaction(transactionHash).orElse(null); if (transaction == null) { return Optional.empty(); } int blockHeight = transaction.blockHeight(); - Set knownTransactionsInBlock = grpcTransactions.getKnownTransactionHashesInBlock(blockHeight) + Set knownTransactionsInBlock = grpcTransactions.getKnownTransactionHashesInBlock(blockHeight) .orElse(null); if (knownTransactionsInBlock == null) { return Optional.empty(); @@ -54,7 +55,7 @@ public class TransactionService { return Optional.of(knownTransactionsInBlock.contains(transactionHash)); } - public Optional getTransaction(String transactionHash) { + public Optional getTransaction(TransactionHash transactionHash) { Optional persistedTransaction = transactionDao.getTransaction(transactionHash); if (persistedTransaction.isPresent()) { return persistedTransaction; @@ -62,15 +63,15 @@ public class TransactionService { return downloadAndPersist(transactionHash); } - public boolean isKnown(String transactionHash) { + public boolean isKnown(TransactionHash transactionHash) { return transactionDao.getTransaction(transactionHash).isPresent(); } - public boolean isUnknown(String transactionHash) { + public boolean isUnknown(TransactionHash transactionHash) { return !isKnown(transactionHash); } - private Optional downloadAndPersist(String transactionHash) { + private Optional downloadAndPersist(TransactionHash transactionHash) { Optional optionalTransaction = transactionProvider.get(transactionHash); optionalTransaction.ifPresent(transactionDao::saveTransaction); return optionalTransaction; diff --git a/transactions/src/test/java/de/cotto/lndmanagej/transactions/download/TransactionProviderTest.java b/transactions/src/test/java/de/cotto/lndmanagej/transactions/download/TransactionProviderTest.java index 661250c0..4fca769f 100644 --- a/transactions/src/test/java/de/cotto/lndmanagej/transactions/download/TransactionProviderTest.java +++ b/transactions/src/test/java/de/cotto/lndmanagej/transactions/download/TransactionProviderTest.java @@ -55,13 +55,14 @@ class TransactionProviderTest { @Test void success_first_client() { - when(blockcypherClient.getTransaction(TRANSACTION_HASH)).thenReturn(Optional.of(BLOCKCYPHER_TRANSACTION)); + when(blockcypherClient.getTransaction(TRANSACTION_HASH.getHash())) + .thenReturn(Optional.of(BLOCKCYPHER_TRANSACTION)); assertThat(transactionProvider.get(TRANSACTION_HASH)).contains(TRANSACTION); } @Test void success_second_client() { - when(bitapsClient.getTransaction(TRANSACTION_HASH)).thenReturn(Optional.of(BITAPS_TRANSACTION)); + when(bitapsClient.getTransaction(TRANSACTION_HASH.getHash())).thenReturn(Optional.of(BITAPS_TRANSACTION)); assertThat(transactionProvider.get(TRANSACTION_HASH)).contains(TRANSACTION); } } \ No newline at end of file diff --git a/transactions/src/test/java/de/cotto/lndmanagej/transactions/persistence/TransactionDaoImplTest.java b/transactions/src/test/java/de/cotto/lndmanagej/transactions/persistence/TransactionDaoImplTest.java index 006e7d1b..fc57e52e 100644 --- a/transactions/src/test/java/de/cotto/lndmanagej/transactions/persistence/TransactionDaoImplTest.java +++ b/transactions/src/test/java/de/cotto/lndmanagej/transactions/persistence/TransactionDaoImplTest.java @@ -35,7 +35,7 @@ class TransactionDaoImplTest { @Test void getTransaction() { - when(transactionRepository.findById(TRANSACTION_HASH)).thenReturn(Optional.of(TRANSACTION_JPA_DTO)); + when(transactionRepository.findById(TRANSACTION_HASH.getHash())).thenReturn(Optional.of(TRANSACTION_JPA_DTO)); assertThat(transactionDao.getTransaction(TRANSACTION_HASH)).contains(TRANSACTION); } @@ -43,7 +43,7 @@ class TransactionDaoImplTest { @Test void saveTransaction() { transactionDao.saveTransaction(TRANSACTION); - verify(transactionRepository).save(argThat(dto -> TRANSACTION_HASH.equals(dto.getHash()))); + verify(transactionRepository).save(argThat(dto -> TRANSACTION_HASH.getHash().equals(dto.getHash()))); verify(transactionRepository).save(argThat(dto -> BLOCK_HEIGHT == dto.getBlockHeight())); verify(transactionRepository).save(argThat(dto -> POSITION_IN_BLOCK == dto.getPositionInBlock())); verify(transactionRepository).save(argThat(dto -> FEES.satoshis() == dto.getFees())); diff --git a/transactions/src/testFixtures/java/de/cotto/lndmanagej/transactions/persistence/TransactionJpaDtoFixtures.java b/transactions/src/testFixtures/java/de/cotto/lndmanagej/transactions/persistence/TransactionJpaDtoFixtures.java index 5d0d52fe..bf51c744 100644 --- a/transactions/src/testFixtures/java/de/cotto/lndmanagej/transactions/persistence/TransactionJpaDtoFixtures.java +++ b/transactions/src/testFixtures/java/de/cotto/lndmanagej/transactions/persistence/TransactionJpaDtoFixtures.java @@ -10,7 +10,7 @@ public class TransactionJpaDtoFixtures { static { TRANSACTION_JPA_DTO = new TransactionJpaDto(); - TRANSACTION_JPA_DTO.setHash(TRANSACTION_HASH); + TRANSACTION_JPA_DTO.setHash(TRANSACTION_HASH.getHash()); TRANSACTION_JPA_DTO.setBlockHeight(BLOCK_HEIGHT); TRANSACTION_JPA_DTO.setPositionInBlock(POSITION_IN_BLOCK); TRANSACTION_JPA_DTO.setFees(FEES.satoshis());