From 8d413e839c4f39240e12029d0945ac8804761817 Mon Sep 17 00:00:00 2001 From: Craig Raw Date: Thu, 5 Nov 2020 16:29:15 +0200 Subject: [PATCH] handle server returning history with the same tx at multiple heights (electrs issue #316) --- .../sparrow/net/ElectrumServer.java | 14 +++++++- .../sparrow/wallet/TransactionEntry.java | 36 ++++++++++++++++--- .../wallet/WalletTransactionsEntry.java | 15 +++++++- 3 files changed, 59 insertions(+), 6 deletions(-) diff --git a/src/main/java/com/sparrowwallet/sparrow/net/ElectrumServer.java b/src/main/java/com/sparrowwallet/sparrow/net/ElectrumServer.java index d08451e4..0ffadf86 100644 --- a/src/main/java/com/sparrowwallet/sparrow/net/ElectrumServer.java +++ b/src/main/java/com/sparrowwallet/sparrow/net/ElectrumServer.java @@ -233,7 +233,19 @@ public class ElectrumServer { if(optionalNode.isPresent()) { WalletNode node = optionalNode.get(); - Set references = Arrays.stream(txes).map(ScriptHashTx::getBlockchainTransactionHash).collect(Collectors.toCollection(TreeSet::new)); + //Some servers can return the same tx as multiple ScriptHashTx entries with different heights. Take the highest height only + Set references = Arrays.stream(txes).map(ScriptHashTx::getBlockchainTransactionHash) + .collect(TreeSet::new, (set, ref) -> { + Optional optExisting = set.stream().filter(prev -> prev.getHash().equals(ref.getHash())).findFirst(); + if(optExisting.isPresent()) { + if(optExisting.get().getHeight() < ref.getHeight()) { + set.remove(optExisting.get()); + set.add(ref); + } + } else { + set.add(ref); + } + }, TreeSet::addAll); Set existingReferences = nodeTransactionMap.get(node); if(existingReferences == null) { diff --git a/src/main/java/com/sparrowwallet/sparrow/wallet/TransactionEntry.java b/src/main/java/com/sparrowwallet/sparrow/wallet/TransactionEntry.java index db93fab4..55a9b498 100644 --- a/src/main/java/com/sparrowwallet/sparrow/wallet/TransactionEntry.java +++ b/src/main/java/com/sparrowwallet/sparrow/wallet/TransactionEntry.java @@ -2,10 +2,9 @@ package com.sparrowwallet.sparrow.wallet; import com.google.common.eventbus.Subscribe; import com.sparrowwallet.drongo.KeyPurpose; -import com.sparrowwallet.drongo.wallet.BlockTransaction; -import com.sparrowwallet.drongo.wallet.BlockTransactionHash; -import com.sparrowwallet.drongo.wallet.BlockTransactionHashIndex; -import com.sparrowwallet.drongo.wallet.Wallet; +import com.sparrowwallet.drongo.protocol.TransactionInput; +import com.sparrowwallet.drongo.protocol.TransactionOutput; +import com.sparrowwallet.drongo.wallet.*; import com.sparrowwallet.sparrow.EventManager; import com.sparrowwallet.sparrow.WalletTabData; import com.sparrowwallet.sparrow.event.WalletBlockHeightChangedEvent; @@ -85,6 +84,35 @@ public class TransactionEntry extends Entry implements Comparable walletTxos = wallet.getWalletTxos(); + for(TransactionInput txInput : blockTransaction.getTransaction().getInputs()) { + Optional optRef = walletTxos.keySet().stream().filter(ref -> ref.getHash().equals(txInput.getOutpoint().getHash()) && ref.getIndex() == txInput.getOutpoint().getIndex()).findFirst(); + if(optRef.isPresent()) { + validEntries++; + if(getChildren().stream().noneMatch(entry -> ((HashIndexEntry)entry).getHashIndex().equals(optRef.get().getSpentBy()) && ((HashIndexEntry)entry).getType().equals(HashIndexEntry.Type.INPUT))) { + return false; + } + } + } + for(TransactionOutput txOutput : blockTransaction.getTransaction().getOutputs()) { + Optional optRef = walletTxos.keySet().stream().filter(ref -> ref.getHash().equals(txOutput.getHash()) && ref.getIndex() == txOutput.getIndex()).findFirst(); + if(optRef.isPresent()) { + validEntries++; + if(getChildren().stream().noneMatch(entry -> ((HashIndexEntry)entry).getHashIndex().equals(optRef.get()) && ((HashIndexEntry)entry).getType().equals(HashIndexEntry.Type.OUTPUT))) { + return false; + } + } + } + + if(getChildren().size() != validEntries) { + return false; + } + + return true; + } + private static List createChildEntries(Wallet wallet, Map incoming, Map outgoing) { List incomingOutputEntries = incoming.entrySet().stream().map(input -> new TransactionHashIndexEntry(wallet, input.getKey(), HashIndexEntry.Type.OUTPUT, input.getValue())).collect(Collectors.toList()); List outgoingInputEntries = outgoing.entrySet().stream().map(output -> new TransactionHashIndexEntry(wallet, output.getKey(), HashIndexEntry.Type.INPUT, output.getValue())).collect(Collectors.toList()); diff --git a/src/main/java/com/sparrowwallet/sparrow/wallet/WalletTransactionsEntry.java b/src/main/java/com/sparrowwallet/sparrow/wallet/WalletTransactionsEntry.java index aa677831..fd1632da 100644 --- a/src/main/java/com/sparrowwallet/sparrow/wallet/WalletTransactionsEntry.java +++ b/src/main/java/com/sparrowwallet/sparrow/wallet/WalletTransactionsEntry.java @@ -9,11 +9,15 @@ import com.sparrowwallet.sparrow.EventManager; import com.sparrowwallet.sparrow.event.NewWalletTransactionsEvent; import javafx.beans.property.LongProperty; import javafx.beans.property.LongPropertyBase; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import java.util.*; import java.util.stream.Collectors; public class WalletTransactionsEntry extends Entry { + private static final Logger log = LoggerFactory.getLogger(WalletTransactionsEntry.class); + private final Wallet wallet; public WalletTransactionsEntry(Wallet wallet) { @@ -67,12 +71,21 @@ public class WalletTransactionsEntry extends Entry { calculateBalances(); - if(!entriesAdded.isEmpty()) { + List entriesComplete = entriesAdded.stream().filter(txEntry -> ((TransactionEntry)txEntry).isComplete()).collect(Collectors.toList()); + if(!entriesComplete.isEmpty()) { List blockTransactions = entriesAdded.stream().map(txEntry -> ((TransactionEntry)txEntry).getBlockTransaction()).collect(Collectors.toList()); long totalBlockchainValue = entriesAdded.stream().filter(txEntry -> ((TransactionEntry)txEntry).getConfirmations() > 0).mapToLong(Entry::getValue).sum(); long totalMempoolValue = entriesAdded.stream().filter(txEntry -> ((TransactionEntry)txEntry).getConfirmations() == 0).mapToLong(Entry::getValue).sum(); EventManager.get().post(new NewWalletTransactionsEvent(wallet, blockTransactions, totalBlockchainValue, totalMempoolValue)); } + + if(entriesAdded.size() > entriesComplete.size()) { + entriesAdded.removeAll(entriesComplete); + for(Entry entry : entriesAdded) { + TransactionEntry txEntry = (TransactionEntry)entry; + log.warn("Not notifying for incomplete entry " + ((TransactionEntry)entry).getBlockTransaction().getHashAsString() + " value " + txEntry.getValue()); + } + } } private static Collection getWalletTransactions(Wallet wallet) {