mirror of
https://github.com/sparrowwallet/sparrow.git
synced 2024-12-25 13:16:44 +00:00
cormorant: improve scanning behaviour
This commit is contained in:
parent
61d9ad1875
commit
5ca60699ef
14 changed files with 141 additions and 37 deletions
|
@ -1611,6 +1611,10 @@ public class AppController implements Initializable {
|
||||||
});
|
});
|
||||||
subTabs.getSelectionModel().select(subTab);
|
subTabs.getSelectionModel().select(subTab);
|
||||||
|
|
||||||
|
if(wallet.isValid()) {
|
||||||
|
Platform.runLater(() -> walletForm.refreshHistory(AppServices.getCurrentBlockHeight()));
|
||||||
|
}
|
||||||
|
|
||||||
return walletForm;
|
return walletForm;
|
||||||
} catch(IOException e) {
|
} catch(IOException e) {
|
||||||
throw new RuntimeException(e);
|
throw new RuntimeException(e);
|
||||||
|
|
|
@ -22,6 +22,11 @@ public class CormorantPruneStatusEvent extends CormorantStatusEvent {
|
||||||
this.legacyWalletExists = legacyWalletExists;
|
this.legacyWalletExists = legacyWalletExists;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isFor(Wallet wallet) {
|
||||||
|
return this.wallet == wallet;
|
||||||
|
}
|
||||||
|
|
||||||
public Wallet getWallet() {
|
public Wallet getWallet() {
|
||||||
return wallet;
|
return wallet;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,17 +1,27 @@
|
||||||
package com.sparrowwallet.sparrow.event;
|
package com.sparrowwallet.sparrow.event;
|
||||||
|
|
||||||
|
import com.sparrowwallet.drongo.wallet.Wallet;
|
||||||
|
|
||||||
import java.time.Duration;
|
import java.time.Duration;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
public class CormorantScanStatusEvent extends CormorantStatusEvent {
|
public class CormorantScanStatusEvent extends CormorantStatusEvent {
|
||||||
|
private final Set<Wallet> scanningWallets;
|
||||||
private final int progress;
|
private final int progress;
|
||||||
private final Duration remainingDuration;
|
private final Duration remainingDuration;
|
||||||
|
|
||||||
public CormorantScanStatusEvent(String status, int progress, Duration remainingDuration) {
|
public CormorantScanStatusEvent(String status, Set<Wallet> scanningWallets, int progress, Duration remainingDuration) {
|
||||||
super(status);
|
super(status);
|
||||||
|
this.scanningWallets = scanningWallets;
|
||||||
this.progress = progress;
|
this.progress = progress;
|
||||||
this.remainingDuration = remainingDuration;
|
this.remainingDuration = remainingDuration;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isFor(Wallet wallet) {
|
||||||
|
return scanningWallets.contains(wallet);
|
||||||
|
}
|
||||||
|
|
||||||
public int getProgress() {
|
public int getProgress() {
|
||||||
return progress;
|
return progress;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
package com.sparrowwallet.sparrow.event;
|
package com.sparrowwallet.sparrow.event;
|
||||||
|
|
||||||
public class CormorantStatusEvent {
|
import com.sparrowwallet.drongo.wallet.Wallet;
|
||||||
|
|
||||||
|
public abstract class CormorantStatusEvent {
|
||||||
private final String status;
|
private final String status;
|
||||||
|
|
||||||
public CormorantStatusEvent(String status) {
|
public CormorantStatusEvent(String status) {
|
||||||
|
@ -10,4 +12,6 @@ public class CormorantStatusEvent {
|
||||||
public String getStatus() {
|
public String getStatus() {
|
||||||
return status;
|
return status;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public abstract boolean isFor(Wallet wallet);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
package com.sparrowwallet.sparrow.event;
|
package com.sparrowwallet.sparrow.event;
|
||||||
|
|
||||||
|
import com.sparrowwallet.drongo.wallet.Wallet;
|
||||||
|
|
||||||
import java.text.DateFormat;
|
import java.text.DateFormat;
|
||||||
import java.text.SimpleDateFormat;
|
import java.text.SimpleDateFormat;
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
|
@ -16,6 +18,11 @@ public class CormorantSyncStatusEvent extends CormorantStatusEvent {
|
||||||
this.tip = tip;
|
this.tip = tip;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isFor(Wallet wallet) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
public int getProgress() {
|
public int getProgress() {
|
||||||
return progress;
|
return progress;
|
||||||
}
|
}
|
||||||
|
|
|
@ -109,6 +109,7 @@ public class ElectrumServer {
|
||||||
if(previousServer != null && !electrumServer.equals(previousServer)) {
|
if(previousServer != null && !electrumServer.equals(previousServer)) {
|
||||||
retrievedScriptHashes.clear();
|
retrievedScriptHashes.clear();
|
||||||
retrievedTransactions.clear();
|
retrievedTransactions.clear();
|
||||||
|
TransactionHistoryService.walletLocks.values().forEach(walletLock -> walletLock.initialized = false);
|
||||||
}
|
}
|
||||||
previousServer = electrumServer;
|
previousServer = electrumServer;
|
||||||
|
|
||||||
|
@ -252,6 +253,7 @@ public class ElectrumServer {
|
||||||
public static void clearRetrievedScriptHashes(Wallet wallet) {
|
public static void clearRetrievedScriptHashes(Wallet wallet) {
|
||||||
wallet.getNode(KeyPurpose.RECEIVE).getChildren().stream().map(ElectrumServer::getScriptHash).forEach(ElectrumServer::clearRetrievedScriptHash);
|
wallet.getNode(KeyPurpose.RECEIVE).getChildren().stream().map(ElectrumServer::getScriptHash).forEach(ElectrumServer::clearRetrievedScriptHash);
|
||||||
wallet.getNode(KeyPurpose.CHANGE).getChildren().stream().map(ElectrumServer::getScriptHash).forEach(ElectrumServer::clearRetrievedScriptHash);
|
wallet.getNode(KeyPurpose.CHANGE).getChildren().stream().map(ElectrumServer::getScriptHash).forEach(ElectrumServer::clearRetrievedScriptHash);
|
||||||
|
TransactionHistoryService.walletLocks.computeIfAbsent(wallet.hashCode(), w -> new WalletLock()).initialized = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void clearRetrievedScriptHash(String scriptHash) {
|
private static void clearRetrievedScriptHash(String scriptHash) {
|
||||||
|
@ -1339,11 +1341,15 @@ public class ElectrumServer {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static class WalletLock {
|
||||||
|
public boolean initialized;
|
||||||
|
}
|
||||||
|
|
||||||
public static class TransactionHistoryService extends Service<Boolean> {
|
public static class TransactionHistoryService extends Service<Boolean> {
|
||||||
private final Wallet mainWallet;
|
private final Wallet mainWallet;
|
||||||
private final List<Wallet> filterToWallets;
|
private final List<Wallet> filterToWallets;
|
||||||
private final Set<WalletNode> filterToNodes;
|
private final Set<WalletNode> filterToNodes;
|
||||||
private final static Map<Wallet, Object> walletSynchronizeLocks = new HashMap<>();
|
private final static Map<Integer, WalletLock> walletLocks = Collections.synchronizedMap(new HashMap<>());
|
||||||
|
|
||||||
public TransactionHistoryService(Wallet wallet) {
|
public TransactionHistoryService(Wallet wallet) {
|
||||||
this.mainWallet = wallet;
|
this.mainWallet = wallet;
|
||||||
|
@ -1389,10 +1395,11 @@ public class ElectrumServer {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
boolean initial = (walletSynchronizeLocks.putIfAbsent(wallet, new Object()) == null);
|
WalletLock walletLock = walletLocks.computeIfAbsent(wallet.hashCode(), w -> new WalletLock());
|
||||||
synchronized(walletSynchronizeLocks.get(wallet)) {
|
synchronized(walletLock) {
|
||||||
if(initial) {
|
if(!walletLock.initialized) {
|
||||||
addCalculatedScriptHashes(wallet);
|
addCalculatedScriptHashes(wallet);
|
||||||
|
walletLock.initialized = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(isConnected()) {
|
if(isConnected()) {
|
||||||
|
|
|
@ -43,6 +43,8 @@ public class Cormorant {
|
||||||
electrumServerThread.setDaemon(true);
|
electrumServerThread.setDaemon(true);
|
||||||
electrumServerThread.start();
|
electrumServerThread.start();
|
||||||
|
|
||||||
|
bitcoindClient.waitUntilInitialImportStarted();
|
||||||
|
|
||||||
running = true;
|
running = true;
|
||||||
return new Server(Protocol.TCP.toUrlString(com.sparrowwallet.sparrow.net.ElectrumServer.CORE_ELECTRUM_HOST, electrumServer.getPort()));
|
return new Server(Protocol.TCP.toUrlString(com.sparrowwallet.sparrow.net.ElectrumServer.CORE_ELECTRUM_HOST, electrumServer.getPort()));
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,6 +8,7 @@ import com.sparrowwallet.drongo.Utils;
|
||||||
import com.sparrowwallet.drongo.wallet.BlockTransactionHash;
|
import com.sparrowwallet.drongo.wallet.BlockTransactionHash;
|
||||||
import com.sparrowwallet.drongo.wallet.Wallet;
|
import com.sparrowwallet.drongo.wallet.Wallet;
|
||||||
import com.sparrowwallet.drongo.wallet.WalletNode;
|
import com.sparrowwallet.drongo.wallet.WalletNode;
|
||||||
|
import com.sparrowwallet.sparrow.AppServices;
|
||||||
import com.sparrowwallet.sparrow.EventManager;
|
import com.sparrowwallet.sparrow.EventManager;
|
||||||
import com.sparrowwallet.sparrow.event.CormorantPruneStatusEvent;
|
import com.sparrowwallet.sparrow.event.CormorantPruneStatusEvent;
|
||||||
import com.sparrowwallet.sparrow.event.CormorantScanStatusEvent;
|
import com.sparrowwallet.sparrow.event.CormorantScanStatusEvent;
|
||||||
|
@ -50,6 +51,8 @@ public class BitcoindClient {
|
||||||
private final Map<String, Lock> descriptorLocks = Collections.synchronizedMap(new HashMap<>());
|
private final Map<String, Lock> descriptorLocks = Collections.synchronizedMap(new HashMap<>());
|
||||||
private final Map<String, ScanDate> importedDescriptors = Collections.synchronizedMap(new HashMap<>());
|
private final Map<String, ScanDate> importedDescriptors = Collections.synchronizedMap(new HashMap<>());
|
||||||
|
|
||||||
|
private final Map<String, Date> descriptorBirthDates = new HashMap<>();
|
||||||
|
|
||||||
private boolean initialized;
|
private boolean initialized;
|
||||||
private boolean stopped;
|
private boolean stopped;
|
||||||
|
|
||||||
|
@ -62,6 +65,11 @@ public class BitcoindClient {
|
||||||
private boolean syncing;
|
private boolean syncing;
|
||||||
|
|
||||||
private final Lock scanningLock = new ReentrantLock();
|
private final Lock scanningLock = new ReentrantLock();
|
||||||
|
private final Set<String> scanningDescriptors = Collections.synchronizedSet(new HashSet<>());
|
||||||
|
|
||||||
|
private final Lock initialImportLock = new ReentrantLock();
|
||||||
|
private final Condition initialImportCondition = initialImportLock.newCondition();
|
||||||
|
private boolean initialImportStarted;
|
||||||
|
|
||||||
public BitcoindClient() {
|
public BitcoindClient() {
|
||||||
BitcoindTransport bitcoindTransport;
|
BitcoindTransport bitcoindTransport;
|
||||||
|
@ -127,49 +135,52 @@ public class BitcoindClient {
|
||||||
return getBitcoindService().listSinceBlock(blockHash, 1, true, true, true);
|
return getBitcoindService().listSinceBlock(blockHash, 1, true, true, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void importWallets(Set<Wallet> wallets) throws ImportFailedException {
|
public void importWallets(Collection<Wallet> wallets) throws ImportFailedException {
|
||||||
importDescriptors(getWalletDescriptors(wallets));
|
importDescriptors(getWalletDescriptors(wallets));
|
||||||
}
|
}
|
||||||
|
|
||||||
public void importWallet(Wallet wallet) throws ImportFailedException {
|
public void importWallet(Wallet wallet) throws ImportFailedException {
|
||||||
importDescriptors(getWalletDescriptors(Set.of(wallet)));
|
//To avoid unnecessary rescans, get all related wallets
|
||||||
|
importWallets(wallet.isMasterWallet() ? wallet.getAllWallets() : wallet.getMasterWallet().getAllWallets());
|
||||||
}
|
}
|
||||||
|
|
||||||
private Map<String, ScanDate> getWalletDescriptors(Set<Wallet> wallets) throws ImportFailedException {
|
private Map<String, ScanDate> getWalletDescriptors(Collection<Wallet> wallets) throws ImportFailedException {
|
||||||
List<Wallet> validWallets = wallets.stream().filter(Wallet::isValid).collect(Collectors.toList());
|
List<Wallet> validWallets = wallets.stream().filter(Wallet::isValid).collect(Collectors.toList());
|
||||||
|
|
||||||
|
Date earliestBirthDate = validWallets.stream().map(Wallet::getBirthDate).filter(Objects::nonNull).sorted().findFirst().orElse(null);
|
||||||
Map<String, ScanDate> outputDescriptors = new LinkedHashMap<>();
|
Map<String, ScanDate> outputDescriptors = new LinkedHashMap<>();
|
||||||
for(Wallet wallet : validWallets) {
|
for(Wallet wallet : validWallets) {
|
||||||
if(pruned) {
|
if(pruned) {
|
||||||
Optional<Date> optPrunedDate = getPrunedDate();
|
Optional<Date> optPrunedDate = getPrunedDate();
|
||||||
if(optPrunedDate.isPresent() && wallet.getBirthDate() != null) {
|
if(optPrunedDate.isPresent() && earliestBirthDate != null) {
|
||||||
Date prunedDate = optPrunedDate.get();
|
Date prunedDate = optPrunedDate.get();
|
||||||
Date earliestScanDate = wallet.getBirthDate();
|
if(earliestBirthDate.before(prunedDate)) {
|
||||||
if(earliestScanDate.before(prunedDate)) {
|
|
||||||
if(!prunedWarningShown) {
|
if(!prunedWarningShown) {
|
||||||
prunedWarningShown = true;
|
prunedWarningShown = true;
|
||||||
Platform.runLater(() -> EventManager.get().post(new CormorantPruneStatusEvent("Error: Wallet birthday earlier than Bitcoin Core prune date", wallet, earliestScanDate, prunedDate, legacyWalletExists)));
|
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");
|
throw new ImportFailedException("Wallet birthday earlier than prune date");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
OutputDescriptor receiveOutputDescriptor = OutputDescriptor.getOutputDescriptor(wallet, KeyPurpose.RECEIVE);
|
String receiveOutputDescriptor = OutputDescriptor.getOutputDescriptor(wallet, KeyPurpose.RECEIVE).toString(false, false);
|
||||||
outputDescriptors.put(OutputDescriptor.normalize(receiveOutputDescriptor.toString(false, false)), getScanDate(wallet, KeyPurpose.RECEIVE));
|
addOutputDescriptor(outputDescriptors, receiveOutputDescriptor, wallet, KeyPurpose.RECEIVE, earliestBirthDate);
|
||||||
OutputDescriptor changeOutputDescriptor = OutputDescriptor.getOutputDescriptor(wallet, KeyPurpose.CHANGE);
|
String changeOutputDescriptor = OutputDescriptor.getOutputDescriptor(wallet, KeyPurpose.CHANGE).toString(false, false);
|
||||||
outputDescriptors.put(OutputDescriptor.normalize(changeOutputDescriptor.toString(false, false)), getScanDate(wallet, KeyPurpose.CHANGE));
|
addOutputDescriptor(outputDescriptors, changeOutputDescriptor, wallet, KeyPurpose.CHANGE, earliestBirthDate);
|
||||||
|
|
||||||
if(wallet.isMasterWallet() && wallet.hasPaymentCode()) {
|
if(wallet.isMasterWallet() && wallet.hasPaymentCode()) {
|
||||||
Wallet notificationWallet = wallet.getNotificationWallet();
|
Wallet notificationWallet = wallet.getNotificationWallet();
|
||||||
WalletNode notificationNode = notificationWallet.getNode(KeyPurpose.NOTIFICATION);
|
WalletNode notificationNode = notificationWallet.getNode(KeyPurpose.NOTIFICATION);
|
||||||
outputDescriptors.put(OutputDescriptor.normalize(OutputDescriptor.toDescriptorString(notificationNode.getAddress())), getScanDate(wallet, null));
|
String notificationOutputDescriptor = OutputDescriptor.toDescriptorString(notificationNode.getAddress());
|
||||||
|
addOutputDescriptor(outputDescriptors, notificationOutputDescriptor, wallet, null, earliestBirthDate);
|
||||||
|
|
||||||
for(Wallet childWallet : wallet.getChildWallets()) {
|
for(Wallet childWallet : wallet.getChildWallets()) {
|
||||||
if(childWallet.isNested()) {
|
if(childWallet.isNested()) {
|
||||||
for(KeyPurpose keyPurpose : KeyPurpose.DEFAULT_PURPOSES) {
|
for(KeyPurpose keyPurpose : KeyPurpose.DEFAULT_PURPOSES) {
|
||||||
for(WalletNode addressNode : childWallet.getNode(keyPurpose).getChildren()) {
|
for(WalletNode addressNode : childWallet.getNode(keyPurpose).getChildren()) {
|
||||||
outputDescriptors.put(OutputDescriptor.normalize(OutputDescriptor.toDescriptorString(addressNode.getAddress())), getScanDate(childWallet, null));
|
String addressOutputDescriptor = OutputDescriptor.toDescriptorString(addressNode.getAddress());
|
||||||
|
addOutputDescriptor(outputDescriptors, addressOutputDescriptor, childWallet, null, earliestBirthDate);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -180,6 +191,12 @@ public class BitcoindClient {
|
||||||
return outputDescriptors;
|
return outputDescriptors;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void addOutputDescriptor(Map<String, ScanDate> outputDescriptors, String outputDescriptor, Wallet wallet, KeyPurpose keyPurpose, Date earliestBirthDate) {
|
||||||
|
String normalizedDescriptor = OutputDescriptor.normalize(outputDescriptor);
|
||||||
|
ScanDate scanDate = getScanDate(normalizedDescriptor, wallet, keyPurpose, earliestBirthDate);
|
||||||
|
outputDescriptors.put(normalizedDescriptor, scanDate);
|
||||||
|
}
|
||||||
|
|
||||||
private Optional<Date> getPrunedDate() {
|
private Optional<Date> getPrunedDate() {
|
||||||
BlockchainInfo blockchainInfo = getBitcoindService().getBlockchainInfo();
|
BlockchainInfo blockchainInfo = getBitcoindService().getBlockchainInfo();
|
||||||
if(blockchainInfo.pruned()) {
|
if(blockchainInfo.pruned()) {
|
||||||
|
@ -191,24 +208,30 @@ public class BitcoindClient {
|
||||||
return Optional.empty();
|
return Optional.empty();
|
||||||
}
|
}
|
||||||
|
|
||||||
private ScanDate getScanDate(Wallet wallet, KeyPurpose keyPurpose) {
|
private ScanDate getScanDate(String normalizedDescriptor, Wallet wallet, KeyPurpose keyPurpose, Date earliestBirthDate) {
|
||||||
Integer range = (keyPurpose == null ? null : wallet.getFreshNode(keyPurpose).getIndex() + GAP_LIMIT);
|
Integer range = (keyPurpose == null ? null : wallet.getFreshNode(keyPurpose).getIndex() + GAP_LIMIT);
|
||||||
|
|
||||||
|
//Force a rescan if loading a wallet with a birthday later than existing transactions, or if the wallet birthdate has been set or changed to an earlier date from the last check
|
||||||
boolean forceRescan = false;
|
boolean forceRescan = false;
|
||||||
Date txBirthDate = wallet.getTransactions().values().stream().map(BlockTransactionHash::getDate).filter(Objects::nonNull).min(Date::compareTo).orElse(null);
|
Date txBirthDate = wallet.getTransactions().values().stream().map(BlockTransactionHash::getDate).filter(Objects::nonNull).min(Date::compareTo).orElse(null);
|
||||||
if((wallet.getBirthDate() != null && txBirthDate != null && wallet.getBirthDate().before(txBirthDate)) || (txBirthDate == null && wallet.getStoredBlockHeight() != null && wallet.getStoredBlockHeight() == 0)) {
|
Date lastBirthDate = descriptorBirthDates.get(normalizedDescriptor);
|
||||||
|
if((wallet.getBirthDate() != null && txBirthDate != null && wallet.getBirthDate().before(txBirthDate))
|
||||||
|
|| (descriptorBirthDates.containsKey(normalizedDescriptor) && earliestBirthDate != null && (lastBirthDate == null || earliestBirthDate.before(lastBirthDate)))) {
|
||||||
forceRescan = true;
|
forceRescan = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
return new ScanDate(wallet.getBirthDate() == null && !wallet.isMasterWallet() ? wallet.getMasterWallet().getBirthDate() : wallet.getBirthDate(), range, forceRescan);
|
return new ScanDate(earliestBirthDate, range, forceRescan);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void importDescriptors(Map<String, ScanDate> descriptors) {
|
private void importDescriptors(Map<String, ScanDate> descriptors) {
|
||||||
for(String descriptor : descriptors.keySet()) {
|
for(String descriptor : descriptors.keySet()) {
|
||||||
Lock lock = descriptorLocks.computeIfAbsent(descriptor, desc -> new ReentrantLock());
|
Lock lock = descriptorLocks.computeIfAbsent(descriptor, desc -> new ReentrantLock());
|
||||||
lock.lock();
|
lock.lock();
|
||||||
|
descriptorBirthDates.put(descriptor, descriptors.get(descriptor).rescanSince);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
signalInitialImportStarted();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
Set<String> addedDescriptors = addDescriptors(descriptors);
|
Set<String> addedDescriptors = addDescriptors(descriptors);
|
||||||
if(!addedDescriptors.isEmpty()) {
|
if(!addedDescriptors.isEmpty()) {
|
||||||
|
@ -268,10 +291,13 @@ public class BitcoindClient {
|
||||||
List<ImportDescriptorResult> results;
|
List<ImportDescriptorResult> results;
|
||||||
scanningLock.lock();
|
scanningLock.lock();
|
||||||
try {
|
try {
|
||||||
|
scanningDescriptors.addAll(importingDescriptors.keySet());
|
||||||
results = getBitcoindService().importDescriptors(importDescriptors);
|
results = getBitcoindService().importDescriptors(importDescriptors);
|
||||||
} finally {
|
} finally {
|
||||||
scanningLock.unlock();
|
scanningLock.unlock();
|
||||||
Platform.runLater(() -> EventManager.get().post(new CormorantScanStatusEvent("Scanning completed", 100, Duration.ZERO)));
|
Set<Wallet> scanningWallets = getScanningWallets();
|
||||||
|
scanningDescriptors.clear();
|
||||||
|
Platform.runLater(() -> EventManager.get().post(new CormorantScanStatusEvent("Scanning completed", scanningWallets, 100, Duration.ZERO)));
|
||||||
}
|
}
|
||||||
|
|
||||||
for(int i = 0; i < importDescriptors.size(); i++) {
|
for(int i = 0; i < importDescriptors.size(); i++) {
|
||||||
|
@ -416,6 +442,31 @@ public class BitcoindClient {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void waitUntilInitialImportStarted() {
|
||||||
|
initialImportLock.lock();
|
||||||
|
try {
|
||||||
|
if(!initialImportStarted) {
|
||||||
|
initialImportCondition.await();
|
||||||
|
}
|
||||||
|
} catch(InterruptedException e) {
|
||||||
|
//ignore
|
||||||
|
} finally {
|
||||||
|
initialImportLock.unlock();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void signalInitialImportStarted() {
|
||||||
|
if(!initialImportStarted) {
|
||||||
|
initialImportLock.lock();
|
||||||
|
try {
|
||||||
|
initialImportStarted = true;
|
||||||
|
initialImportCondition.signal();
|
||||||
|
} finally {
|
||||||
|
initialImportLock.unlock();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public Store getStore() {
|
public Store getStore() {
|
||||||
return store;
|
return store;
|
||||||
}
|
}
|
||||||
|
@ -481,9 +532,10 @@ public class BitcoindClient {
|
||||||
} else {
|
} else {
|
||||||
WalletInfo walletInfo = getBitcoindService().getWalletInfo();
|
WalletInfo walletInfo = getBitcoindService().getWalletInfo();
|
||||||
if(walletInfo.scanning().isScanning()) {
|
if(walletInfo.scanning().isScanning()) {
|
||||||
|
Set<Wallet> scanningWallets = getScanningWallets();
|
||||||
int percent = walletInfo.scanning().getPercent();
|
int percent = walletInfo.scanning().getPercent();
|
||||||
Duration remainingDuration = walletInfo.scanning().getRemaining();
|
Duration remainingDuration = walletInfo.scanning().getRemaining();
|
||||||
Platform.runLater(() -> EventManager.get().post(new CormorantScanStatusEvent("Scanning" + (percent < 100 ? " (" + percent + "%)" : ""), percent, remainingDuration)));
|
Platform.runLater(() -> EventManager.get().post(new CormorantScanStatusEvent("Scanning" + (percent < 100 ? " (" + percent + "%)" : ""), scanningWallets, percent, remainingDuration)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch(Exception e) {
|
} catch(Exception e) {
|
||||||
|
@ -492,6 +544,19 @@ public class BitcoindClient {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private Set<Wallet> getScanningWallets() {
|
||||||
|
Set<Wallet> scanningWallets = new HashSet<>();
|
||||||
|
Set<Wallet> openWallets = AppServices.get().getOpenWallets().keySet();
|
||||||
|
for(Wallet openWallet : openWallets) {
|
||||||
|
String normalizedDescriptor = OutputDescriptor.normalize(OutputDescriptor.getOutputDescriptor(openWallet, KeyPurpose.RECEIVE).toString(false, false));
|
||||||
|
if(scanningDescriptors.contains(normalizedDescriptor)) {
|
||||||
|
scanningWallets.add(openWallet);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return scanningWallets;
|
||||||
|
}
|
||||||
|
|
||||||
private record ScanDate(Date rescanSince, Integer range, boolean forceRescan) {
|
private record ScanDate(Date rescanSince, Integer range, boolean forceRescan) {
|
||||||
public Object getTimestamp() {
|
public Object getTimestamp() {
|
||||||
return rescanSince == null ? "now" : rescanSince.getTime() / 1000;
|
return rescanSince == null ? "now" : rescanSince.getTime() / 1000;
|
||||||
|
|
|
@ -67,7 +67,7 @@ public class BitcoindTransport implements Transport {
|
||||||
|
|
||||||
connection.setDoOutput(true);
|
connection.setDoOutput(true);
|
||||||
|
|
||||||
log.debug("> " + request);
|
log.trace("> " + request);
|
||||||
|
|
||||||
try(OutputStream os = connection.getOutputStream()) {
|
try(OutputStream os = connection.getOutputStream()) {
|
||||||
byte[] jsonBytes = request.getBytes(StandardCharsets.UTF_8);
|
byte[] jsonBytes = request.getBytes(StandardCharsets.UTF_8);
|
||||||
|
@ -93,7 +93,7 @@ public class BitcoindTransport implements Transport {
|
||||||
}
|
}
|
||||||
|
|
||||||
String response = res.toString();
|
String response = res.toString();
|
||||||
log.debug("< " + response);
|
log.trace("< " + response);
|
||||||
|
|
||||||
return response;
|
return response;
|
||||||
}
|
}
|
||||||
|
|
|
@ -142,6 +142,10 @@ public class SparrowTerminal extends Application {
|
||||||
.map(data -> new WalletTabData(TabData.TabType.WALLET, data.getWalletForm())).collect(Collectors.toList());
|
.map(data -> new WalletTabData(TabData.TabType.WALLET, data.getWalletForm())).collect(Collectors.toList());
|
||||||
EventManager.get().post(new OpenWalletsEvent(DEFAULT_WINDOW, walletTabDataList));
|
EventManager.get().post(new OpenWalletsEvent(DEFAULT_WINDOW, walletTabDataList));
|
||||||
|
|
||||||
|
if(wallet.isValid()) {
|
||||||
|
Platform.runLater(() -> walletForm.refreshHistory(AppServices.getCurrentBlockHeight()));
|
||||||
|
}
|
||||||
|
|
||||||
Set<File> walletFiles = new LinkedHashSet<>();
|
Set<File> walletFiles = new LinkedHashSet<>();
|
||||||
walletFiles.add(storage.getWalletFile());
|
walletFiles.add(storage.getWalletFile());
|
||||||
if(Config.get().getRecentWalletFiles() != null) {
|
if(Config.get().getRecentWalletFiles() != null) {
|
||||||
|
|
|
@ -24,7 +24,7 @@ public class SettingsWalletForm extends WalletForm {
|
||||||
private Wallet walletCopy;
|
private Wallet walletCopy;
|
||||||
|
|
||||||
public SettingsWalletForm(Storage storage, Wallet currentWallet) {
|
public SettingsWalletForm(Storage storage, Wallet currentWallet) {
|
||||||
super(storage, currentWallet, false);
|
super(storage, currentWallet);
|
||||||
this.walletCopy = currentWallet.copy();
|
this.walletCopy = currentWallet.copy();
|
||||||
this.walletCopy.setMasterWallet(walletCopy.isMasterWallet() ? null : walletCopy.getMasterWallet().copy());
|
this.walletCopy.setMasterWallet(walletCopy.isMasterWallet() ? null : walletCopy.getMasterWallet().copy());
|
||||||
}
|
}
|
||||||
|
|
|
@ -244,8 +244,10 @@ public class TransactionsController extends WalletFormController implements Init
|
||||||
|
|
||||||
@Subscribe
|
@Subscribe
|
||||||
public void cormorantStatus(CormorantStatusEvent event) {
|
public void cormorantStatus(CormorantStatusEvent event) {
|
||||||
|
if(event.isFor(walletForm.getWallet())) {
|
||||||
walletHistoryStatus(new WalletHistoryStatusEvent(walletForm.getWallet(), true, event.getStatus()));
|
walletHistoryStatus(new WalletHistoryStatusEvent(walletForm.getWallet(), true, event.getStatus()));
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Subscribe
|
@Subscribe
|
||||||
public void bwtSyncStatus(BwtSyncStatusEvent event) {
|
public void bwtSyncStatus(BwtSyncStatusEvent event) {
|
||||||
|
|
|
@ -571,8 +571,10 @@ public class UtxosController extends WalletFormController implements Initializab
|
||||||
|
|
||||||
@Subscribe
|
@Subscribe
|
||||||
public void cormorantStatus(CormorantStatusEvent event) {
|
public void cormorantStatus(CormorantStatusEvent event) {
|
||||||
|
if(event.isFor(walletForm.getWallet())) {
|
||||||
walletHistoryStatus(new WalletHistoryStatusEvent(walletForm.getWallet(), true, event.getStatus()));
|
walletHistoryStatus(new WalletHistoryStatusEvent(walletForm.getWallet(), true, event.getStatus()));
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Subscribe
|
@Subscribe
|
||||||
public void bwtSyncStatus(BwtSyncStatusEvent event) {
|
public void bwtSyncStatus(BwtSyncStatusEvent event) {
|
||||||
|
|
|
@ -54,10 +54,6 @@ public class WalletForm {
|
||||||
private final BooleanProperty lockedProperty = new SimpleBooleanProperty(false);
|
private final BooleanProperty lockedProperty = new SimpleBooleanProperty(false);
|
||||||
|
|
||||||
public WalletForm(Storage storage, Wallet currentWallet) {
|
public WalletForm(Storage storage, Wallet currentWallet) {
|
||||||
this(storage, currentWallet, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
public WalletForm(Storage storage, Wallet currentWallet, boolean refreshHistory) {
|
|
||||||
this.storage = storage;
|
this.storage = storage;
|
||||||
this.wallet = currentWallet;
|
this.wallet = currentWallet;
|
||||||
|
|
||||||
|
@ -70,10 +66,6 @@ public class WalletForm {
|
||||||
}, exception -> {
|
}, exception -> {
|
||||||
log.error("Error refreshing nodes", exception);
|
log.error("Error refreshing nodes", exception);
|
||||||
});
|
});
|
||||||
|
|
||||||
if(refreshHistory && wallet.isValid()) {
|
|
||||||
refreshHistory(AppServices.getCurrentBlockHeight());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public Wallet getWallet() {
|
public Wallet getWallet() {
|
||||||
|
|
Loading…
Reference in a new issue