hide wallet rescan hyperlink when nothing further can be scanned

This commit is contained in:
Craig Raw
2026-05-14 12:40:02 +02:00
parent a035767e38
commit 1be9ac1072
7 changed files with 87 additions and 19 deletions
+1 -1
Submodule drongo updated: 29cd4b7909...e4b63fbd19
@@ -16,7 +16,10 @@ import com.sparrowwallet.sparrow.event.WalletDataChangedEvent;
import com.sparrowwallet.sparrow.event.WalletHistoryStatusEvent;
import com.sparrowwallet.sparrow.io.Config;
import com.sparrowwallet.sparrow.io.Storage;
import com.sparrowwallet.sparrow.net.ElectrumServer;
import com.sparrowwallet.sparrow.net.ServerType;
import com.sparrowwallet.sparrow.net.cormorant.Cormorant;
import com.sparrowwallet.sparrow.net.cormorant.bitcoind.BitcoindClient;
import com.sparrowwallet.sparrow.wallet.Entry;
import io.reactivex.Observable;
import io.reactivex.subjects.PublishSubject;
@@ -107,7 +110,7 @@ public class CoinTreeTable extends TreeTableView<Entry> {
setPlaceholder(new Label("Error loading transactions: " + event.getErrorMessage()));
} else if(event.isLoading()) {
if(event.getStatusMessage() != null) {
setPlaceholder(new Label(event.getStatusMessage() + "..."));
setPlaceholder(new Label(event.getStatusMessage() + (event.getStatusMessage().contains("...") ? "" : "...")));
} else {
setPlaceholder(new Label("Loading transactions..."));
}
@@ -123,7 +126,7 @@ public class CoinTreeTable extends TreeTableView<Entry> {
StackPane stackPane = new StackPane();
stackPane.getChildren().add(AppServices.isConnecting() ? new Label("Loading transactions...") : new Label("No transactions"));
if((Config.get().getServerType() == ServerType.BITCOIN_CORE || wallet.getPolicyType() == PolicyType.SINGLE_SP) && !AppServices.isConnecting()) {
if((Config.get().getServerType() == ServerType.BITCOIN_CORE || wallet.getPolicyType() == PolicyType.SINGLE_SP) && !AppServices.isConnecting() && !isFullyScanned(wallet)) {
Hyperlink hyperlink = new Hyperlink();
hyperlink.setTranslateY(30);
hyperlink.setOnAction(event -> {
@@ -150,12 +153,47 @@ public class CoinTreeTable extends TreeTableView<Entry> {
}
stackPane.getChildren().add(hyperlink);
} else if(!AppServices.isConnecting() && Config.get().getServerType() == ServerType.BITCOIN_CORE && isFullyScanned(wallet)) {
Date prunedDate = getPrunedDate();
if(prunedDate != null) {
DateFormat dateFormat = new SimpleDateFormat(DateStringConverter.FORMAT_PATTERN);
Label prunedLabel = new Label("Scanned to pruned start date of " + dateFormat.format(prunedDate));
prunedLabel.setTranslateY(30);
stackPane.getChildren().add(prunedLabel);
}
}
stackPane.setAlignment(Pos.CENTER);
return stackPane;
}
private boolean isFullyScanned(Wallet wallet) {
if(wallet.getPolicyType() == PolicyType.SINGLE_SP) {
return wallet.isValid() && ElectrumServer.isSilentPaymentsFullyCovered(wallet.getSilentPaymentScanAddress());
}
if(Config.get().getServerType() == ServerType.BITCOIN_CORE) {
Date prunedDate = getPrunedDate();
return prunedDate != null && wallet.getBirthDate() != null && !wallet.getBirthDate().after(prunedDate);
}
return false;
}
private static Date getPrunedDate() {
Cormorant cormorant = ElectrumServer.getCormorant();
if(cormorant == null) {
return null;
}
BitcoindClient bitcoindClient = cormorant.getBitcoindClient();
if(bitcoindClient == null || !bitcoindClient.isPruned()) {
return null;
}
return bitcoindClient.getCachedPrunedDate();
}
protected void setupColumnSort(int defaultColumnIndex, TreeTableColumn.SortType defaultSortType) {
WalletTable.Sort columnSort = getSavedColumnSort();
if(columnSort == null) {
@@ -89,6 +89,8 @@ public class ElectrumServer {
private static final Map<String, SilentPaymentsScanCache> spScanCaches = new ConcurrentHashMap<>();
private static final int TAPROOT_ACTIVATION_HEIGHT = 709632;
private final static Map<String, Integer> subscribedRecent = new ConcurrentHashMap<>();
private final static Map<String, String> broadcastRecent = new ConcurrentHashMap<>();
@@ -1377,6 +1379,33 @@ public class ElectrumServer {
return spScanCaches.containsKey(scanAddress.getAddress());
}
public static boolean isSilentPaymentsFullyCovered(SilentPaymentScanAddress scanAddress) {
if(scanAddress == null) {
return false;
}
SilentPaymentsScanCache cache = spScanCaches.get(scanAddress.getAddress());
if(cache == null) {
return false;
}
cache.lock();
try {
Integer serverStart = cache.getServerStart();
if(!cache.isCompleted() || serverStart == null) {
return false;
}
int earliestPossibleStart = Network.get() == Network.MAINNET ? TAPROOT_ACTIVATION_HEIGHT : 0;
return serverStart <= earliestPossibleStart;
} finally {
cache.unlock();
}
}
public static Cormorant getCormorant() {
return cormorant;
}
private static void cancelSilentPaymentScans() {
for(SilentPaymentsScanCache cache : spScanCaches.values()) {
cache.lock();
@@ -43,6 +43,10 @@ class SilentPaymentsScanCache {
return state == State.CANCELLED;
}
boolean isCompleted() {
return state == State.COMPLETED;
}
void cancel() {
assert lock.isHeldByCurrentThread();
if(state == State.SCANNING) {
@@ -115,4 +115,8 @@ public class Cormorant {
public static EventBus getEventBus() {
return EVENT_BUS;
}
public BitcoindClient getBitcoindClient() {
return bitcoindClient;
}
}
@@ -77,6 +77,7 @@ public class BitcoindClient {
private final boolean useWallets;
private boolean pruned;
private Integer pruneHeight;
private volatile Date cachedPrunedDate;
private boolean legacyWalletExists;
private final Lock syncingLock = new ReentrantLock();
@@ -285,12 +286,18 @@ public class BitcoindClient {
if(blockchainInfo.pruned()) {
String pruneBlockHash = getBitcoindService().getBlockHash(blockchainInfo.pruneheight());
VerboseBlockHeader pruneBlockHeader = getBitcoindService().getBlockHeader(pruneBlockHash);
return Optional.of(new Date(pruneBlockHeader.time() * 1000));
Date prunedDate = new Date(pruneBlockHeader.time() * 1000);
cachedPrunedDate = prunedDate;
return Optional.of(prunedDate);
}
return Optional.empty();
}
public Date getCachedPrunedDate() {
return cachedPrunedDate;
}
private ScanDate getScanDate(String normalizedDescriptor, Wallet wallet, KeyPurpose keyPurpose, Date earliestBirthDate) {
Integer range = (keyPurpose == null ? null : Math.max(getWalletRange(normalizedDescriptor, wallet, keyPurpose), getDefaultRange(wallet, keyPurpose)));
@@ -228,7 +228,7 @@ public class WalletForm {
boolean shouldHold = !spSubscriptionHeld;
ElectrumServer.SilentPaymentScanService scanService = new ElectrumServer.SilentPaymentScanService(wallet, shouldHold, computeNeededStart(wallet));
ElectrumServer.SilentPaymentScanService scanService = new ElectrumServer.SilentPaymentScanService(wallet, shouldHold, wallet.getNeededScanStart());
scanService.setOnSucceeded(workerStateEvent -> {
spScanInProgress = false;
spSubscriptionHeld = true;
@@ -296,20 +296,6 @@ public class WalletForm {
}
}
private static int computeNeededStart(Wallet wallet) {
Integer stored = wallet.getStoredBlockHeight();
if(stored != null && stored > 0) {
return Math.max(0, stored - BlockTransactionHash.BLOCKS_TO_FULLY_CONFIRM);
}
if(wallet.getBirthHeight() != null) {
return Math.max(0, wallet.getBirthHeight() - BlockTransactionHash.BLOCKS_TO_FULLY_CONFIRM);
}
if(wallet.getBirthDate() != null) {
return (int)(wallet.getBirthDate().getTime() / 1000L);
}
return 0;
}
private void updateWallets(Integer blockHeight, Wallet previousWallet) {
List<WalletNode> nestedHistoryChangedNodes = new ArrayList<>();
for(Wallet childWallet : new ArrayList<>(wallet.getChildWallets())) {