refactor transaction history storage

This commit is contained in:
Craig Raw 2020-06-02 09:20:59 +02:00
parent f88e3d4423
commit a25d020e54
6 changed files with 122 additions and 131 deletions

View file

@ -0,0 +1,22 @@
package com.sparrowwallet.drongo.wallet;
import com.sparrowwallet.drongo.protocol.Sha256Hash;
import com.sparrowwallet.drongo.protocol.Transaction;
public class BlockchainTransaction extends BlockchainTransactionHash implements Comparable<BlockchainTransaction> {
private final Transaction transaction;
public BlockchainTransaction(Sha256Hash hash, int height, Long fee, Transaction transaction) {
super(hash, height, fee);
this.transaction = transaction;
}
public Transaction getTransaction() {
return transaction;
}
@Override
public int compareTo(BlockchainTransaction blockchainTransaction) {
return super.compareTo(blockchainTransaction);
}
}

View file

@ -4,20 +4,22 @@ import com.sparrowwallet.drongo.protocol.Sha256Hash;
import java.util.Objects; import java.util.Objects;
public class BlockchainTransactionHash implements Comparable<BlockchainTransactionHash> { public abstract class BlockchainTransactionHash {
private final Sha256Hash hash; private final Sha256Hash hash;
private final Integer height; private final int height;
private final Long fee; private final Long fee;
private String label;
public BlockchainTransactionHash(Sha256Hash hash) { public BlockchainTransactionHash(Sha256Hash hash) {
this(hash, 0, 0L); this(hash, 0, 0L);
} }
public BlockchainTransactionHash(Sha256Hash hash, Integer height) { public BlockchainTransactionHash(Sha256Hash hash, int height) {
this(hash, height, 0L); this(hash, height, 0L);
} }
public BlockchainTransactionHash(Sha256Hash hash, Integer height, Long fee) { public BlockchainTransactionHash(Sha256Hash hash, int height, Long fee) {
this.hash = hash; this.hash = hash;
this.height = height; this.height = height;
this.fee = fee; this.fee = fee;
@ -31,7 +33,7 @@ public class BlockchainTransactionHash implements Comparable<BlockchainTransacti
return hash.toString(); return hash.toString();
} }
public Integer getHeight() { public int getHeight() {
return height; return height;
} }
@ -39,25 +41,38 @@ public class BlockchainTransactionHash implements Comparable<BlockchainTransacti
return fee; return fee;
} }
public String getLabel() {
return label;
}
public void setLabel(String label) {
this.label = label;
}
@Override
public String toString() {
return hash.toString();
}
@Override @Override
public boolean equals(Object o) { public boolean equals(Object o) {
if (this == o) return true; if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false; if (o == null || getClass() != o.getClass()) return false;
BlockchainTransactionHash that = (BlockchainTransactionHash) o; BlockchainTransactionHash that = (BlockchainTransactionHash) o;
return hash.equals(that.hash); return hash.equals(that.hash) && height == that.height;
} }
@Override @Override
public int hashCode() { public int hashCode() {
return Objects.hash(hash); return Objects.hash(hash, height);
} }
@Override
public int compareTo(BlockchainTransactionHash reference) { public int compareTo(BlockchainTransactionHash reference) {
return height - reference.height; int heightDiff = height - reference.height;
if(heightDiff != 0) {
return heightDiff;
} }
public BlockchainTransactionHash copy() { return hash.compareTo(reference.hash);
return new BlockchainTransactionHash(hash, height, fee);
} }
} }

View file

