mirror of
https://github.com/sparrowwallet/drongo.git
synced 2024-12-27 02:26:44 +00:00
refactor transaction history storage
This commit is contained in:
parent
f88e3d4423
commit
a25d020e54
6 changed files with 122 additions and 131 deletions
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
|
@ -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);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
Loading…
Reference in a new issue