handle server returning history with the same tx at multiple heights (electrs issue #316)

This commit is contained in:
Craig Raw 2020-11-05 16:29:15 +02:00
parent 1392199f5c
commit 8d413e839c
3 changed files with 59 additions and 6 deletions

View file

@ -233,7 +233,19 @@ public class ElectrumServer {
if(optionalNode.isPresent()) {
WalletNode node = optionalNode.get();
Set<BlockTransactionHash> 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<BlockTransactionHash> references = Arrays.stream(txes).map(ScriptHashTx::getBlockchainTransactionHash)
.collect(TreeSet::new, (set, ref) -> {
Optional<BlockTransactionHash> 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<BlockTransactionHash> existingReferences = nodeTransactionMap.get(node);
if(existingReferences == null) {

View file

@ -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<TransactionEnt
}
}
public boolean isComplete() {
int validEntries = 0;
Map<BlockTransactionHashIndex, WalletNode> walletTxos = wallet.getWalletTxos();
for(TransactionInput txInput : blockTransaction.getTransaction().getInputs()) {
Optional<BlockTransactionHashIndex> 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<BlockTransactionHashIndex> 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<Entry> createChildEntries(Wallet wallet, Map<BlockTransactionHashIndex, KeyPurpose> incoming, Map<BlockTransactionHashIndex, KeyPurpose> outgoing) {
List<Entry> incomingOutputEntries = incoming.entrySet().stream().map(input -> new TransactionHashIndexEntry(wallet, input.getKey(), HashIndexEntry.Type.OUTPUT, input.getValue())).collect(Collectors.toList());
List<Entry> outgoingInputEntries = outgoing.entrySet().stream().map(output -> new TransactionHashIndexEntry(wallet, output.getKey(), HashIndexEntry.Type.INPUT, output.getValue())).collect(Collectors.toList());

View file

@ -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<Entry> entriesComplete = entriesAdded.stream().filter(txEntry -> ((TransactionEntry)txEntry).isComplete()).collect(Collectors.toList());
if(!entriesComplete.isEmpty()) {
List<BlockTransaction> 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<WalletTransaction> getWalletTransactions(Wallet wallet) {