diff --git a/src/main/java/com/sparrowwallet/drongo/wallet/BlockchainTransactionHash.java b/src/main/java/com/sparrowwallet/drongo/wallet/BlockchainTransactionHash.java new file mode 100644 index 0000000..70fca0b --- /dev/null +++ b/src/main/java/com/sparrowwallet/drongo/wallet/BlockchainTransactionHash.java @@ -0,0 +1,63 @@ +package com.sparrowwallet.drongo.wallet; + +import com.sparrowwallet.drongo.protocol.Sha256Hash; + +import java.util.Objects; + +public class BlockchainTransactionHash implements Comparable { + private final Sha256Hash hash; + private final Integer height; + private final Long fee; + + public BlockchainTransactionHash(Sha256Hash hash) { + this(hash, 0, 0L); + } + + public BlockchainTransactionHash(Sha256Hash hash, Integer height) { + this(hash, height, 0L); + } + + public BlockchainTransactionHash(Sha256Hash hash, Integer height, Long fee) { + this.hash = hash; + this.height = height; + this.fee = fee; + } + + public Sha256Hash getHash() { + return hash; + } + + public String getHashAsString() { + return hash.toString(); + } + + public Integer getHeight() { + return height; + } + + public Long getFee() { + return fee; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + BlockchainTransactionHash that = (BlockchainTransactionHash) o; + return hash.equals(that.hash); + } + + @Override + public int hashCode() { + return Objects.hash(hash); + } + + @Override + public int compareTo(BlockchainTransactionHash reference) { + return height - reference.height; + } + + public BlockchainTransactionHash copy() { + return new BlockchainTransactionHash(hash, height, fee); + } +} diff --git a/src/main/java/com/sparrowwallet/drongo/wallet/BlockchainTransactionHashIndex.java b/src/main/java/com/sparrowwallet/drongo/wallet/BlockchainTransactionHashIndex.java new file mode 100644 index 0000000..e714963 --- /dev/null +++ b/src/main/java/com/sparrowwallet/drongo/wallet/BlockchainTransactionHashIndex.java @@ -0,0 +1,47 @@ +package com.sparrowwallet.drongo.wallet; + +import com.sparrowwallet.drongo.protocol.Sha256Hash; + +import java.util.Objects; + +public class BlockchainTransactionHashIndex extends BlockchainTransactionHash { + private final long index; + private final BlockchainTransactionHashIndex spentBy; + + public BlockchainTransactionHashIndex(Sha256Hash hash, Integer height, Long fee, long index) { + this(hash, height, fee, index, null); + } + + public BlockchainTransactionHashIndex(Sha256Hash hash, Integer height, Long fee, long index, BlockchainTransactionHashIndex spentBy) { + super(hash, height, fee); + this.index = index; + this.spentBy = spentBy; + } + + public long getIndex() { + return index; + } + + public boolean isSpent() { + return spentBy != null; + } + + public BlockchainTransactionHashIndex getSpentBy() { + return spentBy; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + if (!super.equals(o)) return false; + BlockchainTransactionHashIndex that = (BlockchainTransactionHashIndex) o; + return index == that.index && + Objects.equals(spentBy, that.spentBy); + } + + @Override + public int hashCode() { + return Objects.hash(super.hashCode(), index, spentBy); + } +} diff --git a/src/main/java/com/sparrowwallet/drongo/wallet/TransactionReference.java b/src/main/java/com/sparrowwallet/drongo/wallet/TransactionReference.java deleted file mode 100644 index 1b2cf15..0000000 --- a/src/main/java/com/sparrowwallet/drongo/wallet/TransactionReference.java +++ /dev/null @@ -1,57 +0,0 @@ -package com.sparrowwallet.drongo.wallet; - -import java.util.Objects; - -public class TransactionReference implements Comparable { - private final String transactionId; - private final Integer height; - private final Long fee; - - public TransactionReference(String transactionId) { - this(transactionId, 0, 0L); - } - - public TransactionReference(String transactionId, Integer height) { - this(transactionId, height, 0L); - } - - public TransactionReference(String transactionId, Integer height, Long fee) { - this.transactionId = transactionId; - this.height = height; - this.fee = fee; - } - - public String getTransactionId() { - return transactionId; - } - - public Integer getHeight() { - return height; - } - - public Long getFee() { - return fee; - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - TransactionReference that = (TransactionReference) o; - return transactionId.equals(that.transactionId); - } - - @Override - public int hashCode() { - return Objects.hash(transactionId); - } - - @Override - public int compareTo(TransactionReference reference) { - return height - reference.height; - } - - public TransactionReference copy() { - return new TransactionReference(transactionId, height, fee); - } -} diff --git a/src/main/java/com/sparrowwallet/drongo/wallet/Wallet.java b/src/main/java/com/sparrowwallet/drongo/wallet/Wallet.java index b293edf..31ae176 100644 --- a/src/main/java/com/sparrowwallet/drongo/wallet/Wallet.java +++ b/src/main/java/com/sparrowwallet/drongo/wallet/Wallet.java @@ -7,9 +7,7 @@ import com.sparrowwallet.drongo.crypto.ECKey; import com.sparrowwallet.drongo.crypto.Key; import com.sparrowwallet.drongo.policy.Policy; import com.sparrowwallet.drongo.policy.PolicyType; -import com.sparrowwallet.drongo.protocol.Script; -import com.sparrowwallet.drongo.protocol.ScriptType; -import com.sparrowwallet.drongo.protocol.Transaction; +import com.sparrowwallet.drongo.protocol.*; import java.util.*; import java.util.stream.Collectors; @@ -23,7 +21,7 @@ public class Wallet { private Policy defaultPolicy; private List keystores = new ArrayList<>(); private final Set purposeNodes = new TreeSet<>(); - private final Map transactions = new HashMap<>(); + private final Map transactions = new HashMap<>(); public Wallet() { } @@ -84,7 +82,7 @@ public class Wallet { return purposeNodes; } - public Map getTransactions() { + public Map getTransactions() { return transactions; } @@ -204,6 +202,57 @@ public class Wallet { transactions.clear(); } + public WalletNodeHistory getNodeHistory(WalletNode node) { + Set receivedTXOs = new TreeSet<>(); + Set spentTXOs = new TreeSet<>(); + Set spendingTXIs = new TreeSet<>(); + + Script nodeScript = getOutputScript(node); + for(BlockchainTransactionHash reference : node.getHistory()) { + Transaction transaction = transactions.get(reference.getHash()); + if(transaction == null) { + throw new IllegalStateException("Could not retrieve transaction for hash " + reference.getHashAsString()); + } + + for(int inputIndex = 0; inputIndex < transaction.getInputs().size(); inputIndex++) { + TransactionInput input = transaction.getInputs().get(inputIndex); + Sha256Hash previousHash = input.getOutpoint().getHash(); + Transaction previousTransaction = transactions.get(previousHash); + if(previousTransaction == null) { + //No referenced transaction found, cannot check if spends from wallet + //This is fine so long as all referenced transactions have been returned, in which case this refers to a transaction that does not affect this wallet + continue; + } + + Optional optionalTxHash = node.getHistory().stream().filter(txHash -> txHash.getHash().equals(previousHash)).findFirst(); + if(optionalTxHash.isEmpty()) { + //No previous transaction history found, cannot check if spends from wallet + //This is fine so long as all referenced transactions have been returned, in which case this refers to a transaction that does not affect this wallet node + continue; + } + + BlockchainTransactionHash spentTxHash = optionalTxHash.get(); + TransactionOutput spentOutput = previousTransaction.getOutputs().get((int)input.getOutpoint().getIndex()); + if(spentOutput.getScript().equals(nodeScript)) { + BlockchainTransactionHashIndex spendingTXI = new BlockchainTransactionHashIndex(reference.getHash(), reference.getHeight(), reference.getFee(), inputIndex); + spendingTXIs.add(spendingTXI); + BlockchainTransactionHashIndex spentTXO = new BlockchainTransactionHashIndex(spentTxHash.getHash(), spentTxHash.getHeight(), spentTxHash.getFee(), input.getOutpoint().getIndex(), spendingTXI); + spentTXOs.add(spentTXO); + } + } + + for(int outputIndex = 0; outputIndex < transaction.getOutputs().size(); outputIndex++) { + TransactionOutput output = transaction.getOutputs().get(outputIndex); + if(output.getScript().equals(nodeScript)) { + BlockchainTransactionHashIndex receivingTXO = new BlockchainTransactionHashIndex(reference.getHash(), reference.getHeight(), reference.getFee(), outputIndex); + receivedTXOs.add(receivingTXO); + } + } + } + + return new WalletNodeHistory(receivedTXOs, spentTXOs, spendingTXIs); + } + public boolean isValid() { if(policyType == null || scriptType == null || defaultPolicy == null || keystores.isEmpty()) { return false; @@ -302,8 +351,8 @@ public class Wallet { for(WalletNode node : purposeNodes) { copy.getPurposeNodes().add(node.copy()); } - for(String transactionId : transactions.keySet()) { - copy.getTransactions().put(transactionId, transactions.get(transactionId)); + for(Sha256Hash hash : transactions.keySet()) { + copy.getTransactions().put(hash, transactions.get(hash)); } return copy; diff --git a/src/main/java/com/sparrowwallet/drongo/wallet/WalletNode.java b/src/main/java/com/sparrowwallet/drongo/wallet/WalletNode.java index 40890a7..0e06aff 100644 --- a/src/main/java/com/sparrowwallet/drongo/wallet/WalletNode.java +++ b/src/main/java/com/sparrowwallet/drongo/wallet/WalletNode.java @@ -14,7 +14,7 @@ public class WalletNode implements Comparable { private String label; private Long amount; private Set children = new TreeSet<>(); - private Set history = new TreeSet<>(); + private Set history = new TreeSet<>(); private transient KeyPurpose keyPurpose; private transient int index = -1; @@ -97,11 +97,11 @@ public class WalletNode implements Comparable { this.children = children; } - public Set getHistory() { + public Set getHistory() { return history; } - public void setHistory(Set history) { + public void setHistory(Set history) { this.history = history; } @@ -148,7 +148,7 @@ public class WalletNode implements Comparable { for(WalletNode child : getChildren()) { copy.getChildren().add(child.copy()); } - for(TransactionReference reference : getHistory()) { + for(BlockchainTransactionHash reference : getHistory()) { copy.getHistory().add(reference.copy()); } diff --git a/src/main/java/com/sparrowwallet/drongo/wallet/WalletNodeHistory.java b/src/main/java/com/sparrowwallet/drongo/wallet/WalletNodeHistory.java new file mode 100644 index 0000000..5585574 --- /dev/null +++ b/src/main/java/com/sparrowwallet/drongo/wallet/WalletNodeHistory.java @@ -0,0 +1,35 @@ +package com.sparrowwallet.drongo.wallet; + +import java.util.Set; +import java.util.TreeSet; + +public class WalletNodeHistory { + private final Set receivedTXOs; + private final Set spentTXOs; + private final Set spendingTXIs; + + public WalletNodeHistory(Set receivedTXOs, Set spentTXOs, Set spendingTXIs) { + this.receivedTXOs = receivedTXOs; + this.spentTXOs = spentTXOs; + this.spendingTXIs = spendingTXIs; + } + + public Set getReceivedTXOs() { + return receivedTXOs; + } + + public Set getSpentTXOs() { + return spentTXOs; + } + + public Set getSpendingTXIs() { + return spendingTXIs; + } + + public Set getUnspentTXOs() { + Set unspentTXOs = new TreeSet<>(receivedTXOs); + unspentTXOs.removeAll(spentTXOs); + + return unspentTXOs; + } +}