mirror of
https://github.com/sparrowwallet/sparrow.git
synced 2024-12-25 21:26:43 +00:00
various updates and fixes
This commit is contained in:
parent
da74089969
commit
c9d1650ed4
25 changed files with 261 additions and 205 deletions
|
@ -124,8 +124,8 @@ dependencies {
|
||||||
exclude group: 'org.slf4j'
|
exclude group: 'org.slf4j'
|
||||||
}
|
}
|
||||||
implementation('com.sparrowwallet.bokmakierie:bokmakierie:1.0')
|
implementation('com.sparrowwallet.bokmakierie:bokmakierie:1.0')
|
||||||
implementation('io.samourai.code.whirlpool:whirlpool-client:1.0.0-beta10')
|
implementation('io.samourai.code.whirlpool:whirlpool-client:1.0.0-beta13')
|
||||||
implementation('io.samourai.code.wallet:java-http-client:2.0.0-beta3')
|
implementation('io.samourai.code.wallet:java-http-client:2.0.0-beta4')
|
||||||
implementation('io.reactivex.rxjava2:rxjava:2.2.15')
|
implementation('io.reactivex.rxjava2:rxjava:2.2.15')
|
||||||
implementation('io.reactivex.rxjava2:rxjavafx:2.2.2')
|
implementation('io.reactivex.rxjava2:rxjavafx:2.2.2')
|
||||||
implementation('org.apache.commons:commons-lang3:3.7')
|
implementation('org.apache.commons:commons-lang3:3.7')
|
||||||
|
@ -490,7 +490,6 @@ extraJavaModuleInfo {
|
||||||
exports('co.nstant.in.cbor.model')
|
exports('co.nstant.in.cbor.model')
|
||||||
exports('co.nstant.in.cbor.builder')
|
exports('co.nstant.in.cbor.builder')
|
||||||
}
|
}
|
||||||
// begin samourai dependencies
|
|
||||||
module('commons-codec-1.10.jar', 'commons.codec', '1.10') {
|
module('commons-codec-1.10.jar', 'commons.codec', '1.10') {
|
||||||
exports('org.apache.commons.codec')
|
exports('org.apache.commons.codec')
|
||||||
}
|
}
|
||||||
|
@ -507,7 +506,6 @@ extraJavaModuleInfo {
|
||||||
exports('com.lambdaworks.codec')
|
exports('com.lambdaworks.codec')
|
||||||
exports('com.lambdaworks.crypto')
|
exports('com.lambdaworks.crypto')
|
||||||
}
|
}
|
||||||
// end samourai dependencies
|
|
||||||
module('okio-1.6.0.jar', 'com.squareup.okio', '1.6.0') {
|
module('okio-1.6.0.jar', 'com.squareup.okio', '1.6.0') {
|
||||||
exports('okio')
|
exports('okio')
|
||||||
}
|
}
|
||||||
|
|
2
drongo
2
drongo
|
@ -1 +1 @@
|
||||||
Subproject commit c8165e154a4088262cdf9428f8f8a6ef95db5140
|
Subproject commit 3b8435ca37d00d370d859fa9dbc1631d3cdcae45
|
|
@ -70,7 +70,7 @@ public class MixStatusCell extends TreeTableCell<Entry, UtxoEntry.MixStatus> {
|
||||||
}
|
}
|
||||||
|
|
||||||
private void setMixFail(MixFailReason mixFailReason, String mixError, Long mixErrorTimestamp) {
|
private void setMixFail(MixFailReason mixFailReason, String mixError, Long mixErrorTimestamp) {
|
||||||
if(mixFailReason != MixFailReason.CANCEL) {
|
if(mixFailReason != MixFailReason.STOP_UTXO && !mixFailReason.isSilent()) {
|
||||||
long elapsed = mixErrorTimestamp == null ? 0L : System.currentTimeMillis() - mixErrorTimestamp;
|
long elapsed = mixErrorTimestamp == null ? 0L : System.currentTimeMillis() - mixErrorTimestamp;
|
||||||
if(elapsed >= ERROR_DISPLAY_MILLIS) {
|
if(elapsed >= ERROR_DISPLAY_MILLIS) {
|
||||||
//Old error, don't set again.
|
//Old error, don't set again.
|
||||||
|
@ -116,24 +116,10 @@ public class MixStatusCell extends TreeTableCell<Entry, UtxoEntry.MixStatus> {
|
||||||
progressIndicator.setProgress(mixProgress.getMixStep().getProgressPercent() == 100 ? -1 : mixProgress.getMixStep().getProgressPercent() / 100.0);
|
progressIndicator.setProgress(mixProgress.getMixStep().getProgressPercent() == 100 ? -1 : mixProgress.getMixStep().getProgressPercent() / 100.0);
|
||||||
setGraphic(progressIndicator);
|
setGraphic(progressIndicator);
|
||||||
Tooltip tt = new Tooltip();
|
Tooltip tt = new Tooltip();
|
||||||
String status = mixProgress.getMixStep().getMessage().substring(0, 1).toUpperCase(Locale.ROOT) + mixProgress.getMixStep().getMessage().substring(1);
|
String status = mixProgress.getMixStep().getMessage().replaceAll("_", " ");
|
||||||
|
status = status.substring(0, 1).toUpperCase(Locale.ROOT) + status.substring(1).toLowerCase(Locale.ROOT);
|
||||||
tt.setText(status);
|
tt.setText(status);
|
||||||
setTooltip(tt);
|
setTooltip(tt);
|
||||||
|
|
||||||
// TODO nbRegisteredInputs is not available anymore
|
|
||||||
/*
|
|
||||||
if(mixProgress.getMixStep() == MixStep.REGISTERED_INPUT) {
|
|
||||||
tt.setOnShowing(event -> {
|
|
||||||
Whirlpool whirlpool = AppServices.getWhirlpoolServices().getWhirlpool(utxoEntry.getWallet());
|
|
||||||
Whirlpool.RegisteredInputsService registeredInputsService = new Whirlpool.RegisteredInputsService(whirlpool, mixProgress.getPoolId());
|
|
||||||
registeredInputsService.setOnSucceeded(eventStateHandler -> {
|
|
||||||
if(registeredInputsService.getValue() != null) {
|
|
||||||
tt.setText(status + " (1 of " + registeredInputsService.getValue() + ")");
|
|
||||||
}
|
|
||||||
});
|
|
||||||
registeredInputsService.start();
|
|
||||||
});
|
|
||||||
}*/
|
|
||||||
} else {
|
} else {
|
||||||
setGraphic(null);
|
setGraphic(null);
|
||||||
setTooltip(null);
|
setTooltip(null);
|
||||||
|
|
|
@ -256,8 +256,11 @@ public class DbPersistence implements Persistence {
|
||||||
}
|
}
|
||||||
for(Sha256Hash txid : referencedTxIds) {
|
for(Sha256Hash txid : referencedTxIds) {
|
||||||
BlockTransaction blkTx = wallet.getTransactions().get(txid);
|
BlockTransaction blkTx = wallet.getTransactions().get(txid);
|
||||||
|
//May be null for a nested wallet if still updating
|
||||||
|
if(blkTx != null) {
|
||||||
blockTransactionDao.addOrUpdate(wallet, txid, blkTx);
|
blockTransactionDao.addOrUpdate(wallet, txid, blkTx);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
if(!dirtyPersistables.clearHistory) {
|
if(!dirtyPersistables.clearHistory) {
|
||||||
DetachedLabelDao detachedLabelDao = handle.attach(DetachedLabelDao.class);
|
DetachedLabelDao detachedLabelDao = handle.attach(DetachedLabelDao.class);
|
||||||
detachedLabelDao.clearAndAddAll(wallet);
|
detachedLabelDao.clearAndAddAll(wallet);
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
package com.sparrowwallet.sparrow.net;
|
package com.sparrowwallet.sparrow.net;
|
||||||
|
|
||||||
import com.google.common.net.HostAndPort;
|
import com.google.common.net.HostAndPort;
|
||||||
import com.samourai.wallet.api.backend.beans.HttpException;
|
import com.samourai.wallet.httpClient.HttpResponseException;
|
||||||
import com.sparrowwallet.drongo.Network;
|
import com.sparrowwallet.drongo.Network;
|
||||||
import com.sparrowwallet.drongo.Utils;
|
import com.sparrowwallet.drongo.Utils;
|
||||||
import com.sparrowwallet.drongo.protocol.Sha256Hash;
|
import com.sparrowwallet.drongo.protocol.Sha256Hash;
|
||||||
|
@ -158,7 +158,7 @@ public enum BroadcastSource {
|
||||||
} catch(Exception e) {
|
} catch(Exception e) {
|
||||||
throw new BroadcastException("Could not retrieve txid from broadcast, server returned: " + response);
|
throw new BroadcastException("Could not retrieve txid from broadcast, server returned: " + response);
|
||||||
}
|
}
|
||||||
} catch(HttpException e) {
|
} catch(HttpResponseException e) {
|
||||||
throw new BroadcastException("Could not broadcast transaction, server returned " + e.getStatusCode() + ": " + e.getResponseBody());
|
throw new BroadcastException("Could not broadcast transaction, server returned " + e.getStatusCode() + ": " + e.getResponseBody());
|
||||||
} catch(Exception e) {
|
} catch(Exception e) {
|
||||||
log.error("Could not post transaction via " + getName(), e);
|
log.error("Could not post transaction via " + getName(), e);
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
package com.sparrowwallet.sparrow.net;
|
package com.sparrowwallet.sparrow.net;
|
||||||
|
|
||||||
import com.samourai.wallet.api.backend.beans.HttpException;
|
import com.samourai.wallet.httpClient.HttpResponseException;
|
||||||
import com.sparrowwallet.sparrow.AppServices;
|
import com.sparrowwallet.sparrow.AppServices;
|
||||||
import com.sparrowwallet.sparrow.event.ExchangeRatesUpdatedEvent;
|
import com.sparrowwallet.sparrow.event.ExchangeRatesUpdatedEvent;
|
||||||
import javafx.concurrent.ScheduledService;
|
import javafx.concurrent.ScheduledService;
|
||||||
|
@ -107,7 +107,7 @@ public enum ExchangeSource {
|
||||||
if(log.isDebugEnabled()) {
|
if(log.isDebugEnabled()) {
|
||||||
log.warn("Error retrieving historical currency rates", e);
|
log.warn("Error retrieving historical currency rates", e);
|
||||||
} else {
|
} else {
|
||||||
if(e instanceof HttpException httpException && httpException.getStatusCode() == 404) {
|
if(e instanceof HttpResponseException httpException && httpException.getStatusCode() == 404) {
|
||||||
log.warn("Error retrieving historical currency rates (" + e.getMessage() + "). BTC-" + currency.getCurrencyCode() + " may not be supported by " + this);
|
log.warn("Error retrieving historical currency rates (" + e.getMessage() + "). BTC-" + currency.getCurrencyCode() + " may not be supported by " + this);
|
||||||
} else {
|
} else {
|
||||||
log.warn("Error retrieving historical currency rates (" + e.getMessage() + ")");
|
log.warn("Error retrieving historical currency rates (" + e.getMessage() + ")");
|
||||||
|
|
|
@ -5,6 +5,7 @@ import com.samourai.http.client.JettyHttpClientService;
|
||||||
import com.samourai.wallet.httpClient.HttpUsage;
|
import com.samourai.wallet.httpClient.HttpUsage;
|
||||||
import com.samourai.wallet.httpClient.IHttpClient;
|
import com.samourai.wallet.httpClient.IHttpClient;
|
||||||
import com.samourai.wallet.util.AsyncUtil;
|
import com.samourai.wallet.util.AsyncUtil;
|
||||||
|
import com.samourai.wallet.util.ThreadUtil;
|
||||||
import com.samourai.whirlpool.client.utils.ClientUtils;
|
import com.samourai.whirlpool.client.utils.ClientUtils;
|
||||||
import io.reactivex.Observable;
|
import io.reactivex.Observable;
|
||||||
import javafx.concurrent.Service;
|
import javafx.concurrent.Service;
|
||||||
|
@ -17,25 +18,20 @@ public class HttpClientService extends JettyHttpClientService {
|
||||||
private static final int REQUEST_TIMEOUT = 120000;
|
private static final int REQUEST_TIMEOUT = 120000;
|
||||||
|
|
||||||
public HttpClientService(HostAndPort torProxy) {
|
public HttpClientService(HostAndPort torProxy) {
|
||||||
super(REQUEST_TIMEOUT,
|
super(REQUEST_TIMEOUT, ClientUtils.USER_AGENT, new HttpProxySupplier(torProxy));
|
||||||
ClientUtils.USER_AGENT,
|
|
||||||
new HttpProxySupplier(torProxy));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public <T> T requestJson(String url, Class<T> responseType, Map<String, String> headers) throws Exception {
|
public <T> T requestJson(String url, Class<T> responseType, Map<String, String> headers) throws Exception {
|
||||||
return getHttpClient(HttpUsage.BACKEND)
|
return getHttpClient(HttpUsage.BACKEND).getJson(url, responseType, headers);
|
||||||
.getJson(url, responseType, headers);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public <T> Observable<Optional<T>> postJson(String url, Class<T> responseType, Map<String, String> headers, Object body) {
|
public <T> Observable<Optional<T>> postJson(String url, Class<T> responseType, Map<String, String> headers, Object body) {
|
||||||
return getHttpClient(HttpUsage.BACKEND)
|
return getHttpClient(HttpUsage.BACKEND).postJson(url, responseType, headers, body).toObservable();
|
||||||
.postJson(url, responseType, headers, body).toObservable();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public String postString(String url, Map<String, String> headers, String contentType, String content) throws Exception {
|
public String postString(String url, Map<String, String> headers, String contentType, String content) throws Exception {
|
||||||
IHttpClient httpClient = getHttpClient(HttpUsage.BACKEND);
|
IHttpClient httpClient = getHttpClient(HttpUsage.BACKEND);
|
||||||
return AsyncUtil.getInstance().blockingGet(
|
return AsyncUtil.getInstance().blockingGet(httpClient.postString(url, headers, contentType, content)).get();
|
||||||
httpClient.postString(url, headers, contentType, content)).get();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public HostAndPort getTorProxy() {
|
public HostAndPort getTorProxy() {
|
||||||
|
@ -64,6 +60,7 @@ public class HttpClientService extends JettyHttpClientService {
|
||||||
protected Task<Boolean> createTask() {
|
protected Task<Boolean> createTask() {
|
||||||
return new Task<>() {
|
return new Task<>() {
|
||||||
protected Boolean call() throws Exception {
|
protected Boolean call() throws Exception {
|
||||||
|
ThreadUtil.getInstance().getExecutorService().shutdown();
|
||||||
httpClientService.stop();
|
httpClientService.stop();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,7 +21,7 @@ public class HttpProxySupplier implements IHttpProxySupplier {
|
||||||
if (hostAndPort == null) {
|
if (hostAndPort == null) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
// TODO verify
|
|
||||||
return new HttpProxy(HttpProxyProtocol.SOCKS, hostAndPort.getHost(), hostAndPort.getPort());
|
return new HttpProxy(HttpProxyProtocol.SOCKS, hostAndPort.getHost(), hostAndPort.getPort());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -2,7 +2,7 @@ package com.sparrowwallet.sparrow.payjoin;
|
||||||
|
|
||||||
import com.google.common.collect.ImmutableMap;
|
import com.google.common.collect.ImmutableMap;
|
||||||
import com.google.gson.Gson;
|
import com.google.gson.Gson;
|
||||||
import com.samourai.wallet.api.backend.beans.HttpException;
|
import com.samourai.wallet.httpClient.HttpResponseException;
|
||||||
import com.sparrowwallet.drongo.protocol.Script;
|
import com.sparrowwallet.drongo.protocol.Script;
|
||||||
import com.sparrowwallet.drongo.protocol.Transaction;
|
import com.sparrowwallet.drongo.protocol.Transaction;
|
||||||
import com.sparrowwallet.drongo.protocol.TransactionInput;
|
import com.sparrowwallet.drongo.protocol.TransactionInput;
|
||||||
|
@ -87,7 +87,7 @@ public class Payjoin {
|
||||||
checkProposal(psbt, proposalPsbt, changeOutputIndex, maxAdditionalFeeContribution, allowOutputSubstitution);
|
checkProposal(psbt, proposalPsbt, changeOutputIndex, maxAdditionalFeeContribution, allowOutputSubstitution);
|
||||||
|
|
||||||
return proposalPsbt;
|
return proposalPsbt;
|
||||||
} catch(HttpException e) {
|
} catch(HttpResponseException e) {
|
||||||
Gson gson = new Gson();
|
Gson gson = new Gson();
|
||||||
PayjoinReceiverError payjoinReceiverError = gson.fromJson(e.getResponseBody(), PayjoinReceiverError.class);
|
PayjoinReceiverError payjoinReceiverError = gson.fromJson(e.getResponseBody(), PayjoinReceiverError.class);
|
||||||
log.warn("Payjoin receiver returned an error of " + payjoinReceiverError.getErrorCode() + " (" + payjoinReceiverError.getMessage() + ")");
|
log.warn("Payjoin receiver returned an error of " + payjoinReceiverError.getErrorCode() + " (" + payjoinReceiverError.getMessage() + ")");
|
||||||
|
|
|
@ -25,6 +25,8 @@ import javafx.application.Platform;
|
||||||
import javafx.beans.property.ObjectProperty;
|
import javafx.beans.property.ObjectProperty;
|
||||||
import javafx.beans.property.SimpleObjectProperty;
|
import javafx.beans.property.SimpleObjectProperty;
|
||||||
import javafx.collections.FXCollections;
|
import javafx.collections.FXCollections;
|
||||||
|
import javafx.concurrent.Service;
|
||||||
|
import javafx.concurrent.Task;
|
||||||
import javafx.event.ActionEvent;
|
import javafx.event.ActionEvent;
|
||||||
import javafx.fxml.FXML;
|
import javafx.fxml.FXML;
|
||||||
import javafx.scene.control.*;
|
import javafx.scene.control.*;
|
||||||
|
@ -238,10 +240,19 @@ public class CounterpartyController extends SorobanController {
|
||||||
SorobanWalletCounterparty sorobanWalletCounterparty = sorobanWalletService.getSorobanWalletCounterparty(cahootsWallet);
|
SorobanWalletCounterparty sorobanWalletCounterparty = sorobanWalletService.getSorobanWalletCounterparty(cahootsWallet);
|
||||||
sorobanWalletCounterparty.setTimeoutMeetingMs(TIMEOUT_MS);
|
sorobanWalletCounterparty.setTimeoutMeetingMs(TIMEOUT_MS);
|
||||||
|
|
||||||
try {
|
Service<SorobanRequestMessage> receiveMeetingService = new Service<>() {
|
||||||
// TODO run in background thread?
|
@Override
|
||||||
SorobanRequestMessage requestMessage = sorobanWalletCounterparty.receiveMeetingRequest();
|
protected Task<SorobanRequestMessage> createTask() {
|
||||||
|
return new Task<>() {
|
||||||
|
@Override
|
||||||
|
protected SorobanRequestMessage call() throws Exception {
|
||||||
|
return sorobanWalletCounterparty.receiveMeetingRequest();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
receiveMeetingService.setOnSucceeded(event -> {
|
||||||
|
SorobanRequestMessage requestMessage = receiveMeetingService.getValue();
|
||||||
PaymentCode paymentCodeInitiator = requestMessage.getSender();
|
PaymentCode paymentCodeInitiator = requestMessage.getSender();
|
||||||
CahootsType cahootsType = requestMessage.getType();
|
CahootsType cahootsType = requestMessage.getType();
|
||||||
updateMixPartner(paymentCodeInitiator, cahootsType);
|
updateMixPartner(paymentCodeInitiator, cahootsType);
|
||||||
|
@ -261,11 +272,14 @@ public class CounterpartyController extends SorobanController {
|
||||||
mixingPartner.setVisible(false);
|
mixingPartner.setVisible(false);
|
||||||
requestUserAttention();
|
requestUserAttention();
|
||||||
});
|
});
|
||||||
} catch(Exception e) {
|
});
|
||||||
|
receiveMeetingService.setOnFailed(event -> {
|
||||||
|
Throwable e = event.getSource().getException();
|
||||||
log.error("Failed to receive meeting request", e);
|
log.error("Failed to receive meeting request", e);
|
||||||
mixingPartner.setVisible(false);
|
mixingPartner.setVisible(false);
|
||||||
requestUserAttention();
|
requestUserAttention();
|
||||||
}
|
});
|
||||||
|
receiveMeetingService.start();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void updateMixPartner(PaymentCode paymentCodeInitiator, CahootsType cahootsType) {
|
private void updateMixPartner(PaymentCode paymentCodeInitiator, CahootsType cahootsType) {
|
||||||
|
@ -308,6 +322,7 @@ public class CounterpartyController extends SorobanController {
|
||||||
CahootsContext cahootsContext = CahootsContext.newCounterparty(cahootsWallet, cahootsType, account);
|
CahootsContext cahootsContext = CahootsContext.newCounterparty(cahootsWallet, cahootsType, account);
|
||||||
Consumer<OnlineCahootsMessage> onProgress = cahootsMessage -> {
|
Consumer<OnlineCahootsMessage> onProgress = cahootsMessage -> {
|
||||||
if(cahootsMessage != null) {
|
if(cahootsMessage != null) {
|
||||||
|
Platform.runLater(() -> {
|
||||||
Cahoots cahoots = cahootsMessage.getCahoots();
|
Cahoots cahoots = cahootsMessage.getCahoots();
|
||||||
sorobanProgressBar.setProgress((double)(cahoots.getStep() + 1) / 5);
|
sorobanProgressBar.setProgress((double)(cahoots.getStep() + 1) / 5);
|
||||||
|
|
||||||
|
@ -328,12 +343,23 @@ public class CounterpartyController extends SorobanController {
|
||||||
sorobanProgressLabel.setVisible(false);
|
sorobanProgressLabel.setVisible(false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
try {
|
|
||||||
// TODO run in background thread?
|
Service<Cahoots> cahootsService = new Service<>() {
|
||||||
Cahoots result = sorobanWalletCounterparty.counterparty(cahootsContext, initiatorPaymentCode, onProgress);
|
@Override
|
||||||
} catch (Exception error) {
|
protected Task<Cahoots> createTask() {
|
||||||
|
return new Task<>() {
|
||||||
|
@Override
|
||||||
|
protected Cahoots call() throws Exception {
|
||||||
|
return sorobanWalletCounterparty.counterparty(cahootsContext, initiatorPaymentCode, onProgress);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
cahootsService.setOnFailed(event -> {
|
||||||
|
Throwable error = event.getSource().getException();
|
||||||
log.error("Error creating mix transaction", error);
|
log.error("Error creating mix transaction", error);
|
||||||
String cutFrom = "Exception: ";
|
String cutFrom = "Exception: ";
|
||||||
int index = error.getMessage().lastIndexOf(cutFrom);
|
int index = error.getMessage().lastIndexOf(cutFrom);
|
||||||
|
@ -341,7 +367,8 @@ public class CounterpartyController extends SorobanController {
|
||||||
msg = msg.replace("#Cahoots", "mix transaction");
|
msg = msg.replace("#Cahoots", "mix transaction");
|
||||||
step3Desc.setText(msg);
|
step3Desc.setText(msg);
|
||||||
sorobanProgressLabel.setVisible(false);
|
sorobanProgressLabel.setVisible(false);
|
||||||
}
|
});
|
||||||
|
cahootsService.start();
|
||||||
} catch(Exception e) {
|
} catch(Exception e) {
|
||||||
log.error("Error creating mix transaction", e);
|
log.error("Error creating mix transaction", e);
|
||||||
sorobanProgressLabel.setText(e.getMessage());
|
sorobanProgressLabel.setText(e.getMessage());
|
||||||
|
|
|
@ -38,8 +38,6 @@ import com.sparrowwallet.sparrow.paynym.PayNymAddress;
|
||||||
import com.sparrowwallet.sparrow.paynym.PayNymDialog;
|
import com.sparrowwallet.sparrow.paynym.PayNymDialog;
|
||||||
import com.sparrowwallet.sparrow.paynym.PayNymService;
|
import com.sparrowwallet.sparrow.paynym.PayNymService;
|
||||||
import io.reactivex.Observable;
|
import io.reactivex.Observable;
|
||||||
import io.reactivex.rxjavafx.schedulers.JavaFxScheduler;
|
|
||||||
import io.reactivex.schedulers.Schedulers;
|
|
||||||
import javafx.application.Platform;
|
import javafx.application.Platform;
|
||||||
import javafx.beans.property.ObjectProperty;
|
import javafx.beans.property.ObjectProperty;
|
||||||
import javafx.beans.property.SimpleObjectProperty;
|
import javafx.beans.property.SimpleObjectProperty;
|
||||||
|
@ -47,6 +45,8 @@ import javafx.beans.property.SimpleStringProperty;
|
||||||
import javafx.beans.property.StringProperty;
|
import javafx.beans.property.StringProperty;
|
||||||
import javafx.beans.value.ChangeListener;
|
import javafx.beans.value.ChangeListener;
|
||||||
import javafx.collections.FXCollections;
|
import javafx.collections.FXCollections;
|
||||||
|
import javafx.concurrent.Service;
|
||||||
|
import javafx.concurrent.Task;
|
||||||
import javafx.event.ActionEvent;
|
import javafx.event.ActionEvent;
|
||||||
import javafx.fxml.FXML;
|
import javafx.fxml.FXML;
|
||||||
import javafx.scene.control.*;
|
import javafx.scene.control.*;
|
||||||
|
@ -431,6 +431,7 @@ public class InitiatorController extends SorobanController {
|
||||||
public void onResponse(SorobanResponseMessage sorobanResponse) throws Exception {
|
public void onResponse(SorobanResponseMessage sorobanResponse) throws Exception {
|
||||||
super.onResponse(sorobanResponse);
|
super.onResponse(sorobanResponse);
|
||||||
|
|
||||||
|
Platform.runLater(() -> {
|
||||||
requestUserAttention();
|
requestUserAttention();
|
||||||
if(sorobanResponse.isAccept()) {
|
if(sorobanResponse.isAccept()) {
|
||||||
sorobanProgressBar.setProgress(0.1);
|
sorobanProgressBar.setProgress(0.1);
|
||||||
|
@ -439,29 +440,36 @@ public class InitiatorController extends SorobanController {
|
||||||
step2Desc.setText("Mix partner declined.");
|
step2Desc.setText("Mix partner declined.");
|
||||||
sorobanProgressLabel.setVisible(false);
|
sorobanProgressLabel.setVisible(false);
|
||||||
}
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onInteraction(OnlineSorobanInteraction interaction) throws Exception {
|
public void onInteraction(OnlineSorobanInteraction interaction) throws Exception {
|
||||||
SorobanInteraction originInteraction = interaction.getInteraction();
|
SorobanInteraction originInteraction = interaction.getInteraction();
|
||||||
if (originInteraction instanceof TxBroadcastInteraction) {
|
if(originInteraction instanceof TxBroadcastInteraction) {
|
||||||
|
Platform.runLater(() -> {
|
||||||
|
try {
|
||||||
Boolean accepted = (Boolean)Platform.enterNestedEventLoop(transactionAccepted);
|
Boolean accepted = (Boolean)Platform.enterNestedEventLoop(transactionAccepted);
|
||||||
if(accepted) {
|
if(accepted) {
|
||||||
interaction.sorobanAccept();
|
interaction.sorobanAccept();
|
||||||
} else {
|
} else {
|
||||||
interaction.sorobanReject("Mix partner declined to broadcast the transaction.");
|
interaction.sorobanReject("Mix partner declined to broadcast the transaction.");
|
||||||
}
|
}
|
||||||
|
} catch(Exception e) {
|
||||||
|
log.error("Error accepting Soroban interaction", e);
|
||||||
|
}
|
||||||
|
});
|
||||||
} else {
|
} else {
|
||||||
throw new Exception("Unknown interaction: "+originInteraction.getTypeInteraction());
|
throw new Exception("Unknown interaction: "+originInteraction.getTypeInteraction());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void progress(OnlineCahootsMessage message) {
|
public void progress(OnlineCahootsMessage cahootsMessage) {
|
||||||
super.progress(message);
|
super.progress(cahootsMessage);
|
||||||
|
|
||||||
OnlineCahootsMessage cahootsMessage = (OnlineCahootsMessage)message;
|
|
||||||
if(cahootsMessage != null) {
|
if(cahootsMessage != null) {
|
||||||
|
Platform.runLater(() -> {
|
||||||
Cahoots cahoots = cahootsMessage.getCahoots();
|
Cahoots cahoots = cahootsMessage.getCahoots();
|
||||||
sorobanProgressBar.setProgress((double)(cahoots.getStep() + 1) / 5);
|
sorobanProgressBar.setProgress((double)(cahoots.getStep() + 1) / 5);
|
||||||
|
|
||||||
|
@ -489,21 +497,33 @@ public class InitiatorController extends SorobanController {
|
||||||
sorobanProgressLabel.setVisible(false);
|
sorobanProgressLabel.setVisible(false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
SorobanWalletService sorobanWalletService = soroban.getSorobanWalletService();
|
SorobanWalletService sorobanWalletService = soroban.getSorobanWalletService();
|
||||||
sorobanProgressLabel.setText("Waiting for mix partner...");
|
sorobanProgressLabel.setText("Waiting for mix partner...");
|
||||||
try {
|
|
||||||
// TODO run in background thread?
|
Service<Cahoots> cahootsService = new Service<>() {
|
||||||
Cahoots result = sorobanWalletService.getSorobanWalletInitiator(cahootsWallet).meetAndInitiate(cahootsContext, paymentCodeCounterparty, listener);
|
@Override
|
||||||
} catch (Exception error){
|
protected Task<Cahoots> createTask() {
|
||||||
|
return new Task<>() {
|
||||||
|
@Override
|
||||||
|
protected Cahoots call() throws Exception {
|
||||||
|
return sorobanWalletService.getSorobanWalletInitiator(cahootsWallet).meetAndInitiate(cahootsContext, paymentCodeCounterparty, listener);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
cahootsService.setOnFailed(event -> {
|
||||||
|
Throwable error = event.getSource().getException();
|
||||||
log.error("Error receiving meeting response", error);
|
log.error("Error receiving meeting response", error);
|
||||||
step2Desc.setText(getErrorMessage(error));
|
step2Desc.setText(getErrorMessage(error));
|
||||||
sorobanProgressLabel.setVisible(false);
|
sorobanProgressLabel.setVisible(false);
|
||||||
meetingFail.setVisible(true);
|
meetingFail.setVisible(true);
|
||||||
requestUserAttention();
|
requestUserAttention();
|
||||||
}
|
});
|
||||||
|
cahootsService.start();
|
||||||
}, error -> {
|
}, error -> {
|
||||||
log.error("Could not retrieve payment code", error);
|
log.error("Could not retrieve payment code", error);
|
||||||
if(error.getMessage().endsWith("404")) {
|
if(error.getMessage().endsWith("404")) {
|
||||||
|
|
|
@ -79,26 +79,4 @@ public class Soroban {
|
||||||
public SorobanWalletService getSorobanWalletService() {
|
public SorobanWalletService getSorobanWalletService() {
|
||||||
return sorobanWalletService;
|
return sorobanWalletService;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void stop() {
|
|
||||||
AppServices.getHttpClientService().stop();
|
|
||||||
}
|
|
||||||
|
|
||||||
public static class ShutdownService extends Service<Boolean> {
|
|
||||||
private final Soroban soroban;
|
|
||||||
|
|
||||||
public ShutdownService(Soroban soroban) {
|
|
||||||
this.soroban = soroban;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected Task<Boolean> createTask() {
|
|
||||||
return new Task<>() {
|
|
||||||
protected Boolean call() throws Exception {
|
|
||||||
soroban.stop();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -53,14 +53,7 @@ public class SorobanServices {
|
||||||
public void walletTabsClosed(WalletTabsClosedEvent event) {
|
public void walletTabsClosed(WalletTabsClosedEvent event) {
|
||||||
for(WalletTabData walletTabData : event.getClosedWalletTabData()) {
|
for(WalletTabData walletTabData : event.getClosedWalletTabData()) {
|
||||||
String walletId = walletTabData.getStorage().getWalletId(walletTabData.getWallet());
|
String walletId = walletTabData.getStorage().getWalletId(walletTabData.getWallet());
|
||||||
Soroban soroban = sorobanMap.remove(walletId);
|
sorobanMap.remove(walletId);
|
||||||
if(soroban != null) {
|
|
||||||
Soroban.ShutdownService shutdownService = new Soroban.ShutdownService(soroban);
|
|
||||||
shutdownService.setOnFailed(failedEvent -> {
|
|
||||||
log.error("Failed to shutdown soroban", failedEvent.getSource().getException());
|
|
||||||
});
|
|
||||||
shutdownService.start();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -26,13 +26,12 @@ import java.util.List;
|
||||||
|
|
||||||
public class SparrowCahootsWallet extends AbstractCahootsWallet {
|
public class SparrowCahootsWallet extends AbstractCahootsWallet {
|
||||||
private final Wallet wallet;
|
private final Wallet wallet;
|
||||||
private HD_Wallet bip84w;
|
private final HD_Wallet bip84w;
|
||||||
private final int account;
|
private final int account;
|
||||||
private List<CahootsUtxo> utxos;
|
private final List<CahootsUtxo> utxos;
|
||||||
|
|
||||||
public SparrowCahootsWallet(ChainSupplier chainSupplier, Wallet wallet, HD_Wallet bip84w, int bip47Account) {
|
public SparrowCahootsWallet(ChainSupplier chainSupplier, Wallet wallet, HD_Wallet bip84w, int bip47Account) {
|
||||||
super(chainSupplier, bip84w.getFingerprint(),
|
super(chainSupplier, bip84w.getFingerprint(), new BIP47Wallet(bip84w).getAccount(bip47Account));
|
||||||
new BIP47Wallet(bip84w).getAccount(bip47Account));
|
|
||||||
this.wallet = wallet;
|
this.wallet = wallet;
|
||||||
this.bip84w = bip84w;
|
this.bip84w = bip84w;
|
||||||
this.account = wallet.getAccountIndex();
|
this.account = wallet.getAccountIndex();
|
||||||
|
@ -107,7 +106,7 @@ public class SparrowCahootsWallet extends AbstractCahootsWallet {
|
||||||
throw new IllegalStateException("Cannot add BIP47 UTXO", e);
|
throw new IllegalStateException("Cannot add BIP47 UTXO", e);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
HD_Address hdAddress = bip84w.getAddressAt(index, unspentOutput);
|
HD_Address hdAddress = bip84w.getAddressAt(account, unspentOutput);
|
||||||
cahootsUtxo = new CahootsUtxo(myTransactionOutPoint, node.getDerivationPath(), null, hdAddress.getECKey().getPrivKeyBytes());
|
cahootsUtxo = new CahootsUtxo(myTransactionOutPoint, node.getDerivationPath(), null, hdAddress.getECKey().getPrivKeyBytes());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -43,7 +43,7 @@ public class MixTableCell extends TableCell {
|
||||||
|
|
||||||
private String getMixFail(UtxoEntry.MixStatus mixStatus) {
|
private String getMixFail(UtxoEntry.MixStatus mixStatus) {
|
||||||
long elapsed = mixStatus.getMixErrorTimestamp() == null ? 0L : System.currentTimeMillis() - mixStatus.getMixErrorTimestamp();
|
long elapsed = mixStatus.getMixErrorTimestamp() == null ? 0L : System.currentTimeMillis() - mixStatus.getMixErrorTimestamp();
|
||||||
if(mixStatus.getMixFailReason() == MixFailReason.CANCEL || elapsed >= ERROR_DISPLAY_MILLIS) {
|
if(mixStatus.getMixFailReason() == MixFailReason.STOP_UTXO || mixStatus.getMixFailReason().isSilent() || elapsed >= ERROR_DISPLAY_MILLIS) {
|
||||||
return getMixCountOnly(mixStatus);
|
return getMixCountOnly(mixStatus);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -129,9 +129,10 @@ public class WalletTransactionsEntry extends Entry {
|
||||||
private static void getWalletTransactions(Wallet wallet, Map<BlockTransaction, WalletTransaction> walletTransactionMap, WalletNode purposeNode) {
|
private static void getWalletTransactions(Wallet wallet, Map<BlockTransaction, WalletTransaction> walletTransactionMap, WalletNode purposeNode) {
|
||||||
KeyPurpose keyPurpose = purposeNode.getKeyPurpose();
|
KeyPurpose keyPurpose = purposeNode.getKeyPurpose();
|
||||||
List<WalletNode> childNodes = new ArrayList<>(purposeNode.getChildren());
|
List<WalletNode> childNodes = new ArrayList<>(purposeNode.getChildren());
|
||||||
|
Wallet transactionsWallet = wallet.isNested() ? wallet.getMasterWallet() : wallet;
|
||||||
for(WalletNode addressNode : childNodes) {
|
for(WalletNode addressNode : childNodes) {
|
||||||
for(BlockTransactionHashIndex hashIndex : addressNode.getTransactionOutputs()) {
|
for(BlockTransactionHashIndex hashIndex : addressNode.getTransactionOutputs()) {
|
||||||
BlockTransaction inputTx = wallet.getWalletTransaction(hashIndex.getHash());
|
BlockTransaction inputTx = transactionsWallet.getWalletTransaction(hashIndex.getHash());
|
||||||
//A null inputTx here means the wallet is still updating - ignore as the WalletHistoryChangedEvent will run this again
|
//A null inputTx here means the wallet is still updating - ignore as the WalletHistoryChangedEvent will run this again
|
||||||
if(inputTx != null) {
|
if(inputTx != null) {
|
||||||
WalletTransaction inputWalletTx = walletTransactionMap.get(inputTx);
|
WalletTransaction inputWalletTx = walletTransactionMap.get(inputTx);
|
||||||
|
@ -142,7 +143,7 @@ public class WalletTransactionsEntry extends Entry {
|
||||||
inputWalletTx.incoming.put(hashIndex, keyPurpose);
|
inputWalletTx.incoming.put(hashIndex, keyPurpose);
|
||||||
|
|
||||||
if(hashIndex.getSpentBy() != null) {
|
if(hashIndex.getSpentBy() != null) {
|
||||||
BlockTransaction outputTx = wallet.getWalletTransaction(hashIndex.getSpentBy().getHash());
|
BlockTransaction outputTx = transactionsWallet.getWalletTransaction(hashIndex.getSpentBy().getHash());
|
||||||
if(outputTx != null) {
|
if(outputTx != null) {
|
||||||
WalletTransaction outputWalletTx = walletTransactionMap.get(outputTx);
|
WalletTransaction outputWalletTx = walletTransactionMap.get(outputTx);
|
||||||
if(outputWalletTx == null) {
|
if(outputWalletTx == null) {
|
||||||
|
|
|
@ -6,6 +6,7 @@ import com.sparrowwallet.drongo.wallet.WalletNode;
|
||||||
import com.sparrowwallet.sparrow.AppServices;
|
import com.sparrowwallet.sparrow.AppServices;
|
||||||
import com.sparrowwallet.sparrow.io.Config;
|
import com.sparrowwallet.sparrow.io.Config;
|
||||||
import com.sparrowwallet.sparrow.whirlpool.Whirlpool;
|
import com.sparrowwallet.sparrow.whirlpool.Whirlpool;
|
||||||
|
import javafx.application.Platform;
|
||||||
|
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
@ -92,7 +93,8 @@ public class WalletUtxosEntry extends Entry {
|
||||||
|
|
||||||
calculateDuplicates();
|
calculateDuplicates();
|
||||||
calculateDust();
|
calculateDust();
|
||||||
updateMixProgress();
|
//Update mix status after SparrowUtxoSupplier has refreshed
|
||||||
|
Platform.runLater(this::updateMixProgress);
|
||||||
}
|
}
|
||||||
|
|
||||||
public long getBalance() {
|
public long getBalance() {
|
||||||
|
|
|
@ -33,10 +33,7 @@ import com.sparrowwallet.drongo.ExtendedKey;
|
||||||
import com.sparrowwallet.drongo.KeyPurpose;
|
import com.sparrowwallet.drongo.KeyPurpose;
|
||||||
import com.sparrowwallet.drongo.Network;
|
import com.sparrowwallet.drongo.Network;
|
||||||
import com.sparrowwallet.drongo.Utils;
|
import com.sparrowwallet.drongo.Utils;
|
||||||
import com.sparrowwallet.drongo.protocol.Sha256Hash;
|
import com.sparrowwallet.drongo.protocol.*;
|
||||||
import com.sparrowwallet.drongo.protocol.Transaction;
|
|
||||||
import com.sparrowwallet.drongo.protocol.TransactionInput;
|
|
||||||
import com.sparrowwallet.drongo.protocol.TransactionOutput;
|
|
||||||
import com.sparrowwallet.drongo.wallet.*;
|
import com.sparrowwallet.drongo.wallet.*;
|
||||||
import com.sparrowwallet.sparrow.AppServices;
|
import com.sparrowwallet.sparrow.AppServices;
|
||||||
import com.sparrowwallet.sparrow.EventManager;
|
import com.sparrowwallet.sparrow.EventManager;
|
||||||
|
@ -71,7 +68,6 @@ public class Whirlpool {
|
||||||
public static final int DEFAULT_MIXTO_MIN_MIXES = 3;
|
public static final int DEFAULT_MIXTO_MIN_MIXES = 3;
|
||||||
public static final int DEFAULT_MIXTO_RANDOM_FACTOR = 4;
|
public static final int DEFAULT_MIXTO_RANDOM_FACTOR = 4;
|
||||||
|
|
||||||
|
|
||||||
private final WhirlpoolWalletService whirlpoolWalletService;
|
private final WhirlpoolWalletService whirlpoolWalletService;
|
||||||
private final WhirlpoolWalletConfig config;
|
private final WhirlpoolWalletConfig config;
|
||||||
private WhirlpoolInfo whirlpoolInfo;
|
private WhirlpoolInfo whirlpoolInfo;
|
||||||
|
@ -89,11 +85,10 @@ public class Whirlpool {
|
||||||
private final BooleanProperty stoppingProperty = new SimpleBooleanProperty(false);
|
private final BooleanProperty stoppingProperty = new SimpleBooleanProperty(false);
|
||||||
private final BooleanProperty mixingProperty = new SimpleBooleanProperty(false);
|
private final BooleanProperty mixingProperty = new SimpleBooleanProperty(false);
|
||||||
|
|
||||||
public Whirlpool() {
|
public Whirlpool(Integer storedBlockHeight) {
|
||||||
this.whirlpoolWalletService = new WhirlpoolWalletService();
|
this.whirlpoolWalletService = new WhirlpoolWalletService();
|
||||||
Integer storedBlockHeight = null; // TODO
|
|
||||||
this.config = computeWhirlpoolWalletConfig(storedBlockHeight);
|
this.config = computeWhirlpoolWalletConfig(storedBlockHeight);
|
||||||
this.whirlpoolInfo = null; // instanciated by getWhirlpoolInfo()
|
this.whirlpoolInfo = null; // instantiated by getWhirlpoolInfo()
|
||||||
|
|
||||||
WhirlpoolEventService.getInstance().register(this);
|
WhirlpoolEventService.getInstance().register(this);
|
||||||
}
|
}
|
||||||
|
@ -112,17 +107,14 @@ public class Whirlpool {
|
||||||
}
|
}
|
||||||
|
|
||||||
private DataSourceConfig computeDataSourceConfig(Integer storedBlockHeight) {
|
private DataSourceConfig computeDataSourceConfig(Integer storedBlockHeight) {
|
||||||
return new DataSourceConfig(
|
return new DataSourceConfig(SparrowMinerFeeSupplier.getInstance(), new SparrowChainSupplier(storedBlockHeight), BIP_FORMAT.PROVIDER, BIP_WALLETS.WHIRLPOOL);
|
||||||
SparrowMinerFeeSupplier.getInstance(),
|
|
||||||
new SparrowChainSupplier(storedBlockHeight),
|
|
||||||
BIP_FORMAT.PROVIDER,
|
|
||||||
BIP_WALLETS.WHIRLPOOL);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private WhirlpoolInfo getWhirlpoolInfo() {
|
private WhirlpoolInfo getWhirlpoolInfo() {
|
||||||
if (whirlpoolInfo == null) {
|
if(whirlpoolInfo == null) {
|
||||||
whirlpoolInfo = new WhirlpoolInfo(SparrowMinerFeeSupplier.getInstance(), config);
|
whirlpoolInfo = new WhirlpoolInfo(SparrowMinerFeeSupplier.getInstance(), config);
|
||||||
}
|
}
|
||||||
|
|
||||||
return whirlpoolInfo;
|
return whirlpoolInfo;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -144,8 +136,7 @@ public class Whirlpool {
|
||||||
|
|
||||||
public Tx0 broadcastTx0(Pool pool, Collection<BlockTransactionHashIndex> utxos) throws Exception {
|
public Tx0 broadcastTx0(Pool pool, Collection<BlockTransactionHashIndex> utxos) throws Exception {
|
||||||
WhirlpoolWallet whirlpoolWallet = getWhirlpoolWallet();
|
WhirlpoolWallet whirlpoolWallet = getWhirlpoolWallet();
|
||||||
whirlpoolWallet.startAsync().subscribeOn(Schedulers.io())
|
whirlpoolWallet.startAsync().subscribeOn(Schedulers.io()).observeOn(JavaFxScheduler.platform());
|
||||||
.observeOn(JavaFxScheduler.platform());
|
|
||||||
UtxoSupplier utxoSupplier = whirlpoolWallet.getUtxoSupplier();
|
UtxoSupplier utxoSupplier = whirlpoolWallet.getUtxoSupplier();
|
||||||
List<WhirlpoolUtxo> whirlpoolUtxos = utxos.stream().map(ref -> utxoSupplier.findUtxo(ref.getHashAsString(), (int)ref.getIndex())).filter(Objects::nonNull).collect(Collectors.toList());
|
List<WhirlpoolUtxo> whirlpoolUtxos = utxos.stream().map(ref -> utxoSupplier.findUtxo(ref.getHashAsString(), (int)ref.getIndex())).filter(Objects::nonNull).collect(Collectors.toList());
|
||||||
|
|
||||||
|
@ -177,10 +168,13 @@ public class Whirlpool {
|
||||||
|
|
||||||
try {
|
try {
|
||||||
Keystore keystore = wallet.getKeystores().get(0);
|
Keystore keystore = wallet.getKeystores().get(0);
|
||||||
String words = keystore.getSeed().getMnemonicString().asString();
|
ScriptType scriptType = wallet.getScriptType();
|
||||||
|
int purpose = scriptType.getDefaultDerivation().get(0).num();
|
||||||
|
List<String> words = keystore.getSeed().getMnemonicCode();
|
||||||
String passphrase = keystore.getSeed().getPassphrase() == null ? "" : keystore.getSeed().getPassphrase().asString();
|
String passphrase = keystore.getSeed().getPassphrase() == null ? "" : keystore.getSeed().getPassphrase().asString();
|
||||||
HD_WalletFactoryGeneric hdWalletFactory = HD_WalletFactoryGeneric.getInstance();
|
HD_WalletFactoryGeneric hdWalletFactory = HD_WalletFactoryGeneric.getInstance();
|
||||||
return hdWalletFactory.restoreWalletFromWords(words, passphrase, params);
|
byte[] seed = hdWalletFactory.computeSeedFromWords(words);
|
||||||
|
return hdWalletFactory.getHD(purpose, seed, passphrase, params);
|
||||||
} catch(Exception e) {
|
} catch(Exception e) {
|
||||||
throw new IllegalStateException("Could not create Whirlpool HD wallet ", e);
|
throw new IllegalStateException("Could not create Whirlpool HD wallet ", e);
|
||||||
}
|
}
|
||||||
|
@ -264,9 +258,7 @@ public class Whirlpool {
|
||||||
|
|
||||||
public void refreshUtxos() {
|
public void refreshUtxos() {
|
||||||
if(whirlpoolWalletService.whirlpoolWallet() != null) {
|
if(whirlpoolWalletService.whirlpoolWallet() != null) {
|
||||||
whirlpoolWalletService.whirlpoolWallet().refreshUtxosAsync()
|
whirlpoolWalletService.whirlpoolWallet().refreshUtxosAsync().subscribeOn(Schedulers.io()).observeOn(JavaFxScheduler.platform());
|
||||||
.subscribeOn(Schedulers.io())
|
|
||||||
.observeOn(JavaFxScheduler.platform());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -352,7 +344,6 @@ public class Whirlpool {
|
||||||
|
|
||||||
public void shutdown() {
|
public void shutdown() {
|
||||||
whirlpoolWalletService.closeWallet();
|
whirlpoolWalletService.closeWallet();
|
||||||
AppServices.getHttpClientService().stop();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public StartupService createStartupService() {
|
public StartupService createStartupService() {
|
||||||
|
@ -517,7 +508,12 @@ public class Whirlpool {
|
||||||
int mixes = minMixes == null ? DEFAULT_MIXTO_MIN_MIXES : minMixes;
|
int mixes = minMixes == null ? DEFAULT_MIXTO_MIN_MIXES : minMixes;
|
||||||
|
|
||||||
IPostmixHandler postmixHandler = new SparrowPostmixHandler(whirlpoolWalletService, mixToWallet, KeyPurpose.RECEIVE);
|
IPostmixHandler postmixHandler = new SparrowPostmixHandler(whirlpoolWalletService, mixToWallet, KeyPurpose.RECEIVE);
|
||||||
ExternalDestination externalDestination = new ExternalDestination(postmixHandler, 0, mixes, DEFAULT_MIXTO_RANDOM_FACTOR);
|
ExternalDestination externalDestination = new ExternalDestination(postmixHandler, 0, mixes, DEFAULT_MIXTO_RANDOM_FACTOR) {
|
||||||
|
@Override
|
||||||
|
public IPostmixHandler getPostmixHandlerCustomOrDefault(WhirlpoolWallet whirlpoolWallet) {
|
||||||
|
return postmixHandler;
|
||||||
|
}
|
||||||
|
};
|
||||||
config.setExternalDestination(externalDestination);
|
config.setExternalDestination(externalDestination);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -715,10 +711,7 @@ public class Whirlpool {
|
||||||
whirlpool.startingProperty.set(true);
|
whirlpool.startingProperty.set(true);
|
||||||
WhirlpoolWallet whirlpoolWallet = whirlpool.getWhirlpoolWallet();
|
WhirlpoolWallet whirlpoolWallet = whirlpool.getWhirlpoolWallet();
|
||||||
if(AppServices.onlineProperty().get()) {
|
if(AppServices.onlineProperty().get()) {
|
||||||
whirlpoolWallet.startAsync()
|
whirlpoolWallet.startAsync().subscribeOn(Schedulers.io()).observeOn(JavaFxScheduler.platform()).subscribe();
|
||||||
.subscribeOn(Schedulers.io())
|
|
||||||
.observeOn(JavaFxScheduler.platform())
|
|
||||||
.subscribe();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return whirlpoolWallet;
|
return whirlpoolWallet;
|
||||||
|
|
|
@ -73,7 +73,8 @@ public class WhirlpoolServices {
|
||||||
public Whirlpool getWhirlpool(String walletId) {
|
public Whirlpool getWhirlpool(String walletId) {
|
||||||
Whirlpool whirlpool = whirlpoolMap.get(walletId);
|
Whirlpool whirlpool = whirlpoolMap.get(walletId);
|
||||||
if(whirlpool == null) {
|
if(whirlpool == null) {
|
||||||
whirlpool = new Whirlpool();
|
Wallet wallet = AppServices.get().getWallet(walletId);
|
||||||
|
whirlpool = new Whirlpool(wallet == null ? null : wallet.getStoredBlockHeight());
|
||||||
whirlpoolMap.put(walletId, whirlpool);
|
whirlpoolMap.put(walletId, whirlpool);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -3,19 +3,18 @@ package com.sparrowwallet.sparrow.whirlpool.dataSource;
|
||||||
import com.google.common.eventbus.Subscribe;
|
import com.google.common.eventbus.Subscribe;
|
||||||
import com.samourai.wallet.api.backend.beans.WalletResponse;
|
import com.samourai.wallet.api.backend.beans.WalletResponse;
|
||||||
import com.samourai.wallet.chain.ChainSupplier;
|
import com.samourai.wallet.chain.ChainSupplier;
|
||||||
|
import com.sparrowwallet.drongo.Utils;
|
||||||
import com.sparrowwallet.drongo.protocol.Sha256Hash;
|
import com.sparrowwallet.drongo.protocol.Sha256Hash;
|
||||||
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.NewBlockEvent;
|
import com.sparrowwallet.sparrow.event.NewBlockEvent;
|
||||||
|
|
||||||
public class SparrowChainSupplier implements ChainSupplier {
|
public class SparrowChainSupplier implements ChainSupplier {
|
||||||
private int storedBlockHeight;
|
private final int storedBlockHeight;
|
||||||
private WalletResponse.InfoBlock latestBlock;
|
private WalletResponse.InfoBlock latestBlock;
|
||||||
|
|
||||||
public SparrowChainSupplier(Integer storedBlockHeight) {
|
public SparrowChainSupplier(Integer storedBlockHeight) {
|
||||||
this.storedBlockHeight = AppServices.getCurrentBlockHeight() == null ?
|
this.storedBlockHeight = AppServices.getCurrentBlockHeight() == null ? (storedBlockHeight != null ? storedBlockHeight : 0) : AppServices.getCurrentBlockHeight();
|
||||||
(storedBlockHeight!=null?storedBlockHeight:0)
|
|
||||||
: AppServices.getCurrentBlockHeight();
|
|
||||||
this.latestBlock = computeLatestBlock();
|
this.latestBlock = computeLatestBlock();
|
||||||
EventManager.get().register(this);
|
EventManager.get().register(this);
|
||||||
}
|
}
|
||||||
|
@ -27,7 +26,8 @@ public class SparrowChainSupplier implements ChainSupplier {
|
||||||
private WalletResponse.InfoBlock computeLatestBlock() {
|
private WalletResponse.InfoBlock computeLatestBlock() {
|
||||||
WalletResponse.InfoBlock latestBlock = new WalletResponse.InfoBlock();
|
WalletResponse.InfoBlock latestBlock = new WalletResponse.InfoBlock();
|
||||||
latestBlock.height = AppServices.getCurrentBlockHeight() == null ? storedBlockHeight : AppServices.getCurrentBlockHeight();
|
latestBlock.height = AppServices.getCurrentBlockHeight() == null ? storedBlockHeight : AppServices.getCurrentBlockHeight();
|
||||||
latestBlock.hash = Sha256Hash.ZERO_HASH.toString();
|
latestBlock.hash = AppServices.getLatestBlockHeader() == null ? Sha256Hash.ZERO_HASH.toString() :
|
||||||
|
Utils.bytesToHex(Sha256Hash.twiceOf(AppServices.getLatestBlockHeader().bitcoinSerialize()).getReversedBytes());
|
||||||
latestBlock.time = AppServices.getLatestBlockHeader() == null ? 1 : AppServices.getLatestBlockHeader().getTime();
|
latestBlock.time = AppServices.getLatestBlockHeader() == null ? 1 : AppServices.getLatestBlockHeader().getTime();
|
||||||
return latestBlock;
|
return latestBlock;
|
||||||
}
|
}
|
||||||
|
|
|
@ -40,7 +40,7 @@ public class SparrowDataSource extends AbstractDataSource {
|
||||||
|
|
||||||
private final ISeenBackend seenBackend;
|
private final ISeenBackend seenBackend;
|
||||||
private final IPushTx pushTx;
|
private final IPushTx pushTx;
|
||||||
private SparrowUtxoSupplier utxoSupplier;
|
private final SparrowUtxoSupplier utxoSupplier;
|
||||||
|
|
||||||
public SparrowDataSource(
|
public SparrowDataSource(
|
||||||
WhirlpoolWallet whirlpoolWallet,
|
WhirlpoolWallet whirlpoolWallet,
|
||||||
|
@ -57,7 +57,7 @@ public class SparrowDataSource extends AbstractDataSource {
|
||||||
|
|
||||||
private ISeenBackend computeSeenBackend(WhirlpoolWalletConfig whirlpoolWalletConfig) {
|
private ISeenBackend computeSeenBackend(WhirlpoolWalletConfig whirlpoolWalletConfig) {
|
||||||
IHttpClient httpClient = whirlpoolWalletConfig.getHttpClient(HttpUsage.BACKEND);
|
IHttpClient httpClient = whirlpoolWalletConfig.getHttpClient(HttpUsage.BACKEND);
|
||||||
ISeenBackend sparrowSeenBackend = new SparrowSeenBackend(httpClient);
|
ISeenBackend sparrowSeenBackend = new SparrowSeenBackend(getWhirlpoolWallet().getWalletIdentifier(), httpClient);
|
||||||
NetworkParameters params = whirlpoolWalletConfig.getSamouraiNetwork().getParams();
|
NetworkParameters params = whirlpoolWalletConfig.getSamouraiNetwork().getParams();
|
||||||
return SeenBackendWithFallback.withOxt(sparrowSeenBackend, params);
|
return SeenBackendWithFallback.withOxt(sparrowSeenBackend, params);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,8 +1,10 @@
|
||||||
package com.sparrowwallet.sparrow.whirlpool.dataSource;
|
package com.sparrowwallet.sparrow.whirlpool.dataSource;
|
||||||
|
|
||||||
import com.samourai.whirlpool.client.mix.handler.AbstractPostmixHandler;
|
import com.samourai.wallet.client.indexHandler.IIndexHandler;
|
||||||
import com.samourai.whirlpool.client.mix.handler.DestinationType;
|
import com.samourai.whirlpool.client.mix.handler.DestinationType;
|
||||||
|
import com.samourai.whirlpool.client.mix.handler.IPostmixHandler;
|
||||||
import com.samourai.whirlpool.client.mix.handler.MixDestination;
|
import com.samourai.whirlpool.client.mix.handler.MixDestination;
|
||||||
|
import com.samourai.whirlpool.client.utils.ClientUtils;
|
||||||
import com.samourai.whirlpool.client.wallet.WhirlpoolWalletService;
|
import com.samourai.whirlpool.client.wallet.WhirlpoolWalletService;
|
||||||
import com.samourai.whirlpool.client.wallet.beans.IndexRange;
|
import com.samourai.whirlpool.client.wallet.beans.IndexRange;
|
||||||
import com.sparrowwallet.drongo.KeyPurpose;
|
import com.sparrowwallet.drongo.KeyPurpose;
|
||||||
|
@ -12,21 +14,21 @@ import com.sparrowwallet.drongo.wallet.WalletNode;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
// TODO maybe replace with XPubPostmixHandler
|
public class SparrowPostmixHandler implements IPostmixHandler {
|
||||||
public class SparrowPostmixHandler extends AbstractPostmixHandler {
|
|
||||||
private static final Logger log = LoggerFactory.getLogger(SparrowPostmixHandler.class);
|
private static final Logger log = LoggerFactory.getLogger(SparrowPostmixHandler.class);
|
||||||
|
|
||||||
|
private final WhirlpoolWalletService whirlpoolWalletService;
|
||||||
private final Wallet wallet;
|
private final Wallet wallet;
|
||||||
private final KeyPurpose keyPurpose;
|
private final KeyPurpose keyPurpose;
|
||||||
|
|
||||||
|
protected MixDestination destination;
|
||||||
|
|
||||||
public SparrowPostmixHandler(WhirlpoolWalletService whirlpoolWalletService, Wallet wallet, KeyPurpose keyPurpose) {
|
public SparrowPostmixHandler(WhirlpoolWalletService whirlpoolWalletService, Wallet wallet, KeyPurpose keyPurpose) {
|
||||||
super(whirlpoolWalletService.whirlpoolWallet().getWalletStateSupplier().getIndexHandlerExternal(),
|
this.whirlpoolWalletService = whirlpoolWalletService;
|
||||||
whirlpoolWalletService.whirlpoolWallet().getConfig().getSamouraiNetwork().getParams());
|
|
||||||
this.wallet = wallet;
|
this.wallet = wallet;
|
||||||
this.keyPurpose = keyPurpose;
|
this.keyPurpose = keyPurpose;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
protected IndexRange getIndexRange() {
|
protected IndexRange getIndexRange() {
|
||||||
return IndexRange.FULL;
|
return IndexRange.FULL;
|
||||||
}
|
}
|
||||||
|
@ -35,14 +37,50 @@ public class SparrowPostmixHandler extends AbstractPostmixHandler {
|
||||||
return wallet;
|
return wallet;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public final MixDestination computeDestinationNext() throws Exception {
|
||||||
|
// use "unconfirmed" index to avoid huge index gaps on multiple mix failures
|
||||||
|
int index = ClientUtils.computeNextReceiveAddressIndex(getIndexHandler(), getIndexRange());
|
||||||
|
this.destination = computeDestination(index);
|
||||||
|
if (log.isDebugEnabled()) {
|
||||||
|
log.debug(
|
||||||
|
"Mixing to "
|
||||||
|
+ destination.getType()
|
||||||
|
+ " -> receiveAddress="
|
||||||
|
+ destination.getAddress()
|
||||||
|
+ ", path="
|
||||||
|
+ destination.getPath());
|
||||||
|
}
|
||||||
|
return destination;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public MixDestination computeDestination(int index) throws Exception {
|
public MixDestination computeDestination(int index) throws Exception {
|
||||||
// address
|
// address
|
||||||
WalletNode node = new WalletNode(wallet, keyPurpose, index);
|
WalletNode node = new WalletNode(wallet, keyPurpose, index);
|
||||||
Address address = node.getAddress();
|
Address address = node.getAddress();
|
||||||
String path = "xpub/"+keyPurpose.getPathIndex().num()+"/"+index;
|
String path = "xpub/" + keyPurpose.getPathIndex().num() + "/" + index;
|
||||||
|
|
||||||
log.info("Mixing to external xPub -> receiveAddress=" + address + ", path=" + path);
|
log.info("Mixing to external xPub -> receiveAddress=" + address + ", path=" + path);
|
||||||
return new MixDestination(DestinationType.XPUB, index, address.toString(), path);
|
return new MixDestination(DestinationType.XPUB, index, address.toString(), path);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onMixFail() {
|
||||||
|
if(destination != null) {
|
||||||
|
// cancel unconfirmed postmix index if output was not registered yet
|
||||||
|
getIndexHandler().cancelUnconfirmed(destination.getIndex());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onRegisterOutput() {
|
||||||
|
// confirm postmix index on REGISTER_OUTPUT success
|
||||||
|
getIndexHandler().confirmUnconfirmed(destination.getIndex());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public IIndexHandler getIndexHandler() {
|
||||||
|
return whirlpoolWalletService.whirlpoolWallet().getWalletStateSupplier().getIndexHandlerExternal();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,30 +3,51 @@ package com.sparrowwallet.sparrow.whirlpool.dataSource;
|
||||||
import com.samourai.wallet.api.backend.seenBackend.ISeenBackend;
|
import com.samourai.wallet.api.backend.seenBackend.ISeenBackend;
|
||||||
import com.samourai.wallet.api.backend.seenBackend.SeenResponse;
|
import com.samourai.wallet.api.backend.seenBackend.SeenResponse;
|
||||||
import com.samourai.wallet.httpClient.IHttpClient;
|
import com.samourai.wallet.httpClient.IHttpClient;
|
||||||
|
import com.sparrowwallet.drongo.address.Address;
|
||||||
|
import com.sparrowwallet.drongo.wallet.Wallet;
|
||||||
|
import com.sparrowwallet.drongo.wallet.WalletNode;
|
||||||
|
import com.sparrowwallet.sparrow.AppServices;
|
||||||
|
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.LinkedHashMap;
|
import java.util.LinkedHashMap;
|
||||||
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
public class SparrowSeenBackend implements ISeenBackend {
|
public class SparrowSeenBackend implements ISeenBackend {
|
||||||
private IHttpClient httpClient;
|
private final String walletId;
|
||||||
|
private final IHttpClient httpClient;
|
||||||
|
|
||||||
public SparrowSeenBackend(IHttpClient httpClient) {
|
public SparrowSeenBackend(String walletId, IHttpClient httpClient) {
|
||||||
|
this.walletId = walletId;
|
||||||
this.httpClient = httpClient;
|
this.httpClient = httpClient;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public SeenResponse seen(Collection<String> addresses) throws Exception {
|
public SeenResponse seen(Collection<String> addresses) throws Exception {
|
||||||
Map<String,Boolean> map = new LinkedHashMap<>();
|
Wallet wallet = AppServices.get().getWallet(walletId);
|
||||||
for (String address : addresses) {
|
Map<Address, WalletNode> addressMap = wallet.getWalletAddresses();
|
||||||
map.put(address, seen(address));
|
for(Wallet childWallet : wallet.getChildWallets()) {
|
||||||
|
if(!childWallet.isNested()) {
|
||||||
|
addressMap.putAll(childWallet.getWalletAddresses());
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Map<String,Boolean> map = new LinkedHashMap<>();
|
||||||
|
for(String address : addresses) {
|
||||||
|
WalletNode walletNode = addressMap.get(Address.fromString(address));
|
||||||
|
if(walletNode != null) {
|
||||||
|
int highestUsedIndex = walletNode.getWallet().getNode(walletNode.getKeyPurpose()).getHighestUsedIndex();
|
||||||
|
map.put(address, walletNode.getIndex() <= highestUsedIndex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return new SeenResponse(map);
|
return new SeenResponse(map);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean seen(String address) throws Exception {
|
public boolean seen(String address) throws Exception {
|
||||||
return false; // TODO implement: return true if address already received funds
|
SeenResponse seenResponse = seen(List.of(address));
|
||||||
|
return seenResponse.isSeen(address);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -35,7 +35,7 @@ public class SparrowWalletStateSupplier implements WalletStateSupplier {
|
||||||
SamouraiAccount samouraiAccount = bipWallet.getAccount();
|
SamouraiAccount samouraiAccount = bipWallet.getAccount();
|
||||||
String key = mapKey(bipWallet, chain);
|
String key = mapKey(bipWallet, chain);
|
||||||
IIndexHandler indexHandler = indexHandlerWallets.get(key);
|
IIndexHandler indexHandler = indexHandlerWallets.get(key);
|
||||||
if (indexHandler == null) {
|
if(indexHandler == null) {
|
||||||
Wallet wallet = findWallet(samouraiAccount);
|
Wallet wallet = findWallet(samouraiAccount);
|
||||||
KeyPurpose keyPurpose = (chain == Chain.RECEIVE ? KeyPurpose.RECEIVE : KeyPurpose.CHANGE);
|
KeyPurpose keyPurpose = (chain == Chain.RECEIVE ? KeyPurpose.RECEIVE : KeyPurpose.CHANGE);
|
||||||
WalletNode walletNode = wallet.getNode(keyPurpose);
|
WalletNode walletNode = wallet.getNode(keyPurpose);
|
||||||
|
@ -123,7 +123,7 @@ public class SparrowWalletStateSupplier implements WalletStateSupplier {
|
||||||
private String mapKey(BipWallet bipWallet, Chain chain) {
|
private String mapKey(BipWallet bipWallet, Chain chain) {
|
||||||
SamouraiAccount samouraiAccount = bipWallet.getAccount();
|
SamouraiAccount samouraiAccount = bipWallet.getAccount();
|
||||||
BipDerivation derivation = bipWallet.getDerivation();
|
BipDerivation derivation = bipWallet.getDerivation();
|
||||||
return samouraiAccount.name()+"_"+derivation.getPurpose()+"_"+chain.getIndex();
|
return samouraiAccount.name() + "_" + derivation.getPurpose() + "_" + chain.getIndex();
|
||||||
}
|
}
|
||||||
|
|
||||||
private Wallet findWallet(SamouraiAccount samouraiAccount) {
|
private Wallet findWallet(SamouraiAccount samouraiAccount) {
|
||||||
|
|
|
@ -64,7 +64,6 @@ open module com.sparrowwallet.sparrow {
|
||||||
requires com.sparrowwallet.bokmakierie;
|
requires com.sparrowwallet.bokmakierie;
|
||||||
requires java.smartcardio;
|
requires java.smartcardio;
|
||||||
requires com.jcraft.jzlib;
|
requires com.jcraft.jzlib;
|
||||||
// samourai dependencies
|
|
||||||
requires com.samourai.whirlpool.client;
|
requires com.samourai.whirlpool.client;
|
||||||
requires com.samourai.whirlpool.protocol;
|
requires com.samourai.whirlpool.protocol;
|
||||||
requires com.samourai.extlibj;
|
requires com.samourai.extlibj;
|
||||||
|
|
Loading…
Reference in a new issue