mirror of
https://github.com/sparrowwallet/sparrow.git
synced 2024-12-24 12:46:45 +00:00
add bip47 support for bitcoin core connections
This commit is contained in:
parent
5da9532614
commit
336d0e551b
4 changed files with 74 additions and 19 deletions
|
@ -247,11 +247,14 @@ public class AppServices {
|
||||||
|
|
||||||
private ElectrumServer.ConnectionService createConnectionService() {
|
private ElectrumServer.ConnectionService createConnectionService() {
|
||||||
ElectrumServer.ConnectionService connectionService = new ElectrumServer.ConnectionService();
|
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.setPeriod(Duration.seconds(SERVER_PING_PERIOD_SECS));
|
||||||
connectionService.setRestartOnFailure(true);
|
connectionService.setRestartOnFailure(true);
|
||||||
EventManager.get().register(connectionService);
|
EventManager.get().register(connectionService);
|
||||||
|
|
||||||
connectionService.setOnRunning(workerStateEvent -> {
|
connectionService.setOnRunning(workerStateEvent -> {
|
||||||
|
connectionService.setDelay(Duration.ZERO);
|
||||||
if(!ElectrumServer.isConnected()) {
|
if(!ElectrumServer.isConnected()) {
|
||||||
EventManager.get().post(new ConnectionStartEvent(Config.get().getServerAddress()));
|
EventManager.get().post(new ConnectionStartEvent(Config.get().getServerAddress()));
|
||||||
}
|
}
|
||||||
|
@ -1056,11 +1059,27 @@ public class AppServices {
|
||||||
|
|
||||||
@Subscribe
|
@Subscribe
|
||||||
public void walletOpening(WalletOpeningEvent event) {
|
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) {
|
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();
|
connectionService.cancel();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,6 +8,7 @@ import com.sparrowwallet.drongo.Network;
|
||||||
import com.sparrowwallet.drongo.OutputDescriptor;
|
import com.sparrowwallet.drongo.OutputDescriptor;
|
||||||
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.sparrow.AppServices;
|
import com.sparrowwallet.sparrow.AppServices;
|
||||||
import com.sparrowwallet.sparrow.EventManager;
|
import com.sparrowwallet.sparrow.EventManager;
|
||||||
import com.sparrowwallet.sparrow.event.*;
|
import com.sparrowwallet.sparrow.event.*;
|
||||||
|
@ -31,6 +32,8 @@ public class Bwt {
|
||||||
private static final Logger log = LoggerFactory.getLogger(Bwt.class);
|
private static final Logger log = LoggerFactory.getLogger(Bwt.class);
|
||||||
|
|
||||||
public static final String DEFAULT_CORE_WALLET = "sparrow";
|
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 final int IMPORT_BATCH_SIZE = 350;
|
||||||
private static boolean initialized;
|
private static boolean initialized;
|
||||||
private Long shutdownPtr;
|
private Long shutdownPtr;
|
||||||
|
@ -59,18 +62,35 @@ public class Bwt {
|
||||||
}
|
}
|
||||||
|
|
||||||
private void start(CallbackNotifier callback) {
|
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<Wallet> wallets, CallbackNotifier callback) {
|
private void start(Collection<Wallet> wallets, CallbackNotifier callback) {
|
||||||
List<Wallet> validWallets = wallets.stream().filter(Wallet::isValid).collect(Collectors.toList());
|
List<Wallet> validWallets = wallets.stream().filter(Wallet::isValid).collect(Collectors.toList());
|
||||||
|
|
||||||
Set<String> outputDescriptors = new LinkedHashSet<>();
|
Set<String> outputDescriptors = new LinkedHashSet<>();
|
||||||
|
Set<String> addresses = new LinkedHashSet<>();
|
||||||
for(Wallet wallet : validWallets) {
|
for(Wallet wallet : validWallets) {
|
||||||
OutputDescriptor receiveOutputDescriptor = OutputDescriptor.getOutputDescriptor(wallet, KeyPurpose.RECEIVE);
|
OutputDescriptor receiveOutputDescriptor = OutputDescriptor.getOutputDescriptor(wallet, KeyPurpose.RECEIVE);
|
||||||
outputDescriptors.add(receiveOutputDescriptor.toString(false, false));
|
outputDescriptors.add(receiveOutputDescriptor.toString(false, false));
|
||||||
OutputDescriptor changeOutputDescriptor = OutputDescriptor.getOutputDescriptor(wallet, KeyPurpose.CHANGE);
|
OutputDescriptor changeOutputDescriptor = OutputDescriptor.getOutputDescriptor(wallet, KeyPurpose.CHANGE);
|
||||||
outputDescriptors.add(changeOutputDescriptor.toString(false, false));
|
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);
|
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 gapLimit desired gap limit beyond last used address
|
||||||
* @param callback object receiving notifications
|
* @param callback object receiving notifications
|
||||||
*/
|
*/
|
||||||
private void start(Collection<String> outputDescriptors, Integer rescanSince, Boolean forceRescan, Integer gapLimit, CallbackNotifier callback) {
|
private void start(Collection<String> outputDescriptors, Collection<String> addresses, Integer rescanSince, Boolean forceRescan, Integer gapLimit, CallbackNotifier callback) {
|
||||||
BwtConfig bwtConfig = new BwtConfig();
|
BwtConfig bwtConfig = new BwtConfig();
|
||||||
bwtConfig.network = Network.get() == Network.MAINNET ? "bitcoin" : Network.get().getName();
|
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.rescanSince = (rescanSince == null || rescanSince < 0 ? "now" : rescanSince);
|
||||||
bwtConfig.forceRescan = forceRescan;
|
bwtConfig.forceRescan = forceRescan;
|
||||||
bwtConfig.gapLimit = gapLimit;
|
bwtConfig.gapLimit = gapLimit;
|
||||||
|
|
||||||
|
if(!addresses.isEmpty()) {
|
||||||
|
bwtConfig.addresses = addresses;
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
bwtConfig.requireAddresses = false;
|
bwtConfig.requireAddresses = false;
|
||||||
bwtConfig.bitcoindTimeout = 30;
|
bwtConfig.bitcoindTimeout = 30;
|
||||||
|
@ -115,7 +139,7 @@ public class Bwt {
|
||||||
bwtConfig.setupLogger = false;
|
bwtConfig.setupLogger = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
bwtConfig.electrumAddr = "127.0.0.1:0";
|
bwtConfig.electrumAddr = ELECTRUM_HOST + ":" + ELECTRUM_PORT;
|
||||||
bwtConfig.electrumSkipMerkle = true;
|
bwtConfig.electrumSkipMerkle = true;
|
||||||
|
|
||||||
Config config = Config.get();
|
Config config = Config.get();
|
||||||
|
@ -187,8 +211,8 @@ public class Bwt {
|
||||||
return terminating;
|
return terminating;
|
||||||
}
|
}
|
||||||
|
|
||||||
public ConnectionService getConnectionService(Collection<Wallet> wallets) {
|
public ConnectionService getConnectionService(boolean useWallets) {
|
||||||
return wallets != null ? new ConnectionService(wallets) : new ConnectionService();
|
return new ConnectionService(useWallets);
|
||||||
}
|
}
|
||||||
|
|
||||||
public DisconnectionService getDisconnectionService() {
|
public DisconnectionService getDisconnectionService() {
|
||||||
|
@ -226,6 +250,9 @@ public class Bwt {
|
||||||
@SerializedName("descriptors")
|
@SerializedName("descriptors")
|
||||||
public Collection<String> descriptors;
|
public Collection<String> descriptors;
|
||||||
|
|
||||||
|
@SerializedName("addresses")
|
||||||
|
public Collection<String> addresses;
|
||||||
|
|
||||||
@SerializedName("xpubs")
|
@SerializedName("xpubs")
|
||||||
public String xpubs;
|
public String xpubs;
|
||||||
|
|
||||||
|
@ -261,14 +288,10 @@ public class Bwt {
|
||||||
}
|
}
|
||||||
|
|
||||||
public final class ConnectionService extends Service<Void> {
|
public final class ConnectionService extends Service<Void> {
|
||||||
private final Collection<Wallet> wallets;
|
private final boolean useWallets;
|
||||||
|
|
||||||
public ConnectionService() {
|
public ConnectionService(boolean useWallets) {
|
||||||
this.wallets = null;
|
this.useWallets = useWallets;
|
||||||
}
|
|
||||||
|
|
||||||
public ConnectionService(Collection<Wallet> wallets) {
|
|
||||||
this.wallets = wallets;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -332,10 +355,10 @@ public class Bwt {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
if(wallets == null) {
|
if(!useWallets) {
|
||||||
Bwt.this.start(notifier);
|
Bwt.this.start(notifier);
|
||||||
} else {
|
} else {
|
||||||
Bwt.this.start(wallets, notifier);
|
Bwt.this.start(AppServices.get().getOpenWallets().keySet(), notifier);
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
|
|
|
@ -78,6 +78,9 @@ public class ElectrumServer {
|
||||||
throw new ServerConfigException("Could not connect to Bitcoin Core RPC");
|
throw new ServerConfigException("Could not connect to Bitcoin Core RPC");
|
||||||
}
|
}
|
||||||
electrumServer = bwtElectrumServer;
|
electrumServer = bwtElectrumServer;
|
||||||
|
if(previousServerAddress != null && previousServerAddress.contains(Bwt.ELECTRUM_HOST)) {
|
||||||
|
previousServerAddress = bwtElectrumServer;
|
||||||
|
}
|
||||||
} else if(Config.get().getServerType() == ServerType.ELECTRUM_SERVER) {
|
} else if(Config.get().getServerType() == ServerType.ELECTRUM_SERVER) {
|
||||||
electrumServer = Config.get().getElectrumServer();
|
electrumServer = Config.get().getElectrumServer();
|
||||||
electrumServerCert = Config.get().getElectrumServerCert();
|
electrumServerCert = Config.get().getElectrumServerCert();
|
||||||
|
@ -1062,7 +1065,7 @@ public class ElectrumServer {
|
||||||
Bwt.initialize();
|
Bwt.initialize();
|
||||||
|
|
||||||
if(!bwt.isRunning()) {
|
if(!bwt.isRunning()) {
|
||||||
Bwt.ConnectionService bwtConnectionService = bwt.getConnectionService(subscribe ? AppServices.get().getOpenWallets().keySet() : null);
|
Bwt.ConnectionService bwtConnectionService = bwt.getConnectionService(subscribe);
|
||||||
bwtStartException = null;
|
bwtStartException = null;
|
||||||
bwtConnectionService.setOnFailed(workerStateEvent -> {
|
bwtConnectionService.setOnFailed(workerStateEvent -> {
|
||||||
log.error("Failed to start BWT", workerStateEvent.getSource().getException());
|
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();
|
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() {
|
public boolean isConnected() {
|
||||||
return isRunning() && (Config.get().getServerType() != ServerType.BITCOIN_CORE || (bwt.isRunning() && bwt.isReady()));
|
return isRunning() && (Config.get().getServerType() != ServerType.BITCOIN_CORE || (bwt.isRunning() && bwt.isReady()));
|
||||||
}
|
}
|
||||||
|
|
|
@ -410,10 +410,11 @@ public class PayNymController {
|
||||||
byte[] opReturnData = PaymentCode.getOpReturnData(blockTransaction.getTransaction());
|
byte[] opReturnData = PaymentCode.getOpReturnData(blockTransaction.getTransaction());
|
||||||
if(Arrays.equals(opReturnData, blindedPaymentCode)) {
|
if(Arrays.equals(opReturnData, blindedPaymentCode)) {
|
||||||
addedWallets.addAll(addChildWallets(payNym, externalPaymentCode));
|
addedWallets.addAll(addChildWallets(payNym, externalPaymentCode));
|
||||||
|
blockTransaction.setLabel("Link " + payNym.nymName());
|
||||||
} else {
|
} else {
|
||||||
blockTransaction.setLabel(INVALID_PAYMENT_CODE_LABEL);
|
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) {
|
} catch(Exception e) {
|
||||||
log.error("Error adding linked contact from notification transaction", e);
|
log.error("Error adding linked contact from notification transaction", e);
|
||||||
}
|
}
|
||||||
|
@ -641,6 +642,11 @@ public class PayNymController {
|
||||||
if(!changedLabelEntries.isEmpty()) {
|
if(!changedLabelEntries.isEmpty()) {
|
||||||
Platform.runLater(() -> EventManager.get().post(new WalletEntryLabelsChangedEvent(event.getWallet(), changedLabelEntries)));
|
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<T> extends MultipleSelectionModel<T> {
|
public static class NoSelectionModel<T> extends MultipleSelectionModel<T> {
|
||||||
|
|
Loading…
Reference in a new issue