mirror of
https://github.com/sparrowwallet/sparrow.git
synced 2024-12-24 12:46:45 +00:00
support mixing from all single sig wallets, handle tor proxy change, and other minor fixes
This commit is contained in:
parent
a42761981c
commit
88ebef97d4
9 changed files with 90 additions and 52 deletions
|
@ -91,7 +91,7 @@ dependencies {
|
|||
implementation('org.slf4j:jul-to-slf4j:1.7.30') {
|
||||
exclude group: 'org.slf4j'
|
||||
}
|
||||
implementation('com.sparrowwallet.nightjar:nightjar:0.2.13-SNAPSHOT')
|
||||
implementation('com.sparrowwallet.nightjar:nightjar:0.2.16-SNAPSHOT')
|
||||
testImplementation('junit:junit:4.12')
|
||||
}
|
||||
|
||||
|
@ -387,7 +387,7 @@ extraJavaModuleInfo {
|
|||
module('cbor-0.9.jar', 'co.nstant.in.cbor', '0.9') {
|
||||
exports('co.nstant.in.cbor')
|
||||
}
|
||||
module('nightjar-0.2.13-SNAPSHOT.jar', 'com.sparrowwallet.nightjar', '0.2.13-SNAPSHOT') {
|
||||
module('nightjar-0.2.16-SNAPSHOT.jar', 'com.sparrowwallet.nightjar', '0.2.16-SNAPSHOT') {
|
||||
requires('com.google.common')
|
||||
requires('net.sourceforge.streamsupport')
|
||||
requires('org.slf4j')
|
||||
|
|
2
drongo
2
drongo
|
@ -1 +1 @@
|
|||
Subproject commit 94d22b875868760a95222e0254ec10b59c71e04f
|
||||
Subproject commit 0b40c20ab252e29ac192bca34d834b8c3eed04a0
|
|
@ -1738,8 +1738,16 @@ public class AppController implements Initializable {
|
|||
});
|
||||
|
||||
Image image = new Image("image/sparrow-small.png", 50, 50, false, false);
|
||||
String walletName = event.getWallet().getMasterName();
|
||||
if(walletName.length() > 25) {
|
||||
walletName = walletName.substring(0, 25) + "...";
|
||||
}
|
||||
if(!event.getWallet().isMasterWallet()) {
|
||||
walletName += " " + event.getWallet().getName();
|
||||
}
|
||||
|
||||
Notifications notificationBuilder = Notifications.create()
|
||||
.title("Sparrow - " + event.getWallet().getFullName())
|
||||
.title("Sparrow - " + walletName)
|
||||
.text(text)
|
||||
.graphic(new ImageView(image))
|
||||
.hideAfter(Duration.seconds(15))
|
||||
|
|
|
@ -311,39 +311,29 @@ public class UtxosController extends WalletFormController implements Initializab
|
|||
startMix.setDisable(true);
|
||||
stopMix.setDisable(false);
|
||||
|
||||
Whirlpool whirlpool = AppServices.getWhirlpoolServices().getWhirlpool(getWalletForm().getWallet());
|
||||
if(whirlpool != null && !whirlpool.isStarted() && AppServices.isConnected()) {
|
||||
Whirlpool.StartupService startupService = new Whirlpool.StartupService(whirlpool);
|
||||
startupService.setOnFailed(workerStateEvent -> {
|
||||
AppServices.showErrorDialog("Failed to start whirlpool", workerStateEvent.getSource().getException().getMessage());
|
||||
log.error("Failed to start whirlpool", workerStateEvent.getSource().getException());
|
||||
});
|
||||
startupService.start();
|
||||
}
|
||||
|
||||
getWalletForm().getWallet().getMasterMixConfig().setMixOnStartup(Boolean.TRUE);
|
||||
EventManager.get().post(new WalletMasterMixConfigChangedEvent(getWalletForm().getWallet()));
|
||||
|
||||
Whirlpool whirlpool = AppServices.getWhirlpoolServices().getWhirlpool(getWalletForm().getWallet());
|
||||
if(whirlpool != null && !whirlpool.isStarted() && AppServices.isConnected()) {
|
||||
AppServices.getWhirlpoolServices().startWhirlpool(getWalletForm().getWallet(), whirlpool, true);
|
||||
}
|
||||
}
|
||||
|
||||
public void stopMixing(ActionEvent event) {
|
||||
stopMix.setDisable(true);
|
||||
startMix.setDisable(false);
|
||||
|
||||
getWalletForm().getWallet().getMasterMixConfig().setMixOnStartup(Boolean.FALSE);
|
||||
EventManager.get().post(new WalletMasterMixConfigChangedEvent(getWalletForm().getWallet()));
|
||||
|
||||
Whirlpool whirlpool = AppServices.getWhirlpoolServices().getWhirlpool(getWalletForm().getWallet());
|
||||
if(whirlpool.isStarted()) {
|
||||
Whirlpool.ShutdownService shutdownService = new Whirlpool.ShutdownService(whirlpool);
|
||||
shutdownService.setOnFailed(workerStateEvent -> {
|
||||
log.error("Failed to stop whirlpool", workerStateEvent.getSource().getException());
|
||||
AppServices.showErrorDialog("Failed to stop whirlpool", workerStateEvent.getSource().getException().getMessage());
|
||||
});
|
||||
shutdownService.start();
|
||||
AppServices.getWhirlpoolServices().stopWhirlpool(whirlpool, true);
|
||||
} else {
|
||||
//Ensure http clients are shutdown
|
||||
whirlpool.shutdown();
|
||||
}
|
||||
|
||||
getWalletForm().getWallet().getMasterMixConfig().setMixOnStartup(Boolean.FALSE);
|
||||
EventManager.get().post(new WalletMasterMixConfigChangedEvent(getWalletForm().getWallet()));
|
||||
}
|
||||
|
||||
public void showMixToDialog(ActionEvent event) {
|
||||
|
|
|
@ -63,7 +63,6 @@ public class Whirlpool {
|
|||
public static final int DEFAULT_MIXTO_MIN_MIXES = 5;
|
||||
public static final int DEFAULT_MIXTO_RANDOM_FACTOR = 4;
|
||||
|
||||
private final HostAndPort torProxy;
|
||||
private final WhirlpoolServer whirlpoolServer;
|
||||
private final JavaHttpClientService httpClientService;
|
||||
private final JavaStompClientService stompClientService;
|
||||
|
@ -78,19 +77,18 @@ public class Whirlpool {
|
|||
private final BooleanProperty mixingProperty = new SimpleBooleanProperty(false);
|
||||
|
||||
public Whirlpool(Network network, HostAndPort torProxy) {
|
||||
this.torProxy = torProxy;
|
||||
this.whirlpoolServer = WhirlpoolServer.valueOf(network.getName().toUpperCase());
|
||||
this.httpClientService = new JavaHttpClientService(torProxy);
|
||||
this.stompClientService = new JavaStompClientService(httpClientService);
|
||||
this.torClientService = new WhirlpoolTorClientService();
|
||||
|
||||
this.whirlpoolWalletService = new WhirlpoolWalletService();
|
||||
this.config = computeWhirlpoolWalletConfig();
|
||||
this.config = computeWhirlpoolWalletConfig(torProxy);
|
||||
|
||||
WhirlpoolEventService.getInstance().register(this);
|
||||
}
|
||||
|
||||
private WhirlpoolWalletConfig computeWhirlpoolWalletConfig() {
|
||||
private WhirlpoolWalletConfig computeWhirlpoolWalletConfig(HostAndPort torProxy) {
|
||||
DataPersisterFactory dataPersisterFactory = (whirlpoolWallet, bip44w) -> new SparrowDataPersister(whirlpoolWallet);
|
||||
DataSourceFactory dataSourceFactory = (whirlpoolWallet, bip44w, dataPersister) -> new SparrowDataSource(whirlpoolWallet, bip44w, dataPersister);
|
||||
|
||||
|
@ -245,10 +243,6 @@ public class Whirlpool {
|
|||
}
|
||||
}
|
||||
|
||||
public HostAndPort getTorProxy() {
|
||||
return torProxy;
|
||||
}
|
||||
|
||||
public boolean hasWallet() {
|
||||
return hdWallet != null;
|
||||
}
|
||||
|
@ -271,12 +265,14 @@ public class Whirlpool {
|
|||
if(wallet != null) {
|
||||
wallet = getStandardAccountWallet(whirlpoolUtxo.getAccount(), wallet);
|
||||
|
||||
if(wallet != null) {
|
||||
for(BlockTransactionHashIndex utxo : wallet.getWalletUtxos().keySet()) {
|
||||
if(utxo.getHashAsString().equals(whirlpoolUtxo.getUtxo().tx_hash) && utxo.getIndex() == whirlpoolUtxo.getUtxo().tx_output_n) {
|
||||
return new WalletUtxo(wallet, utxo);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
@ -342,6 +338,24 @@ public class Whirlpool {
|
|||
return out;
|
||||
}
|
||||
|
||||
public HostAndPort getTorProxy() {
|
||||
return httpClientService.getTorProxy();
|
||||
}
|
||||
|
||||
public void setTorProxy(HostAndPort torProxy) {
|
||||
if(isStarted()) {
|
||||
throw new IllegalStateException("Cannot set tor proxy on a started Whirlpool");
|
||||
}
|
||||
|
||||
//Ensure all http clients are shutdown first
|
||||
httpClientService.shutdown();
|
||||
|
||||
httpClientService.setTorProxy(torProxy);
|
||||
String serverUrl = whirlpoolServer.getServerUrl(torProxy != null);
|
||||
ServerApi serverApi = new ServerApi(serverUrl, httpClientService);
|
||||
config.setServerApi(serverApi);
|
||||
}
|
||||
|
||||
public String getScode() {
|
||||
return config.getScode();
|
||||
}
|
||||
|
|
|
@ -18,6 +18,7 @@ import org.slf4j.LoggerFactory;
|
|||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.NoSuchElementException;
|
||||
import java.util.Objects;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public class WhirlpoolServices {
|
||||
|
@ -39,7 +40,7 @@ public class WhirlpoolServices {
|
|||
public Whirlpool getWhirlpool(String walletId) {
|
||||
Whirlpool whirlpool = whirlpoolMap.get(walletId);
|
||||
if(whirlpool == null) {
|
||||
HostAndPort torProxy = AppServices.isTorRunning() ? HostAndPort.fromParts("localhost", TorService.PROXY_PORT) : (Config.get().getProxyServer() == null || Config.get().getProxyServer().isEmpty() || !Config.get().isUseProxy() ? null : HostAndPort.fromString(Config.get().getProxyServer()));
|
||||
HostAndPort torProxy = getTorProxy();
|
||||
whirlpool = new Whirlpool(Network.get(), torProxy);
|
||||
whirlpoolMap.put(walletId, whirlpool);
|
||||
}
|
||||
|
@ -47,21 +48,34 @@ public class WhirlpoolServices {
|
|||
return whirlpool;
|
||||
}
|
||||
|
||||
private HostAndPort getTorProxy() {
|
||||
return AppServices.isTorRunning() ?
|
||||
HostAndPort.fromParts("localhost", TorService.PROXY_PORT) :
|
||||
(Config.get().getProxyServer() == null || Config.get().getProxyServer().isEmpty() || !Config.get().isUseProxy() ? null : HostAndPort.fromString(Config.get().getProxyServer()));
|
||||
}
|
||||
|
||||
private void startAllWhirlpool() {
|
||||
for(Map.Entry<String, Whirlpool> entry : whirlpoolMap.entrySet().stream().filter(entry -> entry.getValue().hasWallet() && !entry.getValue().isStarted()).collect(Collectors.toList())) {
|
||||
Wallet wallet = AppServices.get().getWallet(entry.getKey());
|
||||
Whirlpool whirlpool = entry.getValue();
|
||||
startWhirlpool(wallet, whirlpool);
|
||||
startWhirlpool(wallet, whirlpool, false);
|
||||
}
|
||||
}
|
||||
|
||||
private void startWhirlpool(Wallet wallet, Whirlpool whirlpool) {
|
||||
public void startWhirlpool(Wallet wallet, Whirlpool whirlpool, boolean notifyIfMixToMissing) {
|
||||
if(wallet.getMasterMixConfig().getMixOnStartup() != Boolean.FALSE) {
|
||||
HostAndPort torProxy = getTorProxy();
|
||||
if(!Objects.equals(whirlpool.getTorProxy(), torProxy)) {
|
||||
whirlpool.setTorProxy(getTorProxy());
|
||||
}
|
||||
|
||||
try {
|
||||
String mixToWalletId = getWhirlpoolMixToWalletId(wallet.getMasterMixConfig());
|
||||
whirlpool.setMixToWallet(mixToWalletId, wallet.getMasterMixConfig().getMinMixes());
|
||||
} catch(NoSuchElementException e) {
|
||||
AppServices.showWarningDialog("Mix to wallet not open", wallet.getName() + " is configured to mix to " + wallet.getMasterMixConfig().getMixToWalletName() + ", but this wallet is not open. Mix to wallets are required to be open to avoid address reuse.");
|
||||
if(notifyIfMixToMissing) {
|
||||
AppServices.showWarningDialog("Mix to wallet not open", wallet.getMasterName() + " is configured to mix to " + wallet.getMasterMixConfig().getMixToWalletName() + ", but this wallet is not open. Mix to wallets are required to be open to avoid address reuse.");
|
||||
}
|
||||
}
|
||||
|
||||
Whirlpool.StartupService startupService = new Whirlpool.StartupService(whirlpool);
|
||||
|
@ -72,15 +86,22 @@ public class WhirlpoolServices {
|
|||
}
|
||||
}
|
||||
|
||||
private void shutdownAllWhirlpool() {
|
||||
private void stopAllWhirlpool() {
|
||||
for(Whirlpool whirlpool : whirlpoolMap.values().stream().filter(Whirlpool::isStarted).collect(Collectors.toList())) {
|
||||
stopWhirlpool(whirlpool, false);
|
||||
}
|
||||
}
|
||||
|
||||
public void stopWhirlpool(Whirlpool whirlpool, boolean notifyOnFailure) {
|
||||
Whirlpool.ShutdownService shutdownService = new Whirlpool.ShutdownService(whirlpool);
|
||||
shutdownService.setOnFailed(workerStateEvent -> {
|
||||
log.error("Failed to shutdown whirlpool", workerStateEvent.getSource().getException());
|
||||
log.error("Failed to stop whirlpool", workerStateEvent.getSource().getException());
|
||||
if(notifyOnFailure) {
|
||||
AppServices.showErrorDialog("Failed to stop whirlpool", workerStateEvent.getSource().getException().getMessage());
|
||||
}
|
||||
});
|
||||
shutdownService.start();
|
||||
}
|
||||
}
|
||||
|
||||
public String getWhirlpoolMixToWalletId(MixConfig mixConfig) {
|
||||
if(mixConfig == null || mixConfig.getMixToWalletFile() == null || mixConfig.getMixToWalletName() == null) {
|
||||
|
@ -104,7 +125,7 @@ public class WhirlpoolServices {
|
|||
|
||||
@Subscribe
|
||||
public void disconnection(DisconnectionEvent event) {
|
||||
shutdownAllWhirlpool();
|
||||
stopAllWhirlpool();
|
||||
}
|
||||
|
||||
@Subscribe
|
||||
|
@ -112,11 +133,14 @@ public class WhirlpoolServices {
|
|||
String walletId = event.getStorage().getWalletId(event.getWallet());
|
||||
Whirlpool whirlpool = whirlpoolMap.get(walletId);
|
||||
if(whirlpool != null && !whirlpool.isStarted() && AppServices.isConnected()) {
|
||||
startWhirlpool(event.getWallet(), whirlpool);
|
||||
startWhirlpool(event.getWallet(), whirlpool, true);
|
||||
}
|
||||
|
||||
Whirlpool mixFromWhirlpool = whirlpoolMap.entrySet().stream()
|
||||
.filter(entry -> event.getStorage().getWalletFile().equals(AppServices.get().getWallet(entry.getKey()).getMasterMixConfig().getMixToWalletFile()))
|
||||
.filter(entry -> {
|
||||
MixConfig mixConfig = AppServices.get().getWallet(entry.getKey()).getMasterMixConfig();
|
||||
return event.getStorage().getWalletFile().equals(mixConfig.getMixToWalletFile()) && event.getWallet().getName().equals(mixConfig.getMixToWalletName());
|
||||
})
|
||||
.map(Map.Entry::getValue).findFirst().orElse(null);
|
||||
|
||||
if(mixFromWhirlpool != null) {
|
||||
|
|
|
@ -15,7 +15,7 @@ public class SparrowDataPersister implements DataPersister {
|
|||
public SparrowDataPersister(WhirlpoolWallet whirlpoolWallet) throws Exception {
|
||||
WhirlpoolWalletConfig config = whirlpoolWallet.getConfig();
|
||||
String walletIdentifier = whirlpoolWallet.getWalletIdentifier();
|
||||
this.walletStateSupplier = new SparrowWalletStateSupplier(walletIdentifier, config.getExternalDestination());
|
||||
this.walletStateSupplier = new SparrowWalletStateSupplier(walletIdentifier, config);
|
||||
this.utxoConfigSupplier = new UtxoConfigPersistedSupplier(new SparrowUtxoConfigPersister(walletIdentifier));
|
||||
}
|
||||
|
||||
|
|
|
@ -6,6 +6,7 @@ import com.samourai.wallet.hd.Chain;
|
|||
import com.samourai.whirlpool.client.wallet.beans.ExternalDestination;
|
||||
import com.samourai.whirlpool.client.wallet.beans.WhirlpoolAccount;
|
||||
import com.samourai.whirlpool.client.wallet.data.walletState.WalletStateSupplier;
|
||||
import com.samourai.whirlpool.client.whirlpool.WhirlpoolClientConfig;
|
||||
import com.sparrowwallet.drongo.KeyPurpose;
|
||||
import com.sparrowwallet.drongo.crypto.ChildNumber;
|
||||
import com.sparrowwallet.drongo.wallet.MixConfig;
|
||||
|
@ -19,13 +20,13 @@ import java.util.Map;
|
|||
public class SparrowWalletStateSupplier implements WalletStateSupplier {
|
||||
private final String walletId;
|
||||
private final Map<String, IIndexHandler> indexHandlerWallets;
|
||||
private final ExternalDestination externalDestination;
|
||||
private final WhirlpoolClientConfig config;
|
||||
private IIndexHandler externalIndexHandler;
|
||||
|
||||
public SparrowWalletStateSupplier(String walletId, ExternalDestination externalDestination) throws Exception {
|
||||
public SparrowWalletStateSupplier(String walletId, WhirlpoolClientConfig config) throws Exception {
|
||||
this.walletId = walletId;
|
||||
this.indexHandlerWallets = new LinkedHashMap<>();
|
||||
this.externalDestination = externalDestination;
|
||||
this.config = config;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -53,6 +54,7 @@ public class SparrowWalletStateSupplier implements WalletStateSupplier {
|
|||
|
||||
@Override
|
||||
public IIndexHandler getIndexHandlerExternal() {
|
||||
ExternalDestination externalDestination = config.getExternalDestination();
|
||||
if(externalDestination == null) {
|
||||
throw new IllegalStateException("External destination has not been set");
|
||||
}
|
||||
|
|
|
@ -50,7 +50,7 @@
|
|||
<Glyph fontFamily="Font Awesome 5 Free Solid" icon="STOP_CIRCLE" fontSize="12" />
|
||||
</graphic>
|
||||
</Button>
|
||||
<Button fx:id="mixTo" text="Mix to..." onAction="#showMixToDialog" />
|
||||
<Button fx:id="mixTo" text="Mix to..." maxWidth="200" onAction="#showMixToDialog" />
|
||||
</HBox>
|
||||
<Region HBox.hgrow="ALWAYS" />
|
||||
<HBox styleClass="utxos-buttons-box" spacing="20" alignment="BOTTOM_RIGHT">
|
||||
|
|
Loading…
Reference in a new issue