From 427a415fbfed2e58d11600be2fde7254ea15bec2 Mon Sep 17 00:00:00 2001 From: Istvan Szabo Date: Tue, 25 May 2021 23:57:54 +0100 Subject: [PATCH] Adjusted bitcoiny to convert transaction info into the new DTO --- .../resource/CrossChainBitcoinResource.java | 6 +-- .../resource/CrossChainLitecoinResource.java | 6 +-- .../java/org/qortal/crosschain/Bitcoiny.java | 52 +++++++++++++++---- .../apps/GetWalletTransactions.java | 10 ++-- 4 files changed, 52 insertions(+), 22 deletions(-) diff --git a/src/main/java/org/qortal/api/resource/CrossChainBitcoinResource.java b/src/main/java/org/qortal/api/resource/CrossChainBitcoinResource.java index 445d853e..2c1c6991 100644 --- a/src/main/java/org/qortal/api/resource/CrossChainBitcoinResource.java +++ b/src/main/java/org/qortal/api/resource/CrossChainBitcoinResource.java @@ -23,8 +23,8 @@ import org.qortal.api.ApiExceptionFactory; import org.qortal.api.Security; import org.qortal.api.model.crosschain.BitcoinSendRequest; import org.qortal.crosschain.Bitcoin; -import org.qortal.crosschain.BitcoinyTransaction; import org.qortal.crosschain.ForeignBlockchainException; +import org.qortal.crosschain.SimpleTransaction; @Path("/crosschain/btc") @Tag(name = "Cross-Chain (Bitcoin)") @@ -89,12 +89,12 @@ public class CrossChainBitcoinResource { ), responses = { @ApiResponse( - content = @Content(array = @ArraySchema( schema = @Schema( implementation = BitcoinyTransaction.class ) ) ) + content = @Content(array = @ArraySchema( schema = @Schema( implementation = SimpleTransaction.class ) ) ) ) } ) @ApiErrors({ApiError.INVALID_PRIVATE_KEY, ApiError.FOREIGN_BLOCKCHAIN_NETWORK_ISSUE}) - public List getBitcoinWalletTransactions(String key58) { + public List getBitcoinWalletTransactions(String key58) { Security.checkApiCallAllowed(request); Bitcoin bitcoin = Bitcoin.getInstance(); diff --git a/src/main/java/org/qortal/api/resource/CrossChainLitecoinResource.java b/src/main/java/org/qortal/api/resource/CrossChainLitecoinResource.java index 9c841045..8883f964 100644 --- a/src/main/java/org/qortal/api/resource/CrossChainLitecoinResource.java +++ b/src/main/java/org/qortal/api/resource/CrossChainLitecoinResource.java @@ -22,9 +22,9 @@ import org.qortal.api.ApiErrors; import org.qortal.api.ApiExceptionFactory; import org.qortal.api.Security; import org.qortal.api.model.crosschain.LitecoinSendRequest; -import org.qortal.crosschain.BitcoinyTransaction; import org.qortal.crosschain.ForeignBlockchainException; import org.qortal.crosschain.Litecoin; +import org.qortal.crosschain.SimpleTransaction; @Path("/crosschain/ltc") @Tag(name = "Cross-Chain (Litecoin)") @@ -89,12 +89,12 @@ public class CrossChainLitecoinResource { ), responses = { @ApiResponse( - content = @Content(array = @ArraySchema( schema = @Schema( implementation = BitcoinyTransaction.class ) ) ) + content = @Content(array = @ArraySchema( schema = @Schema( implementation = SimpleTransaction.class ) ) ) ) } ) @ApiErrors({ApiError.INVALID_PRIVATE_KEY, ApiError.FOREIGN_BLOCKCHAIN_NETWORK_ISSUE}) - public List getLitecoinWalletTransactions(String key58) { + public List getLitecoinWalletTransactions(String key58) { Security.checkApiCallAllowed(request); Litecoin litecoin = Litecoin.getInstance(); diff --git a/src/main/java/org/qortal/crosschain/Bitcoiny.java b/src/main/java/org/qortal/crosschain/Bitcoiny.java index c3fecb4d..fc98f959 100644 --- a/src/main/java/org/qortal/crosschain/Bitcoiny.java +++ b/src/main/java/org/qortal/crosschain/Bitcoiny.java @@ -91,7 +91,7 @@ public abstract class Bitcoiny implements ForeignBlockchain { return this.params; } - // Interface obligations + // Interface obligations @Override public boolean isValidAddress(String address) { @@ -171,7 +171,7 @@ public abstract class Bitcoiny implements ForeignBlockchain { /** * Returns fixed P2SH spending fee, in sats per 1000bytes, optionally for historic timestamp. - * + * * @param timestamp optional milliseconds since epoch, or null for 'now' * @return sats per 1000bytes * @throws ForeignBlockchainException if something went wrong @@ -271,7 +271,7 @@ public abstract class Bitcoiny implements ForeignBlockchain { /** * Returns bitcoinj transaction sending amount to recipient. - * + * * @param xprv58 BIP32 private key * @param recipient P2PKH address * @param amount unscaled amount @@ -303,7 +303,7 @@ public abstract class Bitcoiny implements ForeignBlockchain { /** * Returns bitcoinj transaction sending amount to recipient using default fees. - * + * * @param xprv58 BIP32 private key * @param recipient P2PKH address * @param amount unscaled amount @@ -332,7 +332,7 @@ public abstract class Bitcoiny implements ForeignBlockchain { return balance.value; } - public List getWalletTransactions(String key58) throws ForeignBlockchainException { + public List getWalletTransactions(String key58) throws ForeignBlockchainException { Context.propagate(bitcoinjContext); Wallet wallet = walletFromDeterministicKey58(key58); @@ -344,6 +344,7 @@ public abstract class Bitcoiny implements ForeignBlockchain { List keys = new ArrayList<>(keyChain.getLeafKeys()); Set walletTransactions = new HashSet<>(); + Set keySet = new HashSet<>(); int ki = 0; do { @@ -354,6 +355,7 @@ public abstract class Bitcoiny implements ForeignBlockchain { // Check for transactions Address address = Address.fromKey(this.params, dKey, ScriptType.P2PKH); + keySet.add(address.toString()); byte[] script = ScriptBuilder.createOutputScript(address).getProgram(); // Ask for transaction history - if it's empty then key has never been used @@ -377,9 +379,41 @@ public abstract class Bitcoiny implements ForeignBlockchain { // Process new keys } while (true); - Comparator newestTimestampFirstComparator = Comparator.comparingInt((BitcoinyTransaction txn) -> txn.timestamp).reversed(); + Comparator newestTimestampFirstComparator = Comparator.comparingInt(SimpleTransaction::getTimestamp).reversed(); - return walletTransactions.stream().sorted(newestTimestampFirstComparator).collect(Collectors.toList()); + return walletTransactions.stream().map(t -> convertToSimpleTransaction(t, keySet)).sorted(newestTimestampFirstComparator).collect(Collectors.toList()); + } + + protected SimpleTransaction convertToSimpleTransaction(BitcoinyTransaction t, Set keySet) { + long amount = 0; + long total = 0L; + for (BitcoinyTransaction.Input input : t.inputs) { + try { + BitcoinyTransaction t2 = getTransaction(input.outputTxHash); + List senders = t2.outputs.get(input.outputVout).addresses; + for (String sender : senders) { + if (keySet.contains(sender)) { + total += t2.outputs.get(input.outputVout).value; + } + } + } catch (ForeignBlockchainException e) { + LOGGER.trace("Failed to retrieve transaction information {}", input.outputTxHash); + } + } + if (t.outputs != null && !t.outputs.isEmpty()) { + for (BitcoinyTransaction.Output output : t.outputs) { + for (String address : output.addresses) { + if (keySet.contains(address)) { + if (total > 0L) { + amount -= (total - output.value); + } else { + amount += output.value; + } + } + } + } + } + return new SimpleTransaction(t.txHash, t.timestamp, amount); } /** @@ -421,7 +455,7 @@ public abstract class Bitcoiny implements ForeignBlockchain { * If there are no unspent outputs then either: * a) all the outputs have been spent * b) address has never been used - * + * * For case (a) we want to remember not to check this address (key) again. */ @@ -501,7 +535,7 @@ public abstract class Bitcoiny implements ForeignBlockchain { * If there are no unspent outputs then either: * a) all the outputs have been spent * b) address has never been used - * + * * For case (a) we want to remember not to check this address (key) again. */ diff --git a/src/test/java/org/qortal/test/crosschain/apps/GetWalletTransactions.java b/src/test/java/org/qortal/test/crosschain/apps/GetWalletTransactions.java index f44fc0a6..7a880b1a 100644 --- a/src/test/java/org/qortal/test/crosschain/apps/GetWalletTransactions.java +++ b/src/test/java/org/qortal/test/crosschain/apps/GetWalletTransactions.java @@ -8,11 +8,7 @@ import java.util.stream.Collectors; import org.bitcoinj.core.AddressFormatException; import org.bouncycastle.jce.provider.BouncyCastleProvider; import org.bouncycastle.jsse.provider.BouncyCastleJsseProvider; -import org.qortal.crosschain.Bitcoin; -import org.qortal.crosschain.Bitcoiny; -import org.qortal.crosschain.BitcoinyTransaction; -import org.qortal.crosschain.ForeignBlockchainException; -import org.qortal.crosschain.Litecoin; +import org.qortal.crosschain.*; import org.qortal.settings.Settings; public class GetWalletTransactions { @@ -69,7 +65,7 @@ public class GetWalletTransactions { System.out.println(String.format("Using %s", bitcoiny.getBlockchainProvider().getNetId())); // Grab all outputs from transaction - List transactions = null; + List transactions = null; try { transactions = bitcoiny.getWalletTransactions(key58); } catch (ForeignBlockchainException e) { @@ -79,7 +75,7 @@ public class GetWalletTransactions { System.out.println(String.format("Found %d transaction%s", transactions.size(), (transactions.size() != 1 ? "s" : ""))); - for (BitcoinyTransaction transaction : transactions.stream().sorted(Comparator.comparingInt(t -> t.timestamp)).collect(Collectors.toList())) + for (SimpleTransaction transaction : transactions.stream().sorted(Comparator.comparingInt(SimpleTransaction::getTimestamp)).collect(Collectors.toList())) System.out.println(String.format("%s", transaction)); }