@ -4,17 +4,19 @@ import com.sparrowwallet.drongo.protocol.Sha256Hash;
import java.util.Objects; import java.util.Objects;
public class BlockchainTransactionHashIndex extends BlockchainTransactionHash { public class BlockchainTransactionHashIndex extends BlockchainTransactionHash implements Comparable<BlockchainTransactionHashIndex> {
private final long index; private final long index;
private final BlockchainTransactionHashIndex spentBy; private final long value;
private BlockchainTransactionHashIndex spentBy;
public BlockchainTransactionHashIndex(Sha256Hash hash, Integer height, Long fee, long index) { public BlockchainTransactionHashIndex(Sha256Hash hash, int height, Long fee, long index, long value) {
this(hash, height, fee, index, null); this(hash, height, fee, index, value, null);
} }
public BlockchainTransactionHashIndex(Sha256Hash hash, Integer height, Long fee, long index, BlockchainTransactionHashIndex spentBy) { public BlockchainTransactionHashIndex(Sha256Hash hash, int height, Long fee, long index, long value, BlockchainTransactionHashIndex spentBy) {
super(hash, height, fee); super(hash, height, fee);
this.index = index; this.index = index;
this.value = value;
this.spentBy = spentBy; this.spentBy = spentBy;
} }
@ -22,6 +24,10 @@ public class BlockchainTransactionHashIndex extends BlockchainTransactionHash {
return index; return index;
} }
public long getValue() {
return value;
}
public boolean isSpent() { public boolean isSpent() {
return spentBy != null; return spentBy != null;
} }
@ -30,18 +36,40 @@ public class BlockchainTransactionHashIndex extends BlockchainTransactionHash {
return spentBy; return spentBy;
} }
public void setSpentBy(BlockchainTransactionHashIndex spentBy) {
this.spentBy = spentBy;
}
@Override
public String toString() {
return getHash().toString() + ":" + index;
}
@Override @Override
public boolean equals(Object o) { public boolean equals(Object o) {
if (this == o) return true; if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false; if (o == null || getClass() != o.getClass()) return false;
if (!super.equals(o)) return false; if (!super.equals(o)) return false;
BlockchainTransactionHashIndex that = (BlockchainTransactionHashIndex) o; BlockchainTransactionHashIndex that = (BlockchainTransactionHashIndex) o;
return index == that.index && return index == that.index;
Objects.equals(spentBy, that.spentBy);
} }
@Override @Override
public int hashCode() { public int hashCode() {
return Objects.hash(super.hashCode(), index, spentBy); return Objects.hash(super.hashCode(), index);
}
@Override
public int compareTo(BlockchainTransactionHashIndex reference) {
int diff = super.compareTo(reference);
if(diff != 0) {
return diff;
}
return (int)(index - reference.index);
}
public BlockchainTransactionHashIndex copy() {
return new BlockchainTransactionHashIndex(super.getHash(), super.getHeight(), super.getFee(), index, value, spentBy.copy());
} }
} }

View file

