From 336d0e551b049a6b6091a455fda5977dc92e5d8e Mon Sep 17 00:00:00 2001 From: Craig Raw Date: Mon, 6 Jun 2022 13:39:38 +0200 Subject: [PATCH] add bip47 support for bitcoin core connections --- .../sparrowwallet/sparrow/AppServices.java | 23 +++++++- .../com/sparrowwallet/sparrow/net/Bwt.java | 53 +++++++++++++------ .../sparrow/net/ElectrumServer.java | 9 +++- .../sparrow/paynym/PayNymController.java | 8 ++- 4 files changed, 74 insertions(+), 19 deletions(-) diff --git a/src/main/java/com/sparrowwallet/sparrow/AppServices.java b/src/main/java/com/sparrowwallet/sparrow/AppServices.java index 64898597..5e8c0552 100644 --- a/src/main/java/com/sparrowwallet/sparrow/AppServices.java +++ b/src/main/java/com/sparrowwallet/sparrow/AppServices.java @@ -247,11 +247,14 @@ public class AppServices { private ElectrumServer.ConnectionService createConnectionService() { ElectrumServer.ConnectionService connectionService = new ElectrumServer.ConnectionService(); + //Delay startup on first connection to Bitcoin Core to allow any unencrypted wallets to open first + connectionService.setDelay(Config.get().getServerType() == ServerType.BITCOIN_CORE ? Duration.seconds(2) : Duration.ZERO); connectionService.setPeriod(Duration.seconds(SERVER_PING_PERIOD_SECS)); connectionService.setRestartOnFailure(true); EventManager.get().register(connectionService); connectionService.setOnRunning(workerStateEvent -> { + connectionService.setDelay(Duration.ZERO); if(!ElectrumServer.isConnected()) { EventManager.get().post(new ConnectionStartEvent(Config.get().getServerAddress())); } @@ -1056,11 +1059,27 @@ public class AppServices { @Subscribe public void walletOpening(WalletOpeningEvent event) { - restartBwt(event.getWallet()); + if(Config.get().getServerType() == ServerType.BITCOIN_CORE) { + Platform.runLater(() -> restartBwt(event.getWallet())); + } + } + + @Subscribe + public void childWalletsAdded(ChildWalletsAddedEvent event) { + if(event.getChildWallets().stream().anyMatch(Wallet::isNested)) { + restartBwt(event.getWallet()); + } + } + + @Subscribe + public void walletHistoryChanged(WalletHistoryChangedEvent event) { + if(Config.get().getServerType() == ServerType.BITCOIN_CORE && event.getNestedHistoryChangedNodes().stream().anyMatch(node -> node.getTransactionOutputs().isEmpty())) { + Platform.runLater(() -> restartBwt(event.getWallet())); + } } private void restartBwt(Wallet wallet) { - if(Config.get().getServerType() == ServerType.BITCOIN_CORE && isConnected() && wallet.isValid()) { + if(Config.get().getServerType() == ServerType.BITCOIN_CORE && connectionService != null && connectionService.isConnectionRunning() && wallet.isValid()) { connectionService.cancel(); } } diff --git a/src/main/java/com/sparrowwallet/sparrow/net/Bwt.java b/src/main/java/com/sparrowwallet/sparrow/net/Bwt.java index 3e91c043..6b25bfae 100644 --- a/src/main/java/com/sparrowwallet/sparrow/net/Bwt.java +++ b/src/main/java/com/sparrowwallet/sparrow/net/Bwt.java @@ -8,6 +8,7 @@ import com.sparrowwallet.drongo.Network; import com.sparrowwallet.drongo.OutputDescriptor; import com.sparrowwallet.drongo.wallet.BlockTransactionHash; import com.sparrowwallet.drongo.wallet.Wallet; +import com.sparrowwallet.drongo.wallet.WalletNode; import com.sparrowwallet.sparrow.AppServices; import com.sparrowwallet.sparrow.EventManager; import com.sparrowwallet.sparrow.event.*; @@ -31,6 +32,8 @@ public class Bwt { private static final Logger log = LoggerFactory.getLogger(Bwt.class); public static final String DEFAULT_CORE_WALLET = "sparrow"; + public static final String ELECTRUM_HOST = "127.0.0.1"; + public static final String ELECTRUM_PORT = "0"; private static final int IMPORT_BATCH_SIZE = 350; private static boolean initialized; private Long shutdownPtr; @@ -59,18 +62,35 @@ public class Bwt { } private void start(CallbackNotifier callback) { - start(Collections.emptyList(), null, null, null, callback); + start(Collections.emptyList(), Collections.emptyList(), null, null, null, callback); } private void start(Collection wallets, CallbackNotifier callback) { List validWallets = wallets.stream().filter(Wallet::isValid).collect(Collectors.toList()); Set outputDescriptors = new LinkedHashSet<>(); + Set addresses = new LinkedHashSet<>(); for(Wallet wallet : validWallets) { OutputDescriptor receiveOutputDescriptor = OutputDescriptor.getOutputDescriptor(wallet, KeyPurpose.RECEIVE); outputDescriptors.add(receiveOutputDescriptor.toString(false, false)); OutputDescriptor changeOutputDescriptor = OutputDescriptor.getOutputDescriptor(wallet, KeyPurpose.CHANGE); outputDescriptors.add(changeOutputDescriptor.toString(false, false)); + + if(wallet.isMasterWallet() && wallet.hasPaymentCode()) { + Wallet notificationWallet = wallet.getNotificationWallet(); + WalletNode notificationNode = notificationWallet.getNode(KeyPurpose.NOTIFICATION); + addresses.add(notificationNode.getAddress().toString()); + + for(Wallet childWallet : wallet.getChildWallets()) { + if(childWallet.isNested()) { + for(KeyPurpose keyPurpose : KeyPurpose.DEFAULT_PURPOSES) { + for(WalletNode addressNode : childWallet.getNode(keyPurpose).getChildren()) { + addresses.add(addressNode.getAddress().toString()); + } + } + } + } + } } int rescanSince = validWallets.stream().filter(wallet -> wallet.getBirthDate() != null).mapToInt(wallet -> (int)(wallet.getBirthDate().getTime() / 1000)).min().orElse(-1); @@ -84,7 +104,7 @@ public class Bwt { } } - start(outputDescriptors, rescanSince, forceRescan, gapLimit, callback); + start(outputDescriptors, addresses, rescanSince, forceRescan, gapLimit, callback); } /** @@ -96,7 +116,7 @@ public class Bwt { * @param gapLimit desired gap limit beyond last used address * @param callback object receiving notifications */ - private void start(Collection outputDescriptors, Integer rescanSince, Boolean forceRescan, Integer gapLimit, CallbackNotifier callback) { + private void start(Collection outputDescriptors, Collection addresses, Integer rescanSince, Boolean forceRescan, Integer gapLimit, CallbackNotifier callback) { BwtConfig bwtConfig = new BwtConfig(); bwtConfig.network = Network.get() == Network.MAINNET ? "bitcoin" : Network.get().getName(); @@ -105,6 +125,10 @@ public class Bwt { bwtConfig.rescanSince = (rescanSince == null || rescanSince < 0 ? "now" : rescanSince); bwtConfig.forceRescan = forceRescan; bwtConfig.gapLimit = gapLimit; + + if(!addresses.isEmpty()) { + bwtConfig.addresses = addresses; + } } else { bwtConfig.requireAddresses = false; bwtConfig.bitcoindTimeout = 30; @@ -115,7 +139,7 @@ public class Bwt { bwtConfig.setupLogger = false; } - bwtConfig.electrumAddr = "127.0.0.1:0"; + bwtConfig.electrumAddr = ELECTRUM_HOST + ":" + ELECTRUM_PORT; bwtConfig.electrumSkipMerkle = true; Config config = Config.get(); @@ -187,8 +211,8 @@ public class Bwt { return terminating; } - public ConnectionService getConnectionService(Collection wallets) { - return wallets != null ? new ConnectionService(wallets) : new ConnectionService(); + public ConnectionService getConnectionService(boolean useWallets) { + return new ConnectionService(useWallets); } public DisconnectionService getDisconnectionService() { @@ -226,6 +250,9 @@ public class Bwt { @SerializedName("descriptors") public Collection descriptors; + @SerializedName("addresses") + public Collection addresses; + @SerializedName("xpubs") public String xpubs; @@ -261,14 +288,10 @@ public class Bwt { } public final class ConnectionService extends Service { - private final Collection wallets; + private final boolean useWallets; - public ConnectionService() { - this.wallets = null; - } - - public ConnectionService(Collection wallets) { - this.wallets = wallets; + public ConnectionService(boolean useWallets) { + this.useWallets = useWallets; } @Override @@ -332,10 +355,10 @@ public class Bwt { } }; - if(wallets == null) { + if(!useWallets) { Bwt.this.start(notifier); } else { - Bwt.this.start(wallets, notifier); + Bwt.this.start(AppServices.get().getOpenWallets().keySet(), notifier); } return null; diff --git a/src/main/java/com/sparrowwallet/sparrow/net/ElectrumServer.java b/src/main/java/com/sparrowwallet/sparrow/net/ElectrumServer.java index 1cdc77e6..5181a5e4 100644 --- a/src/main/java/com/sparrowwallet/sparrow/net/ElectrumServer.java +++ b/src/main/java/com/sparrowwallet/sparrow/net/ElectrumServer.java @@ -78,6 +78,9 @@ public class ElectrumServer { throw new ServerConfigException("Could not connect to Bitcoin Core RPC"); } electrumServer = bwtElectrumServer; + if(previousServerAddress != null && previousServerAddress.contains(Bwt.ELECTRUM_HOST)) { + previousServerAddress = bwtElectrumServer; + } } else if(Config.get().getServerType() == ServerType.ELECTRUM_SERVER) { electrumServer = Config.get().getElectrumServer(); electrumServerCert = Config.get().getElectrumServerCert(); @@ -1062,7 +1065,7 @@ public class ElectrumServer { Bwt.initialize(); if(!bwt.isRunning()) { - Bwt.ConnectionService bwtConnectionService = bwt.getConnectionService(subscribe ? AppServices.get().getOpenWallets().keySet() : null); + Bwt.ConnectionService bwtConnectionService = bwt.getConnectionService(subscribe); bwtStartException = null; bwtConnectionService.setOnFailed(workerStateEvent -> { log.error("Failed to start BWT", workerStateEvent.getSource().getException()); @@ -1176,6 +1179,10 @@ public class ElectrumServer { return isRunning() && Config.get().getServerType() == ServerType.BITCOIN_CORE && bwt.isRunning() && !bwt.isReady(); } + public boolean isConnectionRunning() { + return isRunning() && (Config.get().getServerType() != ServerType.BITCOIN_CORE || bwt.isRunning()); + } + public boolean isConnected() { return isRunning() && (Config.get().getServerType() != ServerType.BITCOIN_CORE || (bwt.isRunning() && bwt.isReady())); } diff --git a/src/main/java/com/sparrowwallet/sparrow/paynym/PayNymController.java b/src/main/java/com/sparrowwallet/sparrow/paynym/PayNymController.java index 2a888cb0..8225b84c 100644 --- a/src/main/java/com/sparrowwallet/sparrow/paynym/PayNymController.java +++ b/src/main/java/com/sparrowwallet/sparrow/paynym/PayNymController.java @@ -410,10 +410,11 @@ public class PayNymController { byte[] opReturnData = PaymentCode.getOpReturnData(blockTransaction.getTransaction()); if(Arrays.equals(opReturnData, blindedPaymentCode)) { addedWallets.addAll(addChildWallets(payNym, externalPaymentCode)); + blockTransaction.setLabel("Link " + payNym.nymName()); } else { blockTransaction.setLabel(INVALID_PAYMENT_CODE_LABEL); - EventManager.get().post(new WalletEntryLabelsChangedEvent(input0Node.getWallet(), new TransactionEntry(input0Node.getWallet(), blockTransaction, Collections.emptyMap(), Collections.emptyMap()))); } + EventManager.get().post(new WalletEntryLabelsChangedEvent(input0Node.getWallet(), new TransactionEntry(input0Node.getWallet(), blockTransaction, Collections.emptyMap(), Collections.emptyMap()))); } catch(Exception e) { log.error("Error adding linked contact from notification transaction", e); } @@ -641,6 +642,11 @@ public class PayNymController { if(!changedLabelEntries.isEmpty()) { Platform.runLater(() -> EventManager.get().post(new WalletEntryLabelsChangedEvent(event.getWallet(), changedLabelEntries))); } + + if(walletPayNym != null) { + //If we have just linked a PayNym wallet that paid for another notification transaction, attempt to link + Platform.runLater(() -> addWalletIfNotificationTransactionPresent(walletPayNym.following())); + } } public static class NoSelectionModel extends MultipleSelectionModel {