mirror of
https://github.com/sparrowwallet/sparrow.git
synced 2024-12-25 13:16:44 +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'
|
||||
}
|
||||
implementation('com.sparrowwallet.bokmakierie:bokmakierie:1.0')
|
||||
implementation('io.samourai.code.whirlpool:whirlpool-client:1.0.0-beta10')
|
||||
implementation('io.samourai.code.wallet:java-http-client:2.0.0-beta3')
|
||||
implementation('io.samourai.code.whirlpool:whirlpool-client:1.0.0-beta13')
|
||||
implementation('io.samourai.code.wallet:java-http-client:2.0.0-beta4')
|
||||
implementation('io.reactivex.rxjava2:rxjava:2.2.15')
|
||||
implementation('io.reactivex.rxjava2:rxjavafx:2.2.2')
|
||||
implementation('org.apache.commons:commons-lang3:3.7')
|
||||
|
@ -490,7 +490,6 @@ extraJavaModuleInfo {
|
|||
exports('co.nstant.in.cbor.model')
|
||||
exports('co.nstant.in.cbor.builder')
|
||||
}
|
||||
// begin samourai dependencies
|
||||
module('commons-codec-1.10.jar', 'commons.codec', '1.10') {
|
||||
exports('org.apache.commons.codec')
|
||||
}
|
||||
|
@ -507,7 +506,6 @@ extraJavaModuleInfo {
|
|||
exports('com.lambdaworks.codec')
|
||||
exports('com.lambdaworks.crypto')
|
||||
}
|
||||
// end samourai dependencies
|
||||
module('okio-1.6.0.jar', 'com.squareup.okio', '1.6.0') {
|
||||
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) {
|
||||
if(mixFailReason != MixFailReason.CANCEL) {
|
||||
if(mixFailReason != MixFailReason.STOP_UTXO && !mixFailReason.isSilent()) {
|
||||
long elapsed = mixErrorTimestamp == null ? 0L : System.currentTimeMillis() - mixErrorTimestamp;
|
||||
if(elapsed >= ERROR_DISPLAY_MILLIS) {
|
||||
//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);
|
||||
setGraphic(progressIndicator);
|
||||
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);
|
||||
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 {
|
||||
setGraphic(null);
|
||||
setTooltip(null);
|
||||
|
|
|
@ -256,8 +256,11 @@ public class DbPersistence implements Persistence {
|
|||
}
|
||||
for(Sha256Hash txid : referencedTxIds) {
|
||||
BlockTransaction blkTx = wallet.getTransactions().get(txid);
|
||||
//May be null for a nested wallet if still updating
|
||||
if(blkTx != null) {
|
||||
blockTransactionDao.addOrUpdate(wallet, txid, blkTx);
|
||||
}
|
||||
}
|
||||
if(!dirtyPersistables.clearHistory) {
|
||||
DetachedLabelDao detachedLabelDao = handle.attach(DetachedLabelDao.class);
|
||||
detachedLabelDao.clearAndAddAll(wallet);
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
package com.sparrowwallet.sparrow.net;
|
||||
|
||||
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.Utils;
|
||||
import com.sparrowwallet.drongo.protocol.Sha256Hash;
|
||||
|
@ -158,7 +158,7 @@ public enum BroadcastSource {
|
|||
} catch(Exception e) {
|
||||
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());
|
||||
} catch(Exception e) {
|
||||
log.error("Could not post transaction via " + getName(), e);
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
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.event.ExchangeRatesUpdatedEvent;
|
||||
import javafx.concurrent.ScheduledService;
|
||||
|
@ -107,7 +107,7 @@ public enum ExchangeSource {
|
|||
if(log.isDebugEnabled()) {
|
||||
log.warn("Error retrieving historical currency rates", e);
|
||||
} 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);
|
||||
} else {
|
||||
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.IHttpClient;
|
||||
import com.samourai.wallet.util.AsyncUtil;
|
||||
import com.samourai.wallet.util.ThreadUtil;
|
||||
import com.samourai.whirlpool.client.utils.ClientUtils;
|
||||
import io.reactivex.Observable;
|
||||
import javafx.concurrent.Service;
|
||||
|
@ -17,25 +18,20 @@ public class HttpClientService extends JettyHttpClientService {
|
|||
private static final int REQUEST_TIMEOUT = 120000;
|
||||
|
||||
public HttpClientService(HostAndPort torProxy) {
|
||||
super(REQUEST_TIMEOUT,
|
||||
ClientUtils.USER_AGENT,
|
||||
new HttpProxySupplier(torProxy));
|
||||
super(REQUEST_TIMEOUT, ClientUtils.USER_AGENT, new HttpProxySupplier(torProxy));
|
||||
}
|
||||
|
||||
public <T> T requestJson(String url, Class<T> responseType, Map<String, String> headers) throws Exception {
|
||||
return getHttpClient(HttpUsage.BACKEND)
|
||||
.getJson(url, responseType, headers);
|
||||
return getHttpClient(HttpUsage.BACKEND).getJson(url, responseType, headers);
|
||||
}
|
||||
|
||||
public <T> Observable<Optional<T>> postJson(String url, Class<T> responseType, Map<String, String> headers, Object body) {
|
||||
return getHttpClient(HttpUsage.BACKEND)
|
||||
.postJson(url, responseType, headers, body).toObservable();
|
||||
return getHttpClient(HttpUsage.BACKEND).postJson(url, responseType, headers, body).toObservable();
|
||||
}
|
||||
|
||||
public String postString(String url, Map<String, String> headers, String contentType, String content) throws Exception {
|
||||
IHttpClient httpClient = getHttpClient(HttpUsage.BACKEND);
|
||||
return AsyncUtil.getInstance().blockingGet(
|
||||
httpClient.postString(url, headers, contentType, content)).get();
|
||||
return AsyncUtil.getInstance().blockingGet(httpClient.postString(url, headers, contentType, content)).get();
|
||||
}
|
||||
|
||||
public HostAndPort getTorProxy() {
|
||||
|
@ -64,6 +60,7 @@ public class HttpClientService extends JettyHttpClientService {
|
|||
protected Task<Boolean> createTask() {
|
||||
return new Task<>() {
|
||||
protected Boolean call() throws Exception {
|
||||
ThreadUtil.getInstance().getExecutorService().shutdown();
|
||||
httpClientService.stop();
|
||||
return true;
|
||||
}
|
||||
|
|
|
@ -21,7 +21,7 @@ public class HttpProxySupplier implements IHttpProxySupplier {
|
|||
if (hostAndPort == null) {
|
||||
return null;
|
||||
}
|
||||
// TODO verify
|
||||
|
||||
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.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.Transaction;
|
||||
import com.sparrowwallet.drongo.protocol.TransactionInput;
|
||||
|
@ -87,7 +87,7 @@ public class Payjoin {
|
|||
checkProposal(psbt, proposalPsbt, changeOutputIndex, maxAdditionalFeeContribution, allowOutputSubstitution);
|
||||
|
||||
return proposalPsbt;
|
||||
} catch(HttpException e) {
|
||||
} catch(HttpResponseException e) {
|
||||
Gson gson = new Gson();
|
||||
PayjoinReceiverError payjoinReceiverError = gson.fromJson(e.getResponseBody(), PayjoinReceiverError.class);
|
||||
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.SimpleObjectProperty;
|
||||
import javafx.collections.FXCollections;
|
||||
import javafx.concurrent.Service;
|
||||
import javafx.concurrent.Task;
|
||||
import javafx.event.ActionEvent;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.scene.control.*;
|
||||
|
@ -238,10 +240,19 @@ public class CounterpartyController extends SorobanController {
|
|||
SorobanWalletCounterparty sorobanWalletCounterparty = sorobanWalletService.getSorobanWalletCounterparty(cahootsWallet);
|
||||
sorobanWalletCounterparty.setTimeoutMeetingMs(TIMEOUT_MS);
|
||||
|
||||
try {
|
||||
// TODO run in background thread?
|
||||
SorobanRequestMessage requestMessage = sorobanWalletCounterparty.receiveMeetingRequest();
|
||||
|
||||
Service<SorobanRequestMessage> receiveMeetingService = new Service<>() {
|
||||
@Override
|
||||
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();
|
||||
CahootsType cahootsType = requestMessage.getType();
|
||||
updateMixPartner(paymentCodeInitiator, cahootsType);
|
||||
|
@ -261,11 +272,14 @@ public class CounterpartyController extends SorobanController {
|
|||
mixingPartner.setVisible(false);
|
||||
requestUserAttention();
|
||||
});
|
||||
} catch(Exception e) {
|
||||
});
|
||||
receiveMeetingService.setOnFailed(event -> {
|
||||
Throwable e = event.getSource().getException();
|
||||
log.error("Failed to receive meeting request", e);
|
||||
mixingPartner.setVisible(false);
|
||||
requestUserAttention();
|
||||
}
|
||||
});
|
||||
receiveMeetingService.start();
|
||||
}
|
||||
|
||||
private void updateMixPartner(PaymentCode paymentCodeInitiator, CahootsType cahootsType) {
|
||||
|
@ -308,6 +322,7 @@ public class CounterpartyController extends SorobanController {
|
|||
CahootsContext cahootsContext = CahootsContext.newCounterparty(cahootsWallet, cahootsType, account);
|
||||
Consumer<OnlineCahootsMessage> onProgress = cahootsMessage -> {
|
||||
if(cahootsMessage != null) {
|
||||
Platform.runLater(() -> {
|
||||
Cahoots cahoots = cahootsMessage.getCahoots();
|
||||
sorobanProgressBar.setProgress((double)(cahoots.getStep() + 1) / 5);
|
||||
|
||||
|
@ -328,12 +343,23 @@ public class CounterpartyController extends SorobanController {
|
|||
sorobanProgressLabel.setVisible(false);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
try {
|
||||
// TODO run in background thread?
|
||||
Cahoots result = sorobanWalletCounterparty.counterparty(cahootsContext, initiatorPaymentCode, onProgress);
|
||||
} catch (Exception error) {
|
||||
|
||||
Service<Cahoots> cahootsService = new Service<>() {
|
||||
@Override
|
||||
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);
|
||||
String cutFrom = "Exception: ";
|
||||
int index = error.getMessage().lastIndexOf(cutFrom);
|
||||
|
@ -341,7 +367,8 @@ public class CounterpartyController extends SorobanController {
|
|||
msg = msg.replace("#Cahoots", "mix transaction");
|
||||
step3Desc.setText(msg);
|
||||
sorobanProgressLabel.setVisible(false);
|
||||
}
|
||||
});
|
||||
cahootsService.start();
|
||||
} catch(Exception e) {
|
||||
log.error("Error creating mix transaction", e);
|
||||
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.PayNymService;
|
||||
import io.reactivex.Observable;
|
||||
import io.reactivex.rxjavafx.schedulers.JavaFxScheduler;
|
||||
import io.reactivex.schedulers.Schedulers;
|
||||
import javafx.application.Platform;
|
||||
import javafx.beans.property.ObjectProperty;
|
||||
import javafx.beans.property.SimpleObjectProperty;
|
||||
|
@ -47,6 +45,8 @@ import javafx.beans.property.SimpleStringProperty;
|
|||
import javafx.beans.property.StringProperty;
|
||||
import javafx.beans.value.ChangeListener;
|
||||
import javafx.collections.FXCollections;
|
||||
import javafx.concurrent.Service;
|
||||
import javafx.concurrent.Task;
|
||||
import javafx.event.ActionEvent;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.scene.control.*;
|
||||
|
@ -431,6 +431,7 @@ public class InitiatorController extends SorobanController {
|
|||
public void onResponse(SorobanResponseMessage sorobanResponse) throws Exception {
|
||||
super.onResponse(sorobanResponse);
|
||||
|
||||
Platform.runLater(() -> {
|
||||
requestUserAttention();
|
||||
if(sorobanResponse.isAccept()) {
|
||||
sorobanProgressBar.setProgress(0.1);
|
||||
|
@ -439,29 +440,36 @@ public class InitiatorController extends SorobanController {
|
|||
step2Desc.setText("Mix partner declined.");
|
||||
sorobanProgressLabel.setVisible(false);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onInteraction(OnlineSorobanInteraction interaction) throws Exception {
|
||||
SorobanInteraction originInteraction = interaction.getInteraction();
|
||||
if(originInteraction instanceof TxBroadcastInteraction) {
|
||||
Platform.runLater(() -> {
|
||||
try {
|
||||
Boolean accepted = (Boolean)Platform.enterNestedEventLoop(transactionAccepted);
|
||||
if(accepted) {
|
||||
interaction.sorobanAccept();
|
||||
} else {
|
||||
interaction.sorobanReject("Mix partner declined to broadcast the transaction.");
|
||||
}
|
||||
} catch(Exception e) {
|
||||
log.error("Error accepting Soroban interaction", e);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
throw new Exception("Unknown interaction: "+originInteraction.getTypeInteraction());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void progress(OnlineCahootsMessage message) {
|
||||
super.progress(message);
|
||||
public void progress(OnlineCahootsMessage cahootsMessage) {
|
||||
super.progress(cahootsMessage);
|
||||
|
||||
OnlineCahootsMessage cahootsMessage = (OnlineCahootsMessage)message;
|
||||
if(cahootsMessage != null) {
|
||||
Platform.runLater(() -> {
|
||||
Cahoots cahoots = cahootsMessage.getCahoots();
|
||||
sorobanProgressBar.setProgress((double)(cahoots.getStep() + 1) / 5);
|
||||
|
||||
|
@ -489,21 +497,33 @@ public class InitiatorController extends SorobanController {
|
|||
sorobanProgressLabel.setVisible(false);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
SorobanWalletService sorobanWalletService = soroban.getSorobanWalletService();
|
||||
sorobanProgressLabel.setText("Waiting for mix partner...");
|
||||
try {
|
||||
// TODO run in background thread?
|
||||
Cahoots result = sorobanWalletService.getSorobanWalletInitiator(cahootsWallet).meetAndInitiate(cahootsContext, paymentCodeCounterparty, listener);
|
||||
} catch (Exception error){
|
||||
|
||||
Service<Cahoots> cahootsService = new Service<>() {
|
||||
@Override
|
||||
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);
|
||||
step2Desc.setText(getErrorMessage(error));
|
||||
sorobanProgressLabel.setVisible(false);
|
||||
meetingFail.setVisible(true);
|
||||
requestUserAttention();
|
||||
}
|
||||
});
|
||||
cahootsService.start();
|
||||
}, error -> {
|
||||
log.error("Could not retrieve payment code", error);
|
||||
if(error.getMessage().endsWith("404")) {
|
||||
|
|
|
@ -79,26 +79,4 @@ public class Soroban {
|
|||
public SorobanWalletService getSorobanWalletService() {
|
||||
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) {
|
||||
for(WalletTabData walletTabData : event.getClosedWalletTabData()) {
|
||||
String walletId = walletTabData.getStorage().getWalletId(walletTabData.getWallet());
|
||||
Soroban soroban = 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();
|
||||
}
|
||||
sorobanMap.remove(walletId);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -26,13 +26,12 @@ import java.util.List;
|
|||
|
||||
public class SparrowCahootsWallet extends AbstractCahootsWallet {
|
||||
private final Wallet wallet;
|
||||
private HD_Wallet bip84w;
|
||||
private final HD_Wallet bip84w;
|
||||
private final int account;
|
||||
private List<CahootsUtxo> utxos;
|
||||
private final List<CahootsUtxo> utxos;
|
||||
|
||||
public SparrowCahootsWallet(ChainSupplier chainSupplier, Wallet wallet, HD_Wallet bip84w, int bip47Account) {
|
||||
super(chainSupplier, bip84w.getFingerprint(),
|
||||
new BIP47Wallet(bip84w).getAccount(bip47Account));
|
||||
super(chainSupplier, bip84w.getFingerprint(), new BIP47Wallet(bip84w).getAccount(bip47Account));
|
||||
this.wallet = wallet;
|
||||
this.bip84w = bip84w;
|
||||
this.account = wallet.getAccountIndex();
|
||||
|
@ -107,7 +106,7 @@ public class SparrowCahootsWallet extends AbstractCahootsWallet {
|
|||
throw new IllegalStateException("Cannot add BIP47 UTXO", e);
|
||||
}
|
||||
} 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());
|
||||
}
|
||||
|
||||
|
|
|
@ -43,7 +43,7 @@ public class MixTableCell extends TableCell {
|
|||
|
||||
private String getMixFail(UtxoEntry.MixStatus mixStatus) {
|
||||
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);
|
||||
}
|
||||
|
||||
|
|
|
@ -129,9 +129,10 @@ public class WalletTransactionsEntry extends Entry {
|
|||
private static void getWalletTransactions(Wallet wallet, Map<BlockTransaction, WalletTransaction> walletTransactionMap, WalletNode purposeNode) {
|
||||
KeyPurpose keyPurpose = purposeNode.getKeyPurpose();
|
||||
List<WalletNode> childNodes = new ArrayList<>(purposeNode.getChildren());
|
||||
Wallet transactionsWallet = wallet.isNested() ? wallet.getMasterWallet() : wallet;
|
||||
for(WalletNode addressNode : childNodes) {
|
||||
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
|
||||
if(inputTx != null) {
|
||||
WalletTransaction inputWalletTx = walletTransactionMap.get(inputTx);
|
||||
|
@ -142,7 +143,7 @@ public class WalletTransactionsEntry extends Entry {
|
|||
inputWalletTx.incoming.put(hashIndex, keyPurpose);
|
||||
|
||||
if(hashIndex.getSpentBy() != null) {
|
||||
BlockTransaction outputTx = wallet.getWalletTransaction(hashIndex.getSpentBy().getHash());
|
||||
BlockTransaction outputTx = transactionsWallet.getWalletTransaction(hashIndex.getSpentBy().getHash());
|
||||
if(outputTx != null) {
|
||||
WalletTransaction outputWalletTx = walletTransactionMap.get(outputTx);
|
||||
if(outputWalletTx == null) {
|
||||
|
|
|
@ -6,6 +6,7 @@ import com.sparrowwallet.drongo.wallet.WalletNode;
|
|||
import com.sparrowwallet.sparrow.AppServices;
|
||||
import com.sparrowwallet.sparrow.io.Config;
|
||||
import com.sparrowwallet.sparrow.whirlpool.Whirlpool;
|
||||
import javafx.application.Platform;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
|
@ -92,7 +93,8 @@ public class WalletUtxosEntry extends Entry {
|
|||
|
||||
calculateDuplicates();
|
||||
calculateDust();
|
||||
updateMixProgress();
|
||||
//Update mix status after SparrowUtxoSupplier has refreshed
|
||||
Platform.runLater(this::updateMixProgress);
|
||||
}
|
||||
|
||||
public long getBalance() {
|
||||
|
|
|
@ -33,10 +33,7 @@ import com.sparrowwallet.drongo.ExtendedKey;
|
|||
import com.sparrowwallet.drongo.KeyPurpose;
|
||||
import com.sparrowwallet.drongo.Network;
|
||||
import com.sparrowwallet.drongo.Utils;
|
||||
import com.sparrowwallet.drongo.protocol.Sha256Hash;
|
||||
import com.sparrowwallet.drongo.protocol.Transaction;
|
||||
import com.sparrowwallet.drongo.protocol.TransactionInput;
|
||||
import com.sparrowwallet.drongo.protocol.TransactionOutput;
|
||||
import com.sparrowwallet.drongo.protocol.*;
|
||||
import com.sparrowwallet.drongo.wallet.*;
|
||||
import com.sparrowwallet.sparrow.AppServices;
|
||||
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_RANDOM_FACTOR = 4;
|
||||
|
||||
|
||||
private final WhirlpoolWalletService whirlpoolWalletService;
|
||||
private final WhirlpoolWalletConfig config;
|
||||
private WhirlpoolInfo whirlpoolInfo;
|
||||
|
@ -89,11 +85,10 @@ public class Whirlpool {
|
|||
private final BooleanProperty stoppingProperty = new SimpleBooleanProperty(false);
|
||||
private final BooleanProperty mixingProperty = new SimpleBooleanProperty(false);
|
||||
|
||||
public Whirlpool() {
|
||||
public Whirlpool(Integer storedBlockHeight) {
|
||||
this.whirlpoolWalletService = new WhirlpoolWalletService();
|
||||
Integer storedBlockHeight = null; // TODO
|
||||
this.config = computeWhirlpoolWalletConfig(storedBlockHeight);
|
||||
this.whirlpoolInfo = null; // instanciated by getWhirlpoolInfo()
|
||||
this.whirlpoolInfo = null; // instantiated by getWhirlpoolInfo()
|
||||
|
||||
WhirlpoolEventService.getInstance().register(this);
|
||||
}
|
||||
|
@ -112,17 +107,14 @@ public class Whirlpool {
|
|||
}
|
||||
|
||||
private DataSourceConfig computeDataSourceConfig(Integer storedBlockHeight) {
|
||||
return new DataSourceConfig(
|
||||
SparrowMinerFeeSupplier.getInstance(),
|
||||
new SparrowChainSupplier(storedBlockHeight),
|
||||
BIP_FORMAT.PROVIDER,
|
||||
BIP_WALLETS.WHIRLPOOL);
|
||||
return new DataSourceConfig(SparrowMinerFeeSupplier.getInstance(), new SparrowChainSupplier(storedBlockHeight), BIP_FORMAT.PROVIDER, BIP_WALLETS.WHIRLPOOL);
|
||||
}
|
||||
|
||||
private WhirlpoolInfo getWhirlpoolInfo() {
|
||||
if(whirlpoolInfo == null) {
|
||||
whirlpoolInfo = new WhirlpoolInfo(SparrowMinerFeeSupplier.getInstance(), config);
|
||||
}
|
||||
|
||||
return whirlpoolInfo;
|
||||
}
|
||||
|
||||
|
@ -144,8 +136,7 @@ public class Whirlpool {
|
|||
|
||||
public Tx0 broadcastTx0(Pool pool, Collection<BlockTransactionHashIndex> utxos) throws Exception {
|
||||
WhirlpoolWallet whirlpoolWallet = getWhirlpoolWallet();
|
||||
whirlpoolWallet.startAsync().subscribeOn(Schedulers.io())
|
||||
.observeOn(JavaFxScheduler.platform());
|
||||
whirlpoolWallet.startAsync().subscribeOn(Schedulers.io()).observeOn(JavaFxScheduler.platform());
|
||||
UtxoSupplier utxoSupplier = whirlpoolWallet.getUtxoSupplier();
|
||||
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 {
|
||||
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();
|
||||
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) {
|
||||
throw new IllegalStateException("Could not create Whirlpool HD wallet ", e);
|
||||
}
|
||||
|
@ -264,9 +258,7 @@ public class Whirlpool {
|
|||
|
||||
public void refreshUtxos() {
|
||||
if(whirlpoolWalletService.whirlpoolWallet() != null) {
|
||||
whirlpoolWalletService.whirlpoolWallet().refreshUtxosAsync()
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(JavaFxScheduler.platform());
|
||||
whirlpoolWalletService.whirlpoolWallet().refreshUtxosAsync().subscribeOn(Schedulers.io()).observeOn(JavaFxScheduler.platform());
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -352,7 +344,6 @@ public class Whirlpool {
|
|||
|
||||
public void shutdown() {
|
||||
whirlpoolWalletService.closeWallet();
|
||||
AppServices.getHttpClientService().stop();
|
||||
}
|
||||
|
||||
public StartupService createStartupService() {
|
||||
|
@ -517,7 +508,12 @@ public class Whirlpool {
|
|||
int mixes = minMixes == null ? DEFAULT_MIXTO_MIN_MIXES : minMixes;
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
|
@ -715,10 +711,7 @@ public class Whirlpool {
|
|||
whirlpool.startingProperty.set(true);
|
||||
WhirlpoolWallet whirlpoolWallet = whirlpool.getWhirlpoolWallet();
|
||||
if(AppServices.onlineProperty().get()) {
|
||||
whirlpoolWallet.startAsync()
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(JavaFxScheduler.platform())
|
||||
.subscribe();
|
||||
whirlpoolWallet.startAsync().subscribeOn(Schedulers.io()).observeOn(JavaFxScheduler.platform()).subscribe();
|
||||
}
|
||||
|
||||
return whirlpoolWallet;
|
||||
|
|
|
@ -73,7 +73,8 @@ public class WhirlpoolServices {
|
|||
public Whirlpool getWhirlpool(String walletId) {
|
||||
Whirlpool whirlpool = whirlpoolMap.get(walletId);
|
||||
if(whirlpool == null) {
|
||||
whirlpool = new Whirlpool();
|
||||
Wallet wallet = AppServices.get().getWallet(walletId);
|
||||
whirlpool = new Whirlpool(wallet == null ? null : wallet.getStoredBlockHeight());
|
||||
whirlpoolMap.put(walletId, whirlpool);
|
||||
}
|
||||
|
||||
|
|
|
@ -3,19 +3,18 @@ package com.sparrowwallet.sparrow.whirlpool.dataSource;
|
|||
import com.google.common.eventbus.Subscribe;
|
||||
import com.samourai.wallet.api.backend.beans.WalletResponse;
|
||||
import com.samourai.wallet.chain.ChainSupplier;
|
||||
import com.sparrowwallet.drongo.Utils;
|
||||
import com.sparrowwallet.drongo.protocol.Sha256Hash;
|
||||
import com.sparrowwallet.sparrow.AppServices;
|
||||
import com.sparrowwallet.sparrow.EventManager;
|
||||
import com.sparrowwallet.sparrow.event.NewBlockEvent;
|
||||
|
||||
public class SparrowChainSupplier implements ChainSupplier {
|
||||
private int storedBlockHeight;
|
||||
private final int storedBlockHeight;
|
||||
private WalletResponse.InfoBlock latestBlock;
|
||||
|
||||
public SparrowChainSupplier(Integer storedBlockHeight) {
|
||||
this.storedBlockHeight = AppServices.getCurrentBlockHeight() == null ?
|
||||
(storedBlockHeight!=null?storedBlockHeight:0)
|
||||
: AppServices.getCurrentBlockHeight();
|
||||
this.storedBlockHeight = AppServices.getCurrentBlockHeight() == null ? (storedBlockHeight != null ? storedBlockHeight : 0) : AppServices.getCurrentBlockHeight();
|
||||
this.latestBlock = computeLatestBlock();
|
||||
EventManager.get().register(this);
|
||||
}
|
||||
|
@ -27,7 +26,8 @@ public class SparrowChainSupplier implements ChainSupplier {
|
|||
private WalletResponse.InfoBlock computeLatestBlock() {
|
||||
WalletResponse.InfoBlock latestBlock = new WalletResponse.InfoBlock();
|
||||
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();
|
||||
return latestBlock;
|
||||
}
|
||||
|
|
|
@ -40,7 +40,7 @@ public class SparrowDataSource extends AbstractDataSource {
|
|||
|
||||
private final ISeenBackend seenBackend;
|
||||
private final IPushTx pushTx;
|
||||
private SparrowUtxoSupplier utxoSupplier;
|
||||
private final SparrowUtxoSupplier utxoSupplier;
|
||||
|
||||
public SparrowDataSource(
|
||||
WhirlpoolWallet whirlpoolWallet,
|
||||
|
@ -57,7 +57,7 @@ public class SparrowDataSource extends AbstractDataSource {
|
|||
|
||||
private ISeenBackend computeSeenBackend(WhirlpoolWalletConfig whirlpoolWalletConfig) {
|
||||
IHttpClient httpClient = whirlpoolWalletConfig.getHttpClient(HttpUsage.BACKEND);
|
||||
ISeenBackend sparrowSeenBackend = new SparrowSeenBackend(httpClient);
|
||||
ISeenBackend sparrowSeenBackend = new SparrowSeenBackend(getWhirlpoolWallet().getWalletIdentifier(), httpClient);
|
||||
NetworkParameters params = whirlpoolWalletConfig.getSamouraiNetwork().getParams();
|
||||
return SeenBackendWithFallback.withOxt(sparrowSeenBackend, params);
|
||||
}
|
||||
|
|
|
@ -1,8 +1,10 @@
|
|||
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.IPostmixHandler;
|
||||
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.beans.IndexRange;
|
||||
import com.sparrowwallet.drongo.KeyPurpose;
|
||||
|
@ -12,21 +14,21 @@ import com.sparrowwallet.drongo.wallet.WalletNode;
|
|||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
// TODO maybe replace with XPubPostmixHandler
|
||||
public class SparrowPostmixHandler extends AbstractPostmixHandler {
|
||||
public class SparrowPostmixHandler implements IPostmixHandler {
|
||||
private static final Logger log = LoggerFactory.getLogger(SparrowPostmixHandler.class);
|
||||
|
||||
private final WhirlpoolWalletService whirlpoolWalletService;
|
||||
private final Wallet wallet;
|
||||
private final KeyPurpose keyPurpose;
|
||||
|
||||
protected MixDestination destination;
|
||||
|
||||
public SparrowPostmixHandler(WhirlpoolWalletService whirlpoolWalletService, Wallet wallet, KeyPurpose keyPurpose) {
|
||||
super(whirlpoolWalletService.whirlpoolWallet().getWalletStateSupplier().getIndexHandlerExternal(),
|
||||
whirlpoolWalletService.whirlpoolWallet().getConfig().getSamouraiNetwork().getParams());
|
||||
this.whirlpoolWalletService = whirlpoolWalletService;
|
||||
this.wallet = wallet;
|
||||
this.keyPurpose = keyPurpose;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected IndexRange getIndexRange() {
|
||||
return IndexRange.FULL;
|
||||
}
|
||||
|
@ -35,6 +37,23 @@ public class SparrowPostmixHandler extends AbstractPostmixHandler {
|
|||
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
|
||||
public MixDestination computeDestination(int index) throws Exception {
|
||||
// address
|
||||
|
@ -45,4 +64,23 @@ public class SparrowPostmixHandler extends AbstractPostmixHandler {
|
|||
log.info("Mixing to external xPub -> receiveAddress=" + address + ", path=" + 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.SeenResponse;
|
||||
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.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SeenResponse seen(Collection<String> addresses) throws Exception {
|
||||
Wallet wallet = AppServices.get().getWallet(walletId);
|
||||
Map<Address, WalletNode> addressMap = wallet.getWalletAddresses();
|
||||
for(Wallet childWallet : wallet.getChildWallets()) {
|
||||
if(!childWallet.isNested()) {
|
||||
addressMap.putAll(childWallet.getWalletAddresses());
|
||||
}
|
||||
}
|
||||
|
||||
Map<String,Boolean> map = new LinkedHashMap<>();
|
||||
for(String address : addresses) {
|
||||
map.put(address, seen(address));
|
||||
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);
|
||||
}
|
||||
|
||||
@Override
|
||||
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
|
||||
|
|
|
@ -64,7 +64,6 @@ open module com.sparrowwallet.sparrow {
|
|||
requires com.sparrowwallet.bokmakierie;
|
||||
requires java.smartcardio;
|
||||
requires com.jcraft.jzlib;
|
||||
// samourai dependencies
|
||||
requires com.samourai.whirlpool.client;
|
||||
requires com.samourai.whirlpool.protocol;
|
||||
requires com.samourai.extlibj;
|
||||
|
|
Loading…
Reference in a new issue