@ -21,7 +21,7 @@ public class Wallet {
private Policy defaultPolicy; private Policy defaultPolicy;
private List<Keystore> keystores = new ArrayList<>(); private List<Keystore> keystores = new ArrayList<>();
private final Set<WalletNode> purposeNodes = new TreeSet<>(); private final Set<WalletNode> purposeNodes = new TreeSet<>();
private final Map<Sha256Hash, Transaction> transactions = new HashMap<>(); private final Map<Sha256Hash, BlockchainTransaction> transactions = new HashMap<>();
public Wallet() { public Wallet() {
} }
@ -82,7 +82,7 @@ public class Wallet {
return purposeNodes; return purposeNodes;
} }
public Map<Sha256Hash, Transaction> getTransactions() { public Map<Sha256Hash, BlockchainTransaction> getTransactions() {
return transactions; return transactions;
} }
@ -193,66 +193,12 @@ public class Wallet {
public void clearHistory() { public void clearHistory() {
for(WalletNode purposeNode : purposeNodes) { for(WalletNode purposeNode : purposeNodes) {
purposeNode.getHistory().clear(); purposeNode.clearHistory();
for(WalletNode addressNode : purposeNode.getChildren()) {
addressNode.getHistory().clear();
}
} }
transactions.clear(); transactions.clear();
} }
public WalletNodeHistory getNodeHistory(WalletNode node) {
Set<BlockchainTransactionHashIndex> receivedTXOs = new TreeSet<>();
Set<BlockchainTransactionHashIndex> spentTXOs = new TreeSet<>();
Set<BlockchainTransactionHashIndex> 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<BlockchainTransactionHash> 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() { public boolean isValid() {
if(policyType == null || scriptType == null || defaultPolicy == null || keystores.isEmpty()) { if(policyType == null || scriptType == null || defaultPolicy == null || keystores.isEmpty()) {
return false; return false;

View file

@ -4,17 +4,14 @@ import com.sparrowwallet.drongo.KeyDerivation;
import com.sparrowwallet.drongo.KeyPurpose; import com.sparrowwallet.drongo.KeyPurpose;
import com.sparrowwallet.drongo.crypto.ChildNumber; import com.sparrowwallet.drongo.crypto.ChildNumber;
import java.util.List; import java.util.*;
import java.util.Objects; import java.util.stream.Collectors;
import java.util.Set;
import java.util.TreeSet;
public class WalletNode implements Comparable<WalletNode> { public class WalletNode implements Comparable<WalletNode> {
private final String derivationPath; private final String derivationPath;
private String label; private String label;
private Long amount;
private Set<WalletNode> children = new TreeSet<>(); private Set<WalletNode> children = new TreeSet<>();
private Set<BlockchainTransactionHash> history = new TreeSet<>(); private Set<BlockchainTransactionHashIndex> transactionOutputs = new TreeSet<>();
private transient KeyPurpose keyPurpose; private transient KeyPurpose keyPurpose;
private transient int index = -1; private transient int index = -1;
@ -81,12 +78,12 @@ public class WalletNode implements Comparable<WalletNode> {
this.label = label; this.label = label;
} }
public Long getAmount() { public Long getValue() {
return amount; if(transactionOutputs == null) {
return null;
} }
public void setAmount(Long amount) { return getUnspentTransactionOutputs().stream().mapToLong(BlockchainTransactionHashIndex::getValue).sum();
this.amount = amount;
} }
public Set<WalletNode> getChildren() { public Set<WalletNode> getChildren() {
@ -97,12 +94,17 @@ public class WalletNode implements Comparable<WalletNode> {
this.children = children; this.children = children;
} }
public Set<BlockchainTransactionHash> getHistory() { public Set<BlockchainTransactionHashIndex> getTransactionOutputs() {
return history; return transactionOutputs;
} }
public void setHistory(Set<BlockchainTransactionHash> history) { public void setTransactionOutputs(Set<BlockchainTransactionHashIndex> transactionOutputs) {
this.history = history; this.transactionOutputs = transactionOutputs;
}
public Set<BlockchainTransactionHashIndex> getUnspentTransactionOutputs() {
Set<BlockchainTransactionHashIndex> unspentTXOs = new TreeSet<>(transactionOutputs);
return unspentTXOs.stream().filter(txo -> !txo.isSpent()).collect(Collectors.toCollection(HashSet::new));
} }
public void fillToIndex(int index) { public void fillToIndex(int index) {
@ -115,7 +117,7 @@ public class WalletNode implements Comparable<WalletNode> {
public Integer getHighestUsedIndex() { public Integer getHighestUsedIndex() {
WalletNode highestNode = null; WalletNode highestNode = null;
for(WalletNode childNode : getChildren()) { for(WalletNode childNode : getChildren()) {
if(!childNode.getHistory().isEmpty()) { if(!childNode.getTransactionOutputs().isEmpty()) {
highestNode = childNode; highestNode = childNode;
} }
} }
@ -123,6 +125,11 @@ public class WalletNode implements Comparable<WalletNode> {
return highestNode == null ? null : highestNode.index; return highestNode == null ? null : highestNode.index;
} }
@Override
public String toString() {
return derivationPath;
}
@Override @Override
public boolean equals(Object o) { public boolean equals(Object o) {
if (this == o) return true; if (this == o) return true;
@ -141,15 +148,23 @@ public class WalletNode implements Comparable<WalletNode> {
return getIndex() - node.getIndex(); return getIndex() - node.getIndex();
} }
public void clearHistory() {
transactionOutputs.clear();
for(WalletNode childNode : getChildren()) {
childNode.clearHistory();
}
}
public WalletNode copy() { public WalletNode copy() {
WalletNode copy = new WalletNode(derivationPath); WalletNode copy = new WalletNode(derivationPath);
copy.setLabel(label); copy.setLabel(label);
copy.setAmount(amount);
for(WalletNode child : getChildren()) { for(WalletNode child : getChildren()) {
copy.getChildren().add(child.copy()); copy.getChildren().add(child.copy());
} }
for(BlockchainTransactionHash reference : getHistory()) {
copy.getHistory().add(reference.copy()); for(BlockchainTransactionHashIndex txo : getTransactionOutputs()) {
copy.getTransactionOutputs().add(txo.copy());
} }
return copy; return copy;

View file

@ -1,35 +0,0 @@
package com.sparrowwallet.drongo.wallet;
import java.util.Set;
import java.util.TreeSet;
public class WalletNodeHistory {
private final Set<BlockchainTransactionHashIndex> receivedTXOs;
private final Set<BlockchainTransactionHashIndex> spentTXOs;
private final Set<BlockchainTransactionHashIndex> spendingTXIs;
public WalletNodeHistory(Set<BlockchainTransactionHashIndex> receivedTXOs, Set<BlockchainTransactionHashIndex> spentTXOs, Set<BlockchainTransactionHashIndex> spendingTXIs) {
this.receivedTXOs = receivedTXOs;
this.spentTXOs = spentTXOs;
this.spendingTXIs = spendingTXIs;
}
public Set<BlockchainTransactionHashIndex> getReceivedTXOs() {
return receivedTXOs;
}
public Set<BlockchainTransactionHashIndex> getSpentTXOs() {
return spentTXOs;
}
public Set<BlockchainTransactionHashIndex> getSpendingTXIs() {
return spendingTXIs;
}
public Set<BlockchainTransactionHashIndex> getUnspentTXOs() {
Set<BlockchainTransactionHashIndex> unspentTXOs = new TreeSet<>(receivedTXOs);
unspentTXOs.removeAll(spentTXOs);
return unspentTXOs;
}
}