From 2cd64aa650b4470262947f7b41d7b5cc371c9087 Mon Sep 17 00:00:00 2001 From: Craig Raw Date: Wed, 1 Feb 2023 13:48:20 +0200 Subject: [PATCH] improve handling of scan dates earlier than core pruned date --- .../control/PrivateKeySweepDialog.java | 4 +- .../sparrow/net/cormorant/Cormorant.java | 11 ++-- .../cormorant/bitcoind/BitcoindClient.java | 50 +++++++++++-------- .../ScanDateBeforePruneException.java | 21 ++++++++ 4 files changed, 62 insertions(+), 24 deletions(-) create mode 100644 src/main/java/com/sparrowwallet/sparrow/net/cormorant/bitcoind/ScanDateBeforePruneException.java diff --git a/src/main/java/com/sparrowwallet/sparrow/control/PrivateKeySweepDialog.java b/src/main/java/com/sparrowwallet/sparrow/control/PrivateKeySweepDialog.java index 1900b9e5..f027aa0b 100644 --- a/src/main/java/com/sparrowwallet/sparrow/control/PrivateKeySweepDialog.java +++ b/src/main/java/com/sparrowwallet/sparrow/control/PrivateKeySweepDialog.java @@ -1,5 +1,6 @@ package com.sparrowwallet.sparrow.control; +import com.google.common.base.Throwables; import com.google.common.io.Files; import com.sparrowwallet.drongo.KeyPurpose; import com.sparrowwallet.drongo.address.Address; @@ -322,8 +323,9 @@ public class PrivateKeySweepDialog extends Dialog { createTransaction(privateKey.getKey(), scriptType, addressUtxosService.getValue(), destAddress); }); addressUtxosService.setOnFailed(failedEvent -> { + Throwable rootCause = Throwables.getRootCause(failedEvent.getSource().getException()); log.error("Error retrieving outputs for address " + fromAddress, failedEvent.getSource().getException()); - AppServices.showErrorDialog("Error retrieving outputs for address", failedEvent.getSource().getException().getMessage()); + AppServices.showErrorDialog("Error retrieving outputs for address", rootCause.getMessage()); }); if(Config.get().getServerType() == ServerType.BITCOIN_CORE) { diff --git a/src/main/java/com/sparrowwallet/sparrow/net/cormorant/Cormorant.java b/src/main/java/com/sparrowwallet/sparrow/net/cormorant/Cormorant.java index d02094ff..ac2dea3e 100644 --- a/src/main/java/com/sparrowwallet/sparrow/net/cormorant/Cormorant.java +++ b/src/main/java/com/sparrowwallet/sparrow/net/cormorant/Cormorant.java @@ -6,6 +6,7 @@ import com.sparrowwallet.drongo.wallet.Wallet; import com.sparrowwallet.sparrow.AppServices; import com.sparrowwallet.sparrow.io.Server; import com.sparrowwallet.sparrow.net.Protocol; +import com.sparrowwallet.sparrow.net.ServerException; import com.sparrowwallet.sparrow.net.cormorant.bitcoind.BitcoindClient; import com.sparrowwallet.sparrow.net.cormorant.bitcoind.CormorantBitcoindException; import com.sparrowwallet.sparrow.net.cormorant.bitcoind.ImportFailedException; @@ -60,14 +61,18 @@ public class Cormorant { bitcoindClient.importWallet(wallet); return true; } catch(ImportFailedException e) { - log.debug("Failed to import wallets", e); + log.warn("Failed to import wallets", e); return false; } } - public void checkAddressImport(Address address, Date since) { + public void checkAddressImport(Address address, Date since) throws ServerException { //Will block until address descriptor has been added - bitcoindClient.importAddress(address, since); + try { + bitcoindClient.importAddress(address, since); + } catch(ImportFailedException e) { + throw new ServerException("Failed to import address", e); + } } public boolean isRunning() { diff --git a/src/main/java/com/sparrowwallet/sparrow/net/cormorant/bitcoind/BitcoindClient.java b/src/main/java/com/sparrowwallet/sparrow/net/cormorant/bitcoind/BitcoindClient.java index 3f9e7ff6..05daca3a 100644 --- a/src/main/java/com/sparrowwallet/sparrow/net/cormorant/bitcoind/BitcoindClient.java +++ b/src/main/java/com/sparrowwallet/sparrow/net/cormorant/bitcoind/BitcoindClient.java @@ -145,7 +145,15 @@ public class BitcoindClient { } public void importWallets(Collection wallets) throws ImportFailedException { - importDescriptors(getWalletDescriptors(wallets)); + try { + importDescriptors(getWalletDescriptors(wallets)); + } catch(ScanDateBeforePruneException e) { + List prePruneWallets = wallets.stream().filter(wallet -> wallet.getBirthDate() != null && wallet.getBirthDate().before(e.getPrunedDate()) && wallet.isValid()).sorted(Comparator.comparingLong(o -> o.getBirthDate().getTime())).collect(Collectors.toList()); + if(!prePruneWallets.isEmpty()) { + Platform.runLater(() -> EventManager.get().post(new CormorantPruneStatusEvent("Error: Wallet birthday earlier than Bitcoin Core prune date", prePruneWallets.get(0), e.getRescanSince(), e.getPrunedDate(), legacyWalletExists))); + } + throw new ImportFailedException("Wallet birthday earlier than prune date"); + } } public void importWallet(Wallet wallet) throws ImportFailedException { @@ -153,11 +161,15 @@ public class BitcoindClient { importWallets(wallet.isMasterWallet() ? wallet.getAllWallets() : wallet.getMasterWallet().getAllWallets()); } - public void importAddress(Address address, Date since) { + public void importAddress(Address address, Date since) throws ImportFailedException { Map outputDescriptors = new HashMap<>(); String addressOutputDescriptor = OutputDescriptor.toDescriptorString(address); - outputDescriptors.put(OutputDescriptor.normalize(addressOutputDescriptor), new ScanDate(since, null, false)); - importDescriptors(outputDescriptors); + outputDescriptors.put(OutputDescriptor.normalize(addressOutputDescriptor), new ScanDate(since, null, true)); + try { + importDescriptors(outputDescriptors); + } catch(ScanDateBeforePruneException e) { + throw new ImportFailedException("Address birth date earlier than prune date."); + } } private Map getWalletDescriptors(Collection wallets) throws ImportFailedException { @@ -166,20 +178,6 @@ public class BitcoindClient { Date earliestBirthDate = validWallets.stream().map(Wallet::getBirthDate).filter(Objects::nonNull).sorted().findFirst().orElse(null); Map outputDescriptors = new LinkedHashMap<>(); for(Wallet wallet : validWallets) { - if(pruned) { - Optional optPrunedDate = getPrunedDate(); - if(optPrunedDate.isPresent() && earliestBirthDate != null) { - Date prunedDate = optPrunedDate.get(); - if(earliestBirthDate.before(prunedDate)) { - if(!prunedWarningShown) { - prunedWarningShown = true; - Platform.runLater(() -> EventManager.get().post(new CormorantPruneStatusEvent("Error: Wallet birthday earlier than Bitcoin Core prune date", wallet, earliestBirthDate, prunedDate, legacyWalletExists))); - } - throw new ImportFailedException("Wallet birthday earlier than prune date"); - } - } - } - String receiveOutputDescriptor = OutputDescriptor.getOutputDescriptor(wallet, KeyPurpose.RECEIVE).toString(false, false); addOutputDescriptor(outputDescriptors, receiveOutputDescriptor, wallet, KeyPurpose.RECEIVE, earliestBirthDate); String changeOutputDescriptor = OutputDescriptor.getOutputDescriptor(wallet, KeyPurpose.CHANGE).toString(false, false); @@ -248,7 +246,7 @@ public class BitcoindClient { return wallet.getStandardAccountType() == StandardAccount.WHIRLPOOL_POSTMIX && keyPurpose == KeyPurpose.RECEIVE ? POSTMIX_GAP_LIMIT : DEFAULT_GAP_LIMIT; } - private void importDescriptors(Map descriptors) { + private void importDescriptors(Map descriptors) throws ScanDateBeforePruneException { //Sort descriptors in alphanumeric order to avoid deadlocks, particularly with BIP47 wallets Set sortedDescriptors = new TreeSet<>(descriptors.keySet()); for(String descriptor : sortedDescriptors) { @@ -273,7 +271,7 @@ public class BitcoindClient { } } - private Set addDescriptors(Map descriptors) { + private Set addDescriptors(Map descriptors) throws ScanDateBeforePruneException { boolean forceRescan = descriptors.values().stream().anyMatch(scanDate -> scanDate.forceRescan); if(!initialized || forceRescan) { ListDescriptorsResult listDescriptorsResult = getBitcoindService().listDescriptors(false); @@ -303,6 +301,18 @@ public class BitcoindClient { } } + if(pruned) { + Optional optPrunedDate = getPrunedDate(); + if(optPrunedDate.isPresent()) { + Date prunedDate = optPrunedDate.get(); + Optional prePruneImport = importingDescriptors.values().stream().filter(scanDate -> scanDate.rescanSince != null && scanDate.rescanSince.before(prunedDate)).findFirst(); + if(prePruneImport.isPresent()) { + ScanDate scanDate = prePruneImport.get(); + throw new ScanDateBeforePruneException(scanDate.rescanSince, prunedDate); + } + } + } + if(!importingDescriptors.isEmpty()) { log.debug("Importing descriptors " + importingDescriptors); diff --git a/src/main/java/com/sparrowwallet/sparrow/net/cormorant/bitcoind/ScanDateBeforePruneException.java b/src/main/java/com/sparrowwallet/sparrow/net/cormorant/bitcoind/ScanDateBeforePruneException.java new file mode 100644 index 00000000..b9bfffac --- /dev/null +++ b/src/main/java/com/sparrowwallet/sparrow/net/cormorant/bitcoind/ScanDateBeforePruneException.java @@ -0,0 +1,21 @@ +package com.sparrowwallet.sparrow.net.cormorant.bitcoind; + +import java.util.Date; + +public class ScanDateBeforePruneException extends Exception { + private final Date rescanSince; + private final Date prunedDate; + + public ScanDateBeforePruneException(Date rescanSince, Date prunedDate) { + this.rescanSince = rescanSince; + this.prunedDate = prunedDate; + } + + public Date getRescanSince() { + return rescanSince; + } + + public Date getPrunedDate() { + return prunedDate; + } +}