mirror of
https://github.com/haveno-dex/haveno.git
synced 2025-08-14 09:25:37 -04:00
Add API functions to initialize Haveno account (#216)
Co-authored-by: woodser@protonmail.com
This commit is contained in:
parent
dc4692d97a
commit
e3b9a9962b
81 changed files with 2755 additions and 1660 deletions
14
core/src/main/java/bisq/core/api/AccountServiceListener.java
Normal file
14
core/src/main/java/bisq/core/api/AccountServiceListener.java
Normal file
|
@ -0,0 +1,14 @@
|
|||
package bisq.core.api;
|
||||
|
||||
/**
|
||||
* Default account listener (takes no action).
|
||||
*/
|
||||
public class AccountServiceListener {
|
||||
public void onAppInitialized() {}
|
||||
public void onAccountCreated() {}
|
||||
public void onAccountOpened() {}
|
||||
public void onAccountClosed() {}
|
||||
public void onAccountRestored(Runnable onShutDown) {}
|
||||
public void onAccountDeleted(Runnable onShutDown) {}
|
||||
public void onPasswordChanged(String oldPassword, String newPassword) {}
|
||||
}
|
|
@ -1,50 +1,163 @@
|
|||
/*
|
||||
* This file is part of Haveno.
|
||||
*
|
||||
* Haveno is free software: you can redistribute it and/or modify it
|
||||
* under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or (at
|
||||
* your option) any later version.
|
||||
*
|
||||
* Haveno is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
|
||||
* License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with Haveno. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package bisq.core.api;
|
||||
|
||||
import javax.inject.Singleton;
|
||||
import static com.google.common.base.Preconditions.checkState;
|
||||
|
||||
import bisq.common.config.Config;
|
||||
import bisq.common.crypto.IncorrectPasswordException;
|
||||
import bisq.common.crypto.KeyRing;
|
||||
import bisq.common.crypto.KeyStorage;
|
||||
import bisq.common.file.FileUtil;
|
||||
import bisq.common.persistence.PersistenceManager;
|
||||
import bisq.common.util.ZipUtils;
|
||||
import java.io.File;
|
||||
import java.io.InputStream;
|
||||
import java.io.PipedInputStream;
|
||||
import java.io.PipedOutputStream;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.concurrent.CopyOnWriteArrayList;
|
||||
import java.util.function.Consumer;
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Singleton;
|
||||
import lombok.Getter;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
/**
|
||||
* @deprecated Should be replaced by actual implementation once it is available
|
||||
* Manages the account state. A created account must have a password which encrypts
|
||||
* all persistence in the PersistenceManager. As a result, opening the account requires
|
||||
* a correct password to be passed in to deserialize the account properties that are
|
||||
* persisted. It is possible to persist the objects without a password (legacy).
|
||||
*
|
||||
* Backup and restore flushes the persistence objects in the app folder and sends or
|
||||
* restores a zip stream.
|
||||
*/
|
||||
@Singleton
|
||||
@Deprecated
|
||||
@Slf4j
|
||||
public class CoreAccountService {
|
||||
|
||||
|
||||
private static final String DEFAULT_PASSWORD = "abctesting123";
|
||||
|
||||
private String password = DEFAULT_PASSWORD;
|
||||
|
||||
private final List<PasswordChangeListener> listeners = new CopyOnWriteArrayList<>();
|
||||
|
||||
|
||||
public String getPassword() {
|
||||
return password;
|
||||
|
||||
private final Config config;
|
||||
private final KeyStorage keyStorage;
|
||||
private final KeyRing keyRing;
|
||||
|
||||
@Getter
|
||||
private String password;
|
||||
private List<AccountServiceListener> listeners = new ArrayList<AccountServiceListener>();
|
||||
|
||||
@Inject
|
||||
public CoreAccountService(Config config,
|
||||
KeyStorage keyStorage,
|
||||
KeyRing keyRing) {
|
||||
this.config = config;
|
||||
this.keyStorage = keyStorage;
|
||||
this.keyRing = keyRing;
|
||||
}
|
||||
|
||||
public void setPassword(String newPassword) {
|
||||
String oldPassword = password;
|
||||
password = newPassword;
|
||||
notifyListenerAboutPasswordChange(oldPassword, newPassword);
|
||||
}
|
||||
|
||||
public void addPasswordChangeListener(PasswordChangeListener listener) {
|
||||
Objects.requireNonNull(listener, "listener");
|
||||
|
||||
public void addListener(AccountServiceListener listener) {
|
||||
listeners.add(listener);
|
||||
}
|
||||
|
||||
private void notifyListenerAboutPasswordChange(String oldPassword, String newPassword) {
|
||||
for (PasswordChangeListener listener : listeners) {
|
||||
listener.onPasswordChange(oldPassword, newPassword);
|
||||
|
||||
public boolean removeListener(AccountServiceListener listener) {
|
||||
return listeners.remove(listener);
|
||||
}
|
||||
|
||||
public boolean accountExists() {
|
||||
return keyStorage.allKeyFilesExist(); // public and private key pair indicate the existence of the account
|
||||
}
|
||||
|
||||
public boolean isAccountOpen() {
|
||||
return keyRing.isUnlocked() && accountExists();
|
||||
}
|
||||
|
||||
public void checkAccountOpen() {
|
||||
checkState(isAccountOpen(), "Account not open");
|
||||
}
|
||||
|
||||
public void createAccount(String password) {
|
||||
if (accountExists()) throw new IllegalStateException("Cannot create account if account already exists");
|
||||
keyRing.generateKeys(password);
|
||||
this.password = password;
|
||||
for (AccountServiceListener listener : listeners) listener.onAccountCreated();
|
||||
}
|
||||
|
||||
public void openAccount(String password) throws IncorrectPasswordException {
|
||||
if (!accountExists()) throw new IllegalStateException("Cannot open account if account does not exist");
|
||||
if (keyRing.unlockKeys(password, false)) {
|
||||
this.password = password;
|
||||
for (AccountServiceListener listener : listeners) listener.onAccountOpened();
|
||||
} else {
|
||||
throw new IllegalStateException("keyRing.unlockKeys() returned false, that should never happen");
|
||||
}
|
||||
}
|
||||
|
||||
public void changePassword(String password) {
|
||||
if (!isAccountOpen()) throw new IllegalStateException("Cannot change password on unopened account");
|
||||
keyStorage.saveKeyRing(keyRing, password);
|
||||
String oldPassword = this.password;
|
||||
this.password = password;
|
||||
for (AccountServiceListener listener : listeners) listener.onPasswordChanged(oldPassword, password);
|
||||
}
|
||||
|
||||
public void closeAccount() {
|
||||
if (!isAccountOpen()) throw new IllegalStateException("Cannot close unopened account");
|
||||
keyRing.lockKeys(); // closed account means the keys are locked
|
||||
for (AccountServiceListener listener : listeners) listener.onAccountClosed();
|
||||
}
|
||||
|
||||
public void backupAccount(int bufferSize, Consumer<InputStream> consume, Consumer<Exception> error) {
|
||||
if (!accountExists()) throw new IllegalStateException("Cannot backup non existing account");
|
||||
|
||||
public interface PasswordChangeListener {
|
||||
|
||||
void onPasswordChange(String oldPassword, String newPassword);
|
||||
|
||||
// flush all known persistence objects to disk
|
||||
PersistenceManager.flushAllDataToDiskAtBackup(() -> {
|
||||
try {
|
||||
File dataDir = new File(config.appDataDir.getPath());
|
||||
PipedInputStream in = new PipedInputStream(bufferSize); // pipe the serialized account object to stream which will be read by the consumer
|
||||
PipedOutputStream out = new PipedOutputStream(in);
|
||||
log.info("Zipping directory " + dataDir);
|
||||
new Thread(() -> {
|
||||
try {
|
||||
ZipUtils.zipDirToStream(dataDir, out, bufferSize);
|
||||
} catch (Exception ex) {
|
||||
error.accept(ex);
|
||||
}
|
||||
}).start();
|
||||
consume.accept(in);
|
||||
} catch (java.io.IOException err) {
|
||||
error.accept(err);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public void restoreAccount(InputStream inputStream, int bufferSize, Runnable onShutdown) throws Exception {
|
||||
if (accountExists()) throw new IllegalStateException("Cannot restore account if there is an existing account");
|
||||
File dataDir = new File(config.appDataDir.getPath());
|
||||
ZipUtils.unzipToDir(dataDir, inputStream, bufferSize);
|
||||
for (AccountServiceListener listener : listeners) listener.onAccountRestored(onShutdown);
|
||||
}
|
||||
|
||||
public void deleteAccount(Runnable onShutdown) {
|
||||
try {
|
||||
keyRing.lockKeys();
|
||||
for (AccountServiceListener listener : listeners) listener.onAccountDeleted(onShutdown);
|
||||
File dataDir = new File(config.appDataDir.getPath()); // TODO (woodser): deleting directory after gracefulShutdown() so services don't throw when they try to persist (e.g. XmrTxProofService), but gracefulShutdown() should honor read-only shutdown
|
||||
FileUtil.deleteDirectory(dataDir, null, false);
|
||||
} catch (Exception err) {
|
||||
throw new RuntimeException(err);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -21,6 +21,7 @@ import bisq.core.api.model.AddressBalanceInfo;
|
|||
import bisq.core.api.model.BalancesInfo;
|
||||
import bisq.core.api.model.MarketPriceInfo;
|
||||
import bisq.core.api.model.TxFeeRateInfo;
|
||||
import bisq.core.app.AppStartupState;
|
||||
import bisq.core.monetary.Price;
|
||||
import bisq.core.offer.Offer;
|
||||
import bisq.core.offer.OfferPayload;
|
||||
|
@ -33,6 +34,7 @@ import bisq.core.trade.statistics.TradeStatisticsManager;
|
|||
|
||||
import bisq.common.app.Version;
|
||||
import bisq.common.config.Config;
|
||||
import bisq.common.crypto.IncorrectPasswordException;
|
||||
import bisq.common.handlers.ErrorMessageHandler;
|
||||
import bisq.common.handlers.ResultHandler;
|
||||
|
||||
|
@ -46,6 +48,8 @@ import javax.inject.Singleton;
|
|||
|
||||
import com.google.common.util.concurrent.FutureCallback;
|
||||
|
||||
import java.io.InputStream;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
@ -58,7 +62,6 @@ import lombok.NonNull;
|
|||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
|
||||
|
||||
import monero.common.MoneroRpcConnection;
|
||||
import monero.wallet.model.MoneroDestination;
|
||||
import monero.wallet.model.MoneroTxWallet;
|
||||
|
@ -73,6 +76,8 @@ public class CoreApi {
|
|||
|
||||
@Getter
|
||||
private final Config config;
|
||||
private final AppStartupState appStartupState;
|
||||
private final CoreAccountService coreAccountService;
|
||||
private final CoreDisputeAgentsService coreDisputeAgentsService;
|
||||
private final CoreHelpService coreHelpService;
|
||||
private final CoreOffersService coreOffersService;
|
||||
|
@ -86,6 +91,8 @@ public class CoreApi {
|
|||
|
||||
@Inject
|
||||
public CoreApi(Config config,
|
||||
AppStartupState appStartupState,
|
||||
CoreAccountService coreAccountService,
|
||||
CoreDisputeAgentsService coreDisputeAgentsService,
|
||||
CoreHelpService coreHelpService,
|
||||
CoreOffersService coreOffersService,
|
||||
|
@ -97,6 +104,8 @@ public class CoreApi {
|
|||
CoreNotificationService notificationService,
|
||||
CoreMoneroConnectionsService coreMoneroConnectionsService) {
|
||||
this.config = config;
|
||||
this.appStartupState = appStartupState;
|
||||
this.coreAccountService = coreAccountService;
|
||||
this.coreDisputeAgentsService = coreDisputeAgentsService;
|
||||
this.coreHelpService = coreHelpService;
|
||||
this.coreOffersService = coreOffersService;
|
||||
|
@ -115,11 +124,197 @@ public class CoreApi {
|
|||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Dispute Agents
|
||||
// Help
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
public void registerDisputeAgent(String disputeAgentType, String registrationKey) {
|
||||
coreDisputeAgentsService.registerDisputeAgent(disputeAgentType, registrationKey);
|
||||
public String getMethodHelp(String methodName) {
|
||||
return coreHelpService.getMethodHelp(methodName);
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Account Service
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
public boolean accountExists() {
|
||||
return coreAccountService.accountExists();
|
||||
}
|
||||
|
||||
public boolean isAccountOpen() {
|
||||
return coreAccountService.isAccountOpen();
|
||||
}
|
||||
|
||||
public void createAccount(String password) {
|
||||
coreAccountService.createAccount(password);
|
||||
}
|
||||
|
||||
public void openAccount(String password) throws IncorrectPasswordException {
|
||||
coreAccountService.openAccount(password);
|
||||
}
|
||||
|
||||
public boolean isAppInitialized() {
|
||||
return appStartupState.isApplicationFullyInitialized();
|
||||
}
|
||||
|
||||
public void changePassword(String password) {
|
||||
coreAccountService.changePassword(password);
|
||||
}
|
||||
|
||||
public void closeAccount() {
|
||||
coreAccountService.closeAccount();
|
||||
}
|
||||
|
||||
public void deleteAccount(Runnable onShutdown) {
|
||||
coreAccountService.deleteAccount(onShutdown);
|
||||
}
|
||||
|
||||
public void backupAccount(int bufferSize, Consumer<InputStream> consume, Consumer<Exception> error) {
|
||||
coreAccountService.backupAccount(bufferSize, consume, error);
|
||||
}
|
||||
|
||||
public void restoreAccount(InputStream zipStream, int bufferSize, Runnable onShutdown) throws Exception {
|
||||
coreAccountService.restoreAccount(zipStream, bufferSize, onShutdown);
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Monero Connections
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
public void addMoneroConnection(MoneroRpcConnection connection) {
|
||||
coreMoneroConnectionsService.addConnection(connection);
|
||||
}
|
||||
|
||||
public void removeMoneroConnection(String connectionUri) {
|
||||
coreMoneroConnectionsService.removeConnection(connectionUri);
|
||||
}
|
||||
|
||||
public MoneroRpcConnection getMoneroConnection() {
|
||||
return coreMoneroConnectionsService.getConnection();
|
||||
}
|
||||
|
||||
public List<MoneroRpcConnection> getMoneroConnections() {
|
||||
return coreMoneroConnectionsService.getConnections();
|
||||
}
|
||||
|
||||
public void setMoneroConnection(String connectionUri) {
|
||||
coreMoneroConnectionsService.setConnection(connectionUri);
|
||||
}
|
||||
|
||||
public void setMoneroConnection(MoneroRpcConnection connection) {
|
||||
coreMoneroConnectionsService.setConnection(connection);
|
||||
}
|
||||
|
||||
public MoneroRpcConnection checkMoneroConnection() {
|
||||
return coreMoneroConnectionsService.checkConnection();
|
||||
}
|
||||
|
||||
public List<MoneroRpcConnection> checkMoneroConnections() {
|
||||
return coreMoneroConnectionsService.checkConnections();
|
||||
}
|
||||
|
||||
public void startCheckingMoneroConnection(Long refreshPeriod) {
|
||||
coreMoneroConnectionsService.startCheckingConnection(refreshPeriod);
|
||||
}
|
||||
|
||||
public void stopCheckingMoneroConnection() {
|
||||
coreMoneroConnectionsService.stopCheckingConnection();
|
||||
}
|
||||
|
||||
public MoneroRpcConnection getBestAvailableMoneroConnection() {
|
||||
return coreMoneroConnectionsService.getBestAvailableConnection();
|
||||
}
|
||||
|
||||
public void setMoneroConnectionAutoSwitch(boolean autoSwitch) {
|
||||
coreMoneroConnectionsService.setAutoSwitch(autoSwitch);
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Wallets
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
public BalancesInfo getBalances(String currencyCode) {
|
||||
return walletsService.getBalances(currencyCode);
|
||||
}
|
||||
|
||||
public String getNewDepositSubaddress() {
|
||||
return walletsService.getNewDepositSubaddress();
|
||||
}
|
||||
|
||||
public List<MoneroTxWallet> getXmrTxs() {
|
||||
return walletsService.getXmrTxs();
|
||||
}
|
||||
|
||||
public MoneroTxWallet createXmrTx(List<MoneroDestination> destinations) {
|
||||
return walletsService.createXmrTx(destinations);
|
||||
}
|
||||
|
||||
public String relayXmrTx(String metadata) {
|
||||
return walletsService.relayXmrTx(metadata);
|
||||
}
|
||||
|
||||
public long getAddressBalance(String addressString) {
|
||||
return walletsService.getAddressBalance(addressString);
|
||||
}
|
||||
|
||||
public AddressBalanceInfo getAddressBalanceInfo(String addressString) {
|
||||
return walletsService.getAddressBalanceInfo(addressString);
|
||||
}
|
||||
|
||||
public List<AddressBalanceInfo> getFundingAddresses() {
|
||||
return walletsService.getFundingAddresses();
|
||||
}
|
||||
|
||||
public void sendBtc(String address,
|
||||
String amount,
|
||||
String txFeeRate,
|
||||
String memo,
|
||||
FutureCallback<Transaction> callback) {
|
||||
walletsService.sendBtc(address, amount, txFeeRate, memo, callback);
|
||||
}
|
||||
|
||||
|
||||
public void getTxFeeRate(ResultHandler resultHandler) {
|
||||
walletsService.getTxFeeRate(resultHandler);
|
||||
}
|
||||
|
||||
public void setTxFeeRatePreference(long txFeeRate,
|
||||
ResultHandler resultHandler) {
|
||||
walletsService.setTxFeeRatePreference(txFeeRate, resultHandler);
|
||||
}
|
||||
|
||||
public void unsetTxFeeRatePreference(ResultHandler resultHandler) {
|
||||
walletsService.unsetTxFeeRatePreference(resultHandler);
|
||||
}
|
||||
|
||||
public TxFeeRateInfo getMostRecentTxFeeRateInfo() {
|
||||
return walletsService.getMostRecentTxFeeRateInfo();
|
||||
}
|
||||
|
||||
public Transaction getTransaction(String txId) {
|
||||
return walletsService.getTransaction(txId);
|
||||
}
|
||||
|
||||
public void setWalletPassword(String password, String newPassword) {
|
||||
walletsService.setWalletPassword(password, newPassword);
|
||||
}
|
||||
|
||||
public void lockWallet() {
|
||||
walletsService.lockWallet();
|
||||
}
|
||||
|
||||
public void unlockWallet(String password, long timeout) {
|
||||
walletsService.unlockWallet(password, timeout);
|
||||
}
|
||||
|
||||
public void removeWalletPassword(String password) {
|
||||
walletsService.removeWalletPassword(password);
|
||||
}
|
||||
|
||||
public List<TradeStatistics3> getTradeStatistics() {
|
||||
return new ArrayList<>(tradeStatisticsManager.getObservableTradeStatisticsSet());
|
||||
}
|
||||
|
||||
public int getNumConfirmationsForMostRecentTransaction(String addressString) {
|
||||
return walletsService.getNumConfirmationsForMostRecentTransaction(addressString);
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
@ -139,11 +334,11 @@ public class CoreApi {
|
|||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Help
|
||||
// Dispute Agents
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
public String getMethodHelp(String methodName) {
|
||||
return coreHelpService.getMethodHelp(methodName);
|
||||
public void registerDisputeAgent(String disputeAgentType, String registrationKey) {
|
||||
coreDisputeAgentsService.registerDisputeAgent(disputeAgentType, registrationKey);
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
@ -312,146 +507,4 @@ public class CoreApi {
|
|||
public String getTradeRole(String tradeId) {
|
||||
return coreTradesService.getTradeRole(tradeId);
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Wallets
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
public BalancesInfo getBalances(String currencyCode) {
|
||||
return walletsService.getBalances(currencyCode);
|
||||
}
|
||||
|
||||
public String getNewDepositSubaddress() {
|
||||
return walletsService.getNewDepositSubaddress();
|
||||
}
|
||||
|
||||
public List<MoneroTxWallet> getXmrTxs() {
|
||||
return walletsService.getXmrTxs();
|
||||
}
|
||||
|
||||
public MoneroTxWallet createXmrTx(List<MoneroDestination> destinations) {
|
||||
return walletsService.createXmrTx(destinations);
|
||||
}
|
||||
|
||||
public String relayXmrTx(String metadata) {
|
||||
return walletsService.relayXmrTx(metadata);
|
||||
}
|
||||
|
||||
public long getAddressBalance(String addressString) {
|
||||
return walletsService.getAddressBalance(addressString);
|
||||
}
|
||||
|
||||
public AddressBalanceInfo getAddressBalanceInfo(String addressString) {
|
||||
return walletsService.getAddressBalanceInfo(addressString);
|
||||
}
|
||||
|
||||
public List<AddressBalanceInfo> getFundingAddresses() {
|
||||
return walletsService.getFundingAddresses();
|
||||
}
|
||||
|
||||
public void sendBtc(String address,
|
||||
String amount,
|
||||
String txFeeRate,
|
||||
String memo,
|
||||
FutureCallback<Transaction> callback) {
|
||||
walletsService.sendBtc(address, amount, txFeeRate, memo, callback);
|
||||
}
|
||||
|
||||
|
||||
public void getTxFeeRate(ResultHandler resultHandler) {
|
||||
walletsService.getTxFeeRate(resultHandler);
|
||||
}
|
||||
|
||||
public void setTxFeeRatePreference(long txFeeRate,
|
||||
ResultHandler resultHandler) {
|
||||
walletsService.setTxFeeRatePreference(txFeeRate, resultHandler);
|
||||
}
|
||||
|
||||
public void unsetTxFeeRatePreference(ResultHandler resultHandler) {
|
||||
walletsService.unsetTxFeeRatePreference(resultHandler);
|
||||
}
|
||||
|
||||
public TxFeeRateInfo getMostRecentTxFeeRateInfo() {
|
||||
return walletsService.getMostRecentTxFeeRateInfo();
|
||||
}
|
||||
|
||||
public Transaction getTransaction(String txId) {
|
||||
return walletsService.getTransaction(txId);
|
||||
}
|
||||
|
||||
public void setWalletPassword(String password, String newPassword) {
|
||||
walletsService.setWalletPassword(password, newPassword);
|
||||
}
|
||||
|
||||
public void lockWallet() {
|
||||
walletsService.lockWallet();
|
||||
}
|
||||
|
||||
public void unlockWallet(String password, long timeout) {
|
||||
walletsService.unlockWallet(password, timeout);
|
||||
}
|
||||
|
||||
public void removeWalletPassword(String password) {
|
||||
walletsService.removeWalletPassword(password);
|
||||
}
|
||||
|
||||
public List<TradeStatistics3> getTradeStatistics() {
|
||||
return new ArrayList<>(tradeStatisticsManager.getObservableTradeStatisticsSet());
|
||||
}
|
||||
|
||||
public int getNumConfirmationsForMostRecentTransaction(String addressString) {
|
||||
return walletsService.getNumConfirmationsForMostRecentTransaction(addressString);
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Monero Connections
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
public void addMoneroConnection(MoneroRpcConnection connection) {
|
||||
coreMoneroConnectionsService.addConnection(connection);
|
||||
}
|
||||
|
||||
public void removeMoneroConnection(String connectionUri) {
|
||||
coreMoneroConnectionsService.removeConnection(connectionUri);
|
||||
}
|
||||
|
||||
public MoneroRpcConnection getMoneroConnection() {
|
||||
return coreMoneroConnectionsService.getConnection();
|
||||
}
|
||||
|
||||
public List<MoneroRpcConnection> getMoneroConnections() {
|
||||
return coreMoneroConnectionsService.getConnections();
|
||||
}
|
||||
|
||||
public void setMoneroConnection(String connectionUri) {
|
||||
coreMoneroConnectionsService.setConnection(connectionUri);
|
||||
}
|
||||
|
||||
public void setMoneroConnection(MoneroRpcConnection connection) {
|
||||
coreMoneroConnectionsService.setConnection(connection);
|
||||
}
|
||||
|
||||
public MoneroRpcConnection checkMoneroConnection() {
|
||||
return coreMoneroConnectionsService.checkConnection();
|
||||
}
|
||||
|
||||
public List<MoneroRpcConnection> checkMoneroConnections() {
|
||||
return coreMoneroConnectionsService.checkConnections();
|
||||
}
|
||||
|
||||
public void startCheckingMoneroConnection(Long refreshPeriod) {
|
||||
coreMoneroConnectionsService.startCheckingConnection(refreshPeriod);
|
||||
}
|
||||
|
||||
public void stopCheckingMoneroConnection() {
|
||||
coreMoneroConnectionsService.stopCheckingConnection();
|
||||
}
|
||||
|
||||
public MoneroRpcConnection getBestAvailableMoneroConnection() {
|
||||
return coreMoneroConnectionsService.getBestAvailableConnection();
|
||||
}
|
||||
|
||||
public void setMoneroConnectionAutoSwitch(boolean autoSwitch) {
|
||||
coreMoneroConnectionsService.setAutoSwitch(autoSwitch);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,21 +1,38 @@
|
|||
package bisq.core.api;
|
||||
|
||||
import bisq.common.UserThread;
|
||||
import bisq.core.btc.model.EncryptedConnectionList;
|
||||
import bisq.core.btc.setup.DownloadListener;
|
||||
import bisq.core.btc.setup.WalletsSetup;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
import javafx.beans.property.IntegerProperty;
|
||||
import javafx.beans.property.LongProperty;
|
||||
import javafx.beans.property.ObjectProperty;
|
||||
import javafx.beans.property.ReadOnlyDoubleProperty;
|
||||
import javafx.beans.property.ReadOnlyIntegerProperty;
|
||||
import javafx.beans.property.ReadOnlyObjectProperty;
|
||||
import javafx.beans.property.SimpleIntegerProperty;
|
||||
import javafx.beans.property.SimpleLongProperty;
|
||||
import javafx.beans.property.SimpleObjectProperty;
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Singleton;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import monero.common.MoneroConnectionManager;
|
||||
import monero.common.MoneroConnectionManagerListener;
|
||||
import monero.common.MoneroRpcConnection;
|
||||
import monero.daemon.MoneroDaemon;
|
||||
import monero.daemon.MoneroDaemonRpc;
|
||||
import monero.daemon.model.MoneroPeer;
|
||||
|
||||
@Slf4j
|
||||
@Singleton
|
||||
public class CoreMoneroConnectionsService {
|
||||
public final class CoreMoneroConnectionsService {
|
||||
|
||||
// TODO: this connection manager should update app status, don't poll in WalletsSetup every 30 seconds
|
||||
private static final long DEFAULT_REFRESH_PERIOD = 15_000L; // check the connection every 15 seconds per default
|
||||
private static final int MIN_BROADCAST_CONNECTIONS = 0; // TODO: 0 for stagenet, 5+ for mainnet
|
||||
private static final long DAEMON_REFRESH_PERIOD_MS = 15000L; // check connection periodically in ms
|
||||
private static final long DAEMON_INFO_POLL_PERIOD_MS = 20000L; // collect daemon info periodically in ms
|
||||
|
||||
// TODO (woodser): support each network type, move to config, remove localhost authentication
|
||||
private static final List<MoneroRpcConnection> DEFAULT_CONNECTIONS = Arrays.asList(
|
||||
|
@ -24,21 +41,222 @@ public class CoreMoneroConnectionsService {
|
|||
);
|
||||
|
||||
private final Object lock = new Object();
|
||||
private final CoreAccountService accountService;
|
||||
private final MoneroConnectionManager connectionManager;
|
||||
private final EncryptedConnectionList connectionList;
|
||||
private final ObjectProperty<List<MoneroPeer>> peers = new SimpleObjectProperty<>();
|
||||
private final IntegerProperty numPeers = new SimpleIntegerProperty(0);
|
||||
private final LongProperty chainHeight = new SimpleLongProperty(0);
|
||||
private final DownloadListener downloadListener = new DownloadListener();
|
||||
|
||||
private MoneroDaemon daemon;
|
||||
private boolean isInitialized = false;
|
||||
|
||||
@Inject
|
||||
public CoreMoneroConnectionsService(MoneroConnectionManager connectionManager,
|
||||
public CoreMoneroConnectionsService(WalletsSetup walletsSetup,
|
||||
CoreAccountService accountService,
|
||||
MoneroConnectionManager connectionManager,
|
||||
EncryptedConnectionList connectionList) {
|
||||
this.accountService = accountService;
|
||||
this.connectionManager = connectionManager;
|
||||
this.connectionList = connectionList;
|
||||
|
||||
// initialize after account open and basic setup
|
||||
walletsSetup.addSetupTaskHandler(() -> { // TODO: use something better than legacy WalletSetup for notification to initialize
|
||||
|
||||
// initialize from connections read from disk
|
||||
initialize();
|
||||
|
||||
// listen for account to be opened or password changed
|
||||
accountService.addListener(new AccountServiceListener() {
|
||||
|
||||
@Override
|
||||
public void onAccountOpened() {
|
||||
try {
|
||||
log.info(getClass() + ".onAccountOpened() called");
|
||||
initialize();
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPasswordChanged(String oldPassword, String newPassword) {
|
||||
log.info(getClass() + ".onPasswordChanged({}, {}) called", oldPassword, newPassword);
|
||||
connectionList.changePassword(oldPassword, newPassword);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
public void initialize() {
|
||||
// ------------------------ CONNECTION MANAGEMENT -------------------------
|
||||
|
||||
public MoneroDaemon getDaemon() {
|
||||
accountService.checkAccountOpen();
|
||||
return this.daemon;
|
||||
}
|
||||
|
||||
public void addListener(MoneroConnectionManagerListener listener) {
|
||||
synchronized (lock) {
|
||||
accountService.checkAccountOpen();
|
||||
connectionManager.addListener(listener);
|
||||
}
|
||||
}
|
||||
|
||||
public void addConnection(MoneroRpcConnection connection) {
|
||||
synchronized (lock) {
|
||||
accountService.checkAccountOpen();
|
||||
connectionList.addConnection(connection);
|
||||
connectionManager.addConnection(connection);
|
||||
}
|
||||
}
|
||||
|
||||
public void removeConnection(String uri) {
|
||||
synchronized (lock) {
|
||||
accountService.checkAccountOpen();
|
||||
connectionList.removeConnection(uri);
|
||||
connectionManager.removeConnection(uri);
|
||||
}
|
||||
}
|
||||
|
||||
public MoneroRpcConnection getConnection() {
|
||||
synchronized (lock) {
|
||||
accountService.checkAccountOpen();
|
||||
return connectionManager.getConnection();
|
||||
}
|
||||
}
|
||||
|
||||
public List<MoneroRpcConnection> getConnections() {
|
||||
synchronized (lock) {
|
||||
accountService.checkAccountOpen();
|
||||
return connectionManager.getConnections();
|
||||
}
|
||||
}
|
||||
|
||||
public void setConnection(String connectionUri) {
|
||||
synchronized (lock) {
|
||||
accountService.checkAccountOpen();
|
||||
connectionManager.setConnection(connectionUri); // listener will update connection list
|
||||
}
|
||||
}
|
||||
|
||||
public void setConnection(MoneroRpcConnection connection) {
|
||||
synchronized (lock) {
|
||||
accountService.checkAccountOpen();
|
||||
connectionManager.setConnection(connection); // listener will update connection list
|
||||
}
|
||||
}
|
||||
|
||||
public MoneroRpcConnection checkConnection() {
|
||||
synchronized (lock) {
|
||||
accountService.checkAccountOpen();
|
||||
connectionManager.checkConnection();
|
||||
return getConnection();
|
||||
}
|
||||
}
|
||||
|
||||
public List<MoneroRpcConnection> checkConnections() {
|
||||
synchronized (lock) {
|
||||
accountService.checkAccountOpen();
|
||||
connectionManager.checkConnections();
|
||||
return getConnections();
|
||||
}
|
||||
}
|
||||
|
||||
public void startCheckingConnection(Long refreshPeriod) {
|
||||
synchronized (lock) {
|
||||
accountService.checkAccountOpen();
|
||||
connectionManager.startCheckingConnection(refreshPeriod == null ? DAEMON_REFRESH_PERIOD_MS : refreshPeriod);
|
||||
connectionList.setRefreshPeriod(refreshPeriod);
|
||||
}
|
||||
}
|
||||
|
||||
public void stopCheckingConnection() {
|
||||
synchronized (lock) {
|
||||
accountService.checkAccountOpen();
|
||||
connectionManager.stopCheckingConnection();
|
||||
connectionList.setRefreshPeriod(-1L);
|
||||
}
|
||||
}
|
||||
|
||||
public MoneroRpcConnection getBestAvailableConnection() {
|
||||
synchronized (lock) {
|
||||
accountService.checkAccountOpen();
|
||||
return connectionManager.getBestAvailableConnection();
|
||||
}
|
||||
}
|
||||
|
||||
public void setAutoSwitch(boolean autoSwitch) {
|
||||
synchronized (lock) {
|
||||
accountService.checkAccountOpen();
|
||||
connectionManager.setAutoSwitch(autoSwitch);
|
||||
connectionList.setAutoSwitch(autoSwitch);
|
||||
}
|
||||
}
|
||||
|
||||
// ----------------------------- APP METHODS ------------------------------
|
||||
|
||||
public boolean isChainHeightSyncedWithinTolerance() {
|
||||
if (daemon == null) return false;
|
||||
Long targetHeight = daemon.getSyncInfo().getTargetHeight();
|
||||
if (targetHeight == 0) return true; // monero-daemon-rpc sync_info's target_height returns 0 when node is fully synced
|
||||
long currentHeight = chainHeight.get();
|
||||
if (Math.abs(targetHeight - currentHeight) <= 3) {
|
||||
return true;
|
||||
}
|
||||
log.warn("Our chain height: {} is out of sync with peer nodes chain height: {}", chainHeight.get(), targetHeight);
|
||||
return false;
|
||||
}
|
||||
|
||||
public ReadOnlyIntegerProperty numPeersProperty() {
|
||||
return numPeers;
|
||||
}
|
||||
|
||||
public ReadOnlyObjectProperty<List<MoneroPeer>> peerConnectionsProperty() {
|
||||
return peers;
|
||||
}
|
||||
|
||||
public boolean hasSufficientPeersForBroadcast() {
|
||||
return numPeers.get() >= getMinBroadcastConnections();
|
||||
}
|
||||
|
||||
public LongProperty chainHeightProperty() {
|
||||
return chainHeight;
|
||||
}
|
||||
|
||||
public ReadOnlyDoubleProperty downloadPercentageProperty() {
|
||||
return downloadListener.percentageProperty();
|
||||
}
|
||||
|
||||
public int getMinBroadcastConnections() {
|
||||
return MIN_BROADCAST_CONNECTIONS;
|
||||
}
|
||||
|
||||
public boolean isDownloadComplete() {
|
||||
return downloadPercentageProperty().get() == 1d;
|
||||
}
|
||||
|
||||
/**
|
||||
* Signals that both the daemon and wallet have synced.
|
||||
*
|
||||
* TODO: separate daemon and wallet download/done listeners
|
||||
*/
|
||||
public void doneDownload() {
|
||||
downloadListener.doneDownload();
|
||||
}
|
||||
|
||||
// ------------------------------- HELPERS --------------------------------
|
||||
|
||||
private void initialize() {
|
||||
synchronized (lock) {
|
||||
|
||||
// reset connection manager's connections and listeners
|
||||
connectionManager.reset();
|
||||
|
||||
// load connections
|
||||
connectionList.getConnections().forEach(connectionManager::addConnection);
|
||||
log.info("Read " + connectionList.getConnections().size() + " connections from disk");
|
||||
|
||||
// add default connections
|
||||
for (MoneroRpcConnection connection : DEFAULT_CONNECTIONS) {
|
||||
|
@ -50,24 +268,38 @@ public class CoreMoneroConnectionsService {
|
|||
connectionList.getCurrentConnectionUri().ifPresentOrElse(connectionManager::setConnection, () -> {
|
||||
connectionManager.setConnection(DEFAULT_CONNECTIONS.get(0).getUri()); // default to localhost
|
||||
});
|
||||
|
||||
// initialize daemon
|
||||
daemon = new MoneroDaemonRpc(connectionManager.getConnection());
|
||||
updateDaemonInfo();
|
||||
|
||||
// restore configuration
|
||||
connectionManager.setAutoSwitch(connectionList.getAutoSwitch());
|
||||
long refreshPeriod = connectionList.getRefreshPeriod();
|
||||
if (refreshPeriod > 0) connectionManager.startCheckingConnection(refreshPeriod);
|
||||
else if (refreshPeriod == 0) connectionManager.startCheckingConnection(DEFAULT_REFRESH_PERIOD);
|
||||
else if (refreshPeriod == 0) connectionManager.startCheckingConnection(DAEMON_REFRESH_PERIOD_MS);
|
||||
else checkConnection();
|
||||
|
||||
// register connection change listener
|
||||
connectionManager.addListener(this::onConnectionChanged);
|
||||
// run once
|
||||
if (!isInitialized) {
|
||||
|
||||
// register connection change listener
|
||||
connectionManager.addListener(this::onConnectionChanged);
|
||||
|
||||
// poll daemon periodically
|
||||
startPollingDaemon();
|
||||
isInitialized = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private void onConnectionChanged(MoneroRpcConnection currentConnection) {
|
||||
synchronized (lock) {
|
||||
if (currentConnection == null) {
|
||||
daemon = null;
|
||||
connectionList.setCurrentConnectionUri(null);
|
||||
} else {
|
||||
daemon = new MoneroDaemonRpc(connectionManager.getConnection());
|
||||
connectionList.removeConnection(currentConnection.getUri());
|
||||
connectionList.addConnection(currentConnection);
|
||||
connectionList.setCurrentConnectionUri(currentConnection.getUri());
|
||||
|
@ -75,88 +307,26 @@ public class CoreMoneroConnectionsService {
|
|||
}
|
||||
}
|
||||
|
||||
public void addConnectionListener(MoneroConnectionManagerListener listener) {
|
||||
synchronized (lock) {
|
||||
connectionManager.addListener(listener);
|
||||
private void startPollingDaemon() {
|
||||
UserThread.runPeriodically(() -> {
|
||||
updateDaemonInfo();
|
||||
}, DAEMON_INFO_POLL_PERIOD_MS / 1000l);
|
||||
}
|
||||
|
||||
private void updateDaemonInfo() {
|
||||
try {
|
||||
if (daemon == null) throw new RuntimeException("No daemon connection");
|
||||
peers.set(getOnlinePeers());
|
||||
numPeers.set(peers.get().size());
|
||||
chainHeight.set(daemon.getHeight());
|
||||
} catch (Exception e) {
|
||||
log.warn("Could not update daemon info: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
public void addConnection(MoneroRpcConnection connection) {
|
||||
synchronized (lock) {
|
||||
connectionList.addConnection(connection);
|
||||
connectionManager.addConnection(connection);
|
||||
}
|
||||
}
|
||||
|
||||
public void removeConnection(String uri) {
|
||||
synchronized (lock) {
|
||||
connectionList.removeConnection(uri);
|
||||
connectionManager.removeConnection(uri);
|
||||
}
|
||||
}
|
||||
|
||||
public MoneroRpcConnection getConnection() {
|
||||
synchronized (lock) {
|
||||
return connectionManager.getConnection();
|
||||
}
|
||||
}
|
||||
|
||||
public List<MoneroRpcConnection> getConnections() {
|
||||
synchronized (lock) {
|
||||
return connectionManager.getConnections();
|
||||
}
|
||||
}
|
||||
|
||||
public void setConnection(String connectionUri) {
|
||||
synchronized (lock) {
|
||||
connectionManager.setConnection(connectionUri); // listener will update connection list
|
||||
}
|
||||
}
|
||||
|
||||
public void setConnection(MoneroRpcConnection connection) {
|
||||
synchronized (lock) {
|
||||
connectionManager.setConnection(connection); // listener will update connection list
|
||||
}
|
||||
}
|
||||
|
||||
public MoneroRpcConnection checkConnection() {
|
||||
synchronized (lock) {
|
||||
connectionManager.checkConnection();
|
||||
return getConnection();
|
||||
}
|
||||
}
|
||||
|
||||
public List<MoneroRpcConnection> checkConnections() {
|
||||
synchronized (lock) {
|
||||
connectionManager.checkConnections();
|
||||
return getConnections();
|
||||
}
|
||||
}
|
||||
|
||||
public void startCheckingConnection(Long refreshPeriod) {
|
||||
synchronized (lock) {
|
||||
connectionManager.startCheckingConnection(refreshPeriod == null ? DEFAULT_REFRESH_PERIOD : refreshPeriod);
|
||||
connectionList.setRefreshPeriod(refreshPeriod);
|
||||
}
|
||||
}
|
||||
|
||||
public void stopCheckingConnection() {
|
||||
synchronized (lock) {
|
||||
connectionManager.stopCheckingConnection();
|
||||
connectionList.setRefreshPeriod(-1L);
|
||||
}
|
||||
}
|
||||
|
||||
public MoneroRpcConnection getBestAvailableConnection() {
|
||||
synchronized (lock) {
|
||||
return connectionManager.getBestAvailableConnection();
|
||||
}
|
||||
}
|
||||
|
||||
public void setAutoSwitch(boolean autoSwitch) {
|
||||
synchronized (lock) {
|
||||
connectionManager.setAutoSwitch(autoSwitch);
|
||||
connectionList.setAutoSwitch(autoSwitch);
|
||||
}
|
||||
private List<MoneroPeer> getOnlinePeers() {
|
||||
return daemon.getPeers().stream()
|
||||
.filter(peer -> peer.isOnline())
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -40,7 +40,14 @@ public class CoreNotificationService {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public void sendAppInitializedNotification() {
|
||||
sendNotification(NotificationMessage.newBuilder()
|
||||
.setType(NotificationType.APP_INITIALIZED)
|
||||
.setTimestamp(System.currentTimeMillis())
|
||||
.build());
|
||||
}
|
||||
|
||||
public void sendTradeNotification(Trade trade, String title, String message) {
|
||||
sendNotification(NotificationMessage.newBuilder()
|
||||
.setType(NotificationType.TRADE_UPDATE)
|
||||
|
|
|
@ -89,6 +89,7 @@ import monero.wallet.model.MoneroTxWallet;
|
|||
class CoreWalletsService {
|
||||
|
||||
private final AppStartupState appStartupState;
|
||||
private final CoreAccountService accountService;
|
||||
private final CoreContext coreContext;
|
||||
private final Balances balances;
|
||||
private final WalletsManager walletsManager;
|
||||
|
@ -110,6 +111,7 @@ class CoreWalletsService {
|
|||
@Inject
|
||||
public CoreWalletsService(AppStartupState appStartupState,
|
||||
CoreContext coreContext,
|
||||
CoreAccountService accountService,
|
||||
Balances balances,
|
||||
WalletsManager walletsManager,
|
||||
WalletsSetup walletsSetup,
|
||||
|
@ -120,6 +122,7 @@ class CoreWalletsService {
|
|||
Preferences preferences) {
|
||||
this.appStartupState = appStartupState;
|
||||
this.coreContext = coreContext;
|
||||
this.accountService = accountService;
|
||||
this.balances = balances;
|
||||
this.walletsManager = walletsManager;
|
||||
this.walletsSetup = walletsSetup;
|
||||
|
@ -141,6 +144,7 @@ class CoreWalletsService {
|
|||
}
|
||||
|
||||
BalancesInfo getBalances(String currencyCode) {
|
||||
accountService.checkAccountOpen();
|
||||
verifyWalletCurrencyCodeIsValid(currencyCode);
|
||||
verifyWalletsAreAvailable();
|
||||
verifyEncryptedWalletIsUnlocked();
|
||||
|
@ -158,14 +162,17 @@ class CoreWalletsService {
|
|||
}
|
||||
|
||||
String getNewDepositSubaddress() {
|
||||
accountService.checkAccountOpen();
|
||||
return xmrWalletService.getWallet().createSubaddress(0).getAddress();
|
||||
}
|
||||
|
||||
List<MoneroTxWallet> getXmrTxs(){
|
||||
List<MoneroTxWallet> getXmrTxs() {
|
||||
accountService.checkAccountOpen();
|
||||
return xmrWalletService.getWallet().getTxs();
|
||||
}
|
||||
|
||||
MoneroTxWallet createXmrTx(List<MoneroDestination> destinations) {
|
||||
accountService.checkAccountOpen();
|
||||
verifyWalletsAreAvailable();
|
||||
verifyEncryptedWalletIsUnlocked();
|
||||
try {
|
||||
|
@ -177,6 +184,7 @@ class CoreWalletsService {
|
|||
}
|
||||
|
||||
String relayXmrTx(String metadata) {
|
||||
accountService.checkAccountOpen();
|
||||
verifyWalletsAreAvailable();
|
||||
verifyEncryptedWalletIsUnlocked();
|
||||
try {
|
||||
|
|
|
@ -12,7 +12,7 @@ import lombok.Value;
|
|||
@Builder(toBuilder = true)
|
||||
public class EncryptedConnection implements PersistablePayload {
|
||||
|
||||
String uri;
|
||||
String url;
|
||||
String username;
|
||||
byte[] encryptedPassword;
|
||||
byte[] encryptionSalt;
|
||||
|
@ -21,7 +21,7 @@ public class EncryptedConnection implements PersistablePayload {
|
|||
@Override
|
||||
public protobuf.EncryptedConnection toProtoMessage() {
|
||||
return protobuf.EncryptedConnection.newBuilder()
|
||||
.setUri(uri)
|
||||
.setUrl(url)
|
||||
.setUsername(username)
|
||||
.setEncryptedPassword(ByteString.copyFrom(encryptedPassword))
|
||||
.setEncryptionSalt(ByteString.copyFrom(encryptionSalt))
|
||||
|
@ -31,7 +31,7 @@ public class EncryptedConnection implements PersistablePayload {
|
|||
|
||||
public static EncryptedConnection fromProto(protobuf.EncryptedConnection encryptedConnection) {
|
||||
return new EncryptedConnection(
|
||||
encryptedConnection.getUri(),
|
||||
encryptedConnection.getUrl(),
|
||||
encryptedConnection.getUsername(),
|
||||
encryptedConnection.getEncryptedPassword().toByteArray(),
|
||||
encryptedConnection.getEncryptionSalt().toByteArray(),
|
||||
|
|
|
@ -17,22 +17,18 @@
|
|||
|
||||
package bisq.core.app;
|
||||
|
||||
import bisq.core.btc.setup.WalletsSetup;
|
||||
|
||||
import bisq.core.api.CoreMoneroConnectionsService;
|
||||
import bisq.core.api.CoreNotificationService;
|
||||
import bisq.network.p2p.BootstrapListener;
|
||||
import bisq.network.p2p.P2PService;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Singleton;
|
||||
|
||||
import org.fxmisc.easybind.EasyBind;
|
||||
import org.fxmisc.easybind.monadic.MonadicBinding;
|
||||
|
||||
import javafx.beans.property.BooleanProperty;
|
||||
import javafx.beans.property.ReadOnlyBooleanProperty;
|
||||
import javafx.beans.property.SimpleBooleanProperty;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Singleton;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.fxmisc.easybind.EasyBind;
|
||||
import org.fxmisc.easybind.monadic.MonadicBinding;
|
||||
|
||||
/**
|
||||
* We often need to wait until network and wallet is ready or other combination of startup states.
|
||||
|
@ -53,7 +49,9 @@ public class AppStartupState {
|
|||
private final BooleanProperty hasSufficientPeersForBroadcast = new SimpleBooleanProperty();
|
||||
|
||||
@Inject
|
||||
public AppStartupState(WalletsSetup walletsSetup, P2PService p2PService) {
|
||||
public AppStartupState(CoreNotificationService notificationService,
|
||||
CoreMoneroConnectionsService connectionsService,
|
||||
P2PService p2PService) {
|
||||
|
||||
p2PService.addP2PServiceListener(new BootstrapListener() {
|
||||
@Override
|
||||
|
@ -62,13 +60,13 @@ public class AppStartupState {
|
|||
}
|
||||
});
|
||||
|
||||
walletsSetup.downloadPercentageProperty().addListener((observable, oldValue, newValue) -> {
|
||||
if (walletsSetup.isDownloadComplete())
|
||||
connectionsService.downloadPercentageProperty().addListener((observable, oldValue, newValue) -> {
|
||||
if (connectionsService.isDownloadComplete())
|
||||
isBlockDownloadComplete.set(true);
|
||||
});
|
||||
|
||||
walletsSetup.numPeersProperty().addListener((observable, oldValue, newValue) -> {
|
||||
if (walletsSetup.hasSufficientPeersForBroadcast())
|
||||
connectionsService.numPeersProperty().addListener((observable, oldValue, newValue) -> {
|
||||
if (connectionsService.hasSufficientPeersForBroadcast())
|
||||
hasSufficientPeersForBroadcast.set(true);
|
||||
});
|
||||
|
||||
|
@ -77,6 +75,7 @@ public class AppStartupState {
|
|||
hasSufficientPeersForBroadcast,
|
||||
allDomainServicesInitialized,
|
||||
(a, b, c, d) -> {
|
||||
log.info("p2pNetworkAndWalletInitialized = {} = updatedDataReceived={} && isBlockDownloadComplete={} && hasSufficientPeersForBroadcast={} && allDomainServicesInitialized={}", (a && b && c && d), updatedDataReceived.get(), isBlockDownloadComplete.get(), hasSufficientPeersForBroadcast.get(), allDomainServicesInitialized.get());
|
||||
if (a && b && c) {
|
||||
walletAndNetworkReady.set(true);
|
||||
}
|
||||
|
@ -85,6 +84,7 @@ public class AppStartupState {
|
|||
p2pNetworkAndWalletInitialized.subscribe((observable, oldValue, newValue) -> {
|
||||
if (newValue) {
|
||||
applicationFullyInitialized.set(true);
|
||||
notificationService.sendAppInitializedNotification();
|
||||
log.info("Application fully initialized");
|
||||
}
|
||||
});
|
||||
|
|
|
@ -41,8 +41,6 @@ import bisq.network.p2p.seed.SeedNodeRepository;
|
|||
|
||||
import bisq.common.app.AppModule;
|
||||
import bisq.common.config.Config;
|
||||
import bisq.common.crypto.PubKeyRing;
|
||||
import bisq.common.crypto.PubKeyRingProvider;
|
||||
import bisq.common.proto.network.NetworkProtoResolver;
|
||||
import bisq.common.proto.persistable.PersistenceProtoResolver;
|
||||
|
||||
|
@ -93,6 +91,5 @@ public class CoreModule extends AppModule {
|
|||
install(new FilterModule(config));
|
||||
install(new CorePresentationModule(config));
|
||||
install(new MoneroConnectionModule(config));
|
||||
bind(PubKeyRing.class).toProvider(PubKeyRingProvider.class);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -230,14 +230,14 @@ public class DomainInitialisation {
|
|||
triggerPriceService.onAllServicesInitialized();
|
||||
mempoolService.onAllServicesInitialized();
|
||||
|
||||
if (revolutAccountsUpdateHandler != null) {
|
||||
if (revolutAccountsUpdateHandler != null && user.getPaymentAccountsAsObservable() != null) {
|
||||
revolutAccountsUpdateHandler.accept(user.getPaymentAccountsAsObservable().stream()
|
||||
.filter(paymentAccount -> paymentAccount instanceof RevolutAccount)
|
||||
.map(paymentAccount -> (RevolutAccount) paymentAccount)
|
||||
.filter(RevolutAccount::userNameNotSet)
|
||||
.collect(Collectors.toList()));
|
||||
}
|
||||
if (amazonGiftCardAccountsUpdateHandler != null) {
|
||||
if (amazonGiftCardAccountsUpdateHandler != null && user.getPaymentAccountsAsObservable() != null) {
|
||||
amazonGiftCardAccountsUpdateHandler.accept(user.getPaymentAccountsAsObservable().stream()
|
||||
.filter(paymentAccount -> paymentAccount instanceof AmazonGiftCardAccount)
|
||||
.map(paymentAccount -> (AmazonGiftCardAccount) paymentAccount)
|
||||
|
|
|
@ -17,6 +17,8 @@
|
|||
|
||||
package bisq.core.app;
|
||||
|
||||
import bisq.core.api.AccountServiceListener;
|
||||
import bisq.core.api.CoreAccountService;
|
||||
import bisq.core.btc.setup.WalletsSetup;
|
||||
import bisq.core.btc.wallet.BtcWalletService;
|
||||
import bisq.core.btc.wallet.XmrWalletService;
|
||||
|
@ -27,7 +29,6 @@ import bisq.core.setup.CoreSetup;
|
|||
import bisq.core.support.dispute.arbitration.arbitrator.ArbitratorManager;
|
||||
import bisq.core.trade.statistics.TradeStatisticsManager;
|
||||
import bisq.core.trade.txproof.xmr.XmrTxProofService;
|
||||
|
||||
import bisq.network.p2p.P2PService;
|
||||
|
||||
import bisq.common.UserThread;
|
||||
|
@ -35,6 +36,7 @@ import bisq.common.app.AppModule;
|
|||
import bisq.common.config.HavenoHelpFormatter;
|
||||
import bisq.common.config.Config;
|
||||
import bisq.common.config.ConfigException;
|
||||
import bisq.common.crypto.IncorrectPasswordException;
|
||||
import bisq.common.handlers.ResultHandler;
|
||||
import bisq.common.persistence.PersistenceManager;
|
||||
import bisq.common.proto.persistable.PersistedDataHost;
|
||||
|
@ -45,7 +47,6 @@ import bisq.common.util.Utilities;
|
|||
|
||||
import com.google.inject.Guice;
|
||||
import com.google.inject.Injector;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
|
@ -64,11 +65,12 @@ public abstract class HavenoExecutable implements GracefulShutDownHandler, Haven
|
|||
private final String appName;
|
||||
private final String version;
|
||||
|
||||
protected CoreAccountService accountService;
|
||||
protected Injector injector;
|
||||
protected AppModule module;
|
||||
protected Config config;
|
||||
private boolean isShutdownInProgress;
|
||||
private boolean hasDowngraded;
|
||||
private boolean isReadOnly;
|
||||
|
||||
public HavenoExecutable(String fullName, String scriptName, String appName, String version) {
|
||||
this.fullName = fullName;
|
||||
|
@ -136,17 +138,61 @@ public abstract class HavenoExecutable implements GracefulShutDownHandler, Haven
|
|||
setupGuice();
|
||||
setupAvoidStandbyMode();
|
||||
|
||||
hasDowngraded = HavenoSetup.hasDowngraded();
|
||||
if (hasDowngraded) {
|
||||
// If user tried to downgrade we do not read the persisted data to avoid data corruption
|
||||
// We call startApplication to enable UI to show popup. We prevent in HavenoSetup to go further
|
||||
// in the process and require a shut down.
|
||||
startApplication();
|
||||
} else {
|
||||
// If user tried to downgrade we do not read the persisted data to avoid data corruption
|
||||
// We call startApplication to enable UI to show popup. We prevent in HavenoSetup to go further
|
||||
// in the process and require a shut down.
|
||||
isReadOnly = HavenoSetup.hasDowngraded();
|
||||
|
||||
// Account service should be available before attempting to login.
|
||||
accountService = injector.getInstance(CoreAccountService.class);
|
||||
|
||||
// Application needs to restart on delete and restore of account.
|
||||
accountService.addListener(new AccountServiceListener() {
|
||||
@Override public void onAccountDeleted(Runnable onShutdown) { shutDownNoPersist(onShutdown); }
|
||||
@Override public void onAccountRestored(Runnable onShutdown) { shutDownNoPersist(onShutdown); }
|
||||
});
|
||||
|
||||
// Attempt to login, subclasses should implement interactive login and or rpc login.
|
||||
if (!isReadOnly && loginAccount()) {
|
||||
readAllPersisted(this::startApplication);
|
||||
} else {
|
||||
log.warn("Running application in readonly mode");
|
||||
startApplication();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Do not persist when shutting down after account restore and restarts since
|
||||
* that causes the current persistables to overwrite the restored or deleted state.
|
||||
*/
|
||||
protected void shutDownNoPersist(Runnable onShutdown) {
|
||||
this.isReadOnly = true;
|
||||
gracefulShutDown(() -> {
|
||||
log.info("Shutdown without persisting");
|
||||
if (onShutdown != null) onShutdown.run();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempt to login. TODO: supply a password in config or args
|
||||
*
|
||||
* @return true if account is opened successfully.
|
||||
*/
|
||||
protected boolean loginAccount() {
|
||||
if (accountService.accountExists()) {
|
||||
log.info("Account already exists, attempting to open");
|
||||
try {
|
||||
accountService.openAccount(null);
|
||||
} catch (IncorrectPasswordException ipe) {
|
||||
log.info("Account password protected, password required");
|
||||
}
|
||||
} else if (!config.passwordRequired) {
|
||||
log.info("Creating Haveno account with null password");
|
||||
accountService.createAccount(null);
|
||||
}
|
||||
return accountService.isAccountOpen();
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
// We continue with a series of synchronous execution tasks
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
@ -198,9 +244,9 @@ public abstract class HavenoExecutable implements GracefulShutDownHandler, Haven
|
|||
}
|
||||
|
||||
protected void runHavenoSetup() {
|
||||
HavenoSetup bisqSetup = injector.getInstance(HavenoSetup.class);
|
||||
bisqSetup.addHavenoSetupListener(this);
|
||||
bisqSetup.start();
|
||||
HavenoSetup havenoSetup = injector.getInstance(HavenoSetup.class);
|
||||
havenoSetup.addHavenoSetupListener(this);
|
||||
havenoSetup.start();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -233,7 +279,7 @@ public abstract class HavenoExecutable implements GracefulShutDownHandler, Haven
|
|||
injector.getInstance(TradeStatisticsManager.class).shutDown();
|
||||
injector.getInstance(XmrTxProofService.class).shutDown();
|
||||
injector.getInstance(AvoidStandbyModeService.class).shutDown();
|
||||
injector.getInstance(XmrWalletService.class).shutDown(); // TODO: why not shut down BtcWalletService, etc?
|
||||
injector.getInstance(XmrWalletService.class).shutDown(); // TODO: why not shut down BtcWalletService, etc? shutdown CoreMoneroConnectionsService
|
||||
log.info("OpenOfferManager shutdown started");
|
||||
injector.getInstance(OpenOfferManager.class).shutDown(() -> {
|
||||
log.info("OpenOfferManager shutdown completed");
|
||||
|
@ -248,16 +294,7 @@ public abstract class HavenoExecutable implements GracefulShutDownHandler, Haven
|
|||
injector.getInstance(P2PService.class).shutDown(() -> {
|
||||
log.info("P2PService shutdown completed");
|
||||
module.close(injector);
|
||||
if (!hasDowngraded) {
|
||||
// If user tried to downgrade we do not write the persistable data to avoid data corruption
|
||||
PersistenceManager.flushAllDataToDiskAtShutdown(() -> {
|
||||
log.info("Graceful shutdown completed. Exiting now.");
|
||||
resultHandler.handleResult();
|
||||
UserThread.runAfter(() -> System.exit(EXIT_SUCCESS), 1);
|
||||
});
|
||||
} else {
|
||||
UserThread.runAfter(() -> System.exit(EXIT_SUCCESS), 1);
|
||||
}
|
||||
completeShutdown(resultHandler, EXIT_SUCCESS);
|
||||
});
|
||||
});
|
||||
walletsSetup.shutDown();
|
||||
|
@ -267,31 +304,26 @@ public abstract class HavenoExecutable implements GracefulShutDownHandler, Haven
|
|||
// Wait max 20 sec.
|
||||
UserThread.runAfter(() -> {
|
||||
log.warn("Graceful shut down not completed in 20 sec. We trigger our timeout handler.");
|
||||
if (!hasDowngraded) {
|
||||
// If user tried to downgrade we do not write the persistable data to avoid data corruption
|
||||
PersistenceManager.flushAllDataToDiskAtShutdown(() -> {
|
||||
log.info("Graceful shutdown resulted in a timeout. Exiting now.");
|
||||
resultHandler.handleResult();
|
||||
UserThread.runAfter(() -> System.exit(EXIT_SUCCESS), 1);
|
||||
});
|
||||
} else {
|
||||
UserThread.runAfter(() -> System.exit(EXIT_SUCCESS), 1);
|
||||
}
|
||||
|
||||
completeShutdown(resultHandler, EXIT_SUCCESS);
|
||||
}, 20);
|
||||
} catch (Throwable t) {
|
||||
log.error("App shutdown failed with exception {}", t.toString());
|
||||
t.printStackTrace();
|
||||
if (!hasDowngraded) {
|
||||
// If user tried to downgrade we do not write the persistable data to avoid data corruption
|
||||
PersistenceManager.flushAllDataToDiskAtShutdown(() -> {
|
||||
log.info("Graceful shutdown resulted in an error. Exiting now.");
|
||||
resultHandler.handleResult();
|
||||
UserThread.runAfter(() -> System.exit(EXIT_FAILURE), 1);
|
||||
});
|
||||
} else {
|
||||
UserThread.runAfter(() -> System.exit(EXIT_FAILURE), 1);
|
||||
}
|
||||
completeShutdown(resultHandler, EXIT_FAILURE);
|
||||
}
|
||||
}
|
||||
|
||||
private void completeShutdown(ResultHandler resultHandler, int exitCode) {
|
||||
if (!isReadOnly) {
|
||||
// If user tried to downgrade we do not write the persistable data to avoid data corruption
|
||||
PersistenceManager.flushAllDataToDiskAtShutdown(() -> {
|
||||
log.info("Graceful shutdown flushed persistence. Exiting now.");
|
||||
resultHandler.handleResult();
|
||||
UserThread.runAfter(() -> System.exit(exitCode), 1);
|
||||
});
|
||||
} else {
|
||||
resultHandler.handleResult();
|
||||
UserThread.runAfter(() -> System.exit(exitCode), 1);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -36,6 +36,8 @@ import lombok.extern.slf4j.Slf4j;
|
|||
public class HavenoHeadlessApp implements HeadlessApp {
|
||||
@Getter
|
||||
private static Runnable shutDownHandler;
|
||||
@Setter
|
||||
public static Runnable onGracefulShutDownHandler;
|
||||
|
||||
@Setter
|
||||
protected Injector injector;
|
||||
|
@ -50,6 +52,7 @@ public class HavenoHeadlessApp implements HeadlessApp {
|
|||
shutDownHandler = this::stop;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void startApplication() {
|
||||
try {
|
||||
bisqSetup = injector.getInstance(HavenoSetup.class);
|
||||
|
@ -103,13 +106,13 @@ public class HavenoHeadlessApp implements HeadlessApp {
|
|||
UserThread.runAfter(() -> {
|
||||
gracefulShutDownHandler.gracefulShutDown(() -> {
|
||||
log.debug("App shutdown complete");
|
||||
if (onGracefulShutDownHandler != null) onGracefulShutDownHandler.run();
|
||||
});
|
||||
}, 200, TimeUnit.MILLISECONDS);
|
||||
shutDownRequested = true;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
// UncaughtExceptionHandler implementation
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
|
|
@ -115,6 +115,7 @@ public class HavenoHeadlessAppMain extends HavenoExecutable {
|
|||
onApplicationStarted();
|
||||
}
|
||||
|
||||
// TODO: implement interactive console which allows user to input commands; login, logoff, exit
|
||||
private void keepRunning() {
|
||||
while (true) {
|
||||
try {
|
||||
|
|
|
@ -195,7 +195,7 @@ public class HavenoSetup {
|
|||
private boolean allBasicServicesInitialized;
|
||||
@SuppressWarnings("FieldCanBeLocal")
|
||||
private MonadicBinding<Boolean> p2pNetworkAndWalletInitialized;
|
||||
private final List<HavenoSetupListener> bisqSetupListeners = new ArrayList<>();
|
||||
private final List<HavenoSetupListener> havenoSetupListeners = new ArrayList<>();
|
||||
|
||||
@Inject
|
||||
public HavenoSetup(DomainInitialisation domainInitialisation,
|
||||
|
@ -274,7 +274,7 @@ public class HavenoSetup {
|
|||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
public void addHavenoSetupListener(HavenoSetupListener listener) {
|
||||
bisqSetupListeners.add(listener);
|
||||
havenoSetupListeners.add(listener);
|
||||
}
|
||||
|
||||
public void start() {
|
||||
|
@ -284,7 +284,7 @@ public class HavenoSetup {
|
|||
return;
|
||||
}
|
||||
|
||||
persistBisqVersion();
|
||||
persistHavenoVersion();
|
||||
maybeReSyncSPVChain();
|
||||
maybeShowTac(this::step2);
|
||||
}
|
||||
|
@ -303,7 +303,7 @@ public class HavenoSetup {
|
|||
private void step4() {
|
||||
initDomainServices();
|
||||
|
||||
bisqSetupListeners.forEach(HavenoSetupListener::onSetupComplete);
|
||||
havenoSetupListeners.forEach(HavenoSetupListener::onSetupComplete);
|
||||
|
||||
// We set that after calling the setupCompleteHandler to not trigger a popup from the dev dummy accounts
|
||||
// in MainViewModel
|
||||
|
@ -373,7 +373,7 @@ public class HavenoSetup {
|
|||
}, STARTUP_TIMEOUT_MINUTES, TimeUnit.MINUTES);
|
||||
|
||||
log.info("Init P2P network");
|
||||
bisqSetupListeners.forEach(HavenoSetupListener::onInitP2pNetwork);
|
||||
havenoSetupListeners.forEach(HavenoSetupListener::onInitP2pNetwork);
|
||||
p2pNetworkReady = p2PNetworkSetup.init(this::initWallet, displayTorNetworkSettingsHandler);
|
||||
|
||||
// We only init wallet service here if not using Tor for bitcoinj.
|
||||
|
@ -402,10 +402,10 @@ public class HavenoSetup {
|
|||
|
||||
private void initWallet() {
|
||||
log.info("Init wallet");
|
||||
bisqSetupListeners.forEach(HavenoSetupListener::onInitWallet);
|
||||
havenoSetupListeners.forEach(HavenoSetupListener::onInitWallet);
|
||||
Runnable walletPasswordHandler = () -> {
|
||||
log.info("Wallet password required");
|
||||
bisqSetupListeners.forEach(HavenoSetupListener::onRequestWalletPassword);
|
||||
havenoSetupListeners.forEach(HavenoSetupListener::onRequestWalletPassword);
|
||||
if (p2pNetworkReady.get())
|
||||
p2PNetworkSetup.setSplashP2PNetworkAnimationVisible(true);
|
||||
|
||||
|
@ -581,7 +581,7 @@ public class HavenoSetup {
|
|||
return hasDowngraded;
|
||||
}
|
||||
|
||||
public static void persistBisqVersion() {
|
||||
public static void persistHavenoVersion() {
|
||||
File versionFile = getVersionFile();
|
||||
if (!versionFile.exists()) {
|
||||
try {
|
||||
|
@ -639,6 +639,7 @@ public class HavenoSetup {
|
|||
}
|
||||
|
||||
private void maybeShowSecurityRecommendation() {
|
||||
if (user.getPaymentAccountsAsObservable() == null) return;
|
||||
String key = "remindPasswordAndBackup";
|
||||
user.getPaymentAccountsAsObservable().addListener((SetChangeListener<PaymentAccount>) change -> {
|
||||
if (!walletsManager.areWalletsEncrypted() && !user.isPaymentAccountImport() && preferences.showAgain(key) && change.wasAdded() &&
|
||||
|
|
|
@ -17,7 +17,7 @@
|
|||
|
||||
package bisq.core.app;
|
||||
|
||||
import bisq.core.btc.setup.WalletsSetup;
|
||||
import bisq.core.api.CoreMoneroConnectionsService;
|
||||
import bisq.core.locale.Res;
|
||||
import bisq.core.provider.price.PriceFeedService;
|
||||
import bisq.core.user.Preferences;
|
||||
|
@ -51,7 +51,7 @@ import javax.annotation.Nullable;
|
|||
public class P2PNetworkSetup {
|
||||
private final PriceFeedService priceFeedService;
|
||||
private final P2PService p2PService;
|
||||
private final WalletsSetup walletsSetup;
|
||||
private final CoreMoneroConnectionsService connectionService;
|
||||
private final Preferences preferences;
|
||||
|
||||
@SuppressWarnings("FieldCanBeLocal")
|
||||
|
@ -75,12 +75,12 @@ public class P2PNetworkSetup {
|
|||
@Inject
|
||||
public P2PNetworkSetup(PriceFeedService priceFeedService,
|
||||
P2PService p2PService,
|
||||
WalletsSetup walletsSetup,
|
||||
CoreMoneroConnectionsService connectionService,
|
||||
Preferences preferences) {
|
||||
|
||||
this.priceFeedService = priceFeedService;
|
||||
this.p2PService = p2PService;
|
||||
this.walletsSetup = walletsSetup;
|
||||
this.connectionService = connectionService;
|
||||
this.preferences = preferences;
|
||||
}
|
||||
|
||||
|
@ -91,7 +91,7 @@ public class P2PNetworkSetup {
|
|||
BooleanProperty initialP2PNetworkDataReceived = new SimpleBooleanProperty();
|
||||
|
||||
p2PNetworkInfoBinding = EasyBind.combine(bootstrapState, bootstrapWarning, p2PService.getNumConnectedPeers(),
|
||||
walletsSetup.numPeersProperty(), hiddenServicePublished, initialP2PNetworkDataReceived,
|
||||
connectionService.numPeersProperty(), hiddenServicePublished, initialP2PNetworkDataReceived,
|
||||
(state, warning, numP2pPeers, numBtcPeers, hiddenService, dataReceived) -> {
|
||||
String result;
|
||||
int p2pPeers = (int) numP2pPeers;
|
||||
|
|
|
@ -18,6 +18,7 @@
|
|||
package bisq.core.app;
|
||||
|
||||
import bisq.core.api.CoreContext;
|
||||
import bisq.core.api.CoreMoneroConnectionsService;
|
||||
import bisq.core.btc.exceptions.InvalidHostException;
|
||||
import bisq.core.btc.exceptions.RejectedTxException;
|
||||
import bisq.core.btc.setup.WalletsSetup;
|
||||
|
@ -67,6 +68,7 @@ public class WalletAppSetup {
|
|||
private final CoreContext coreContext;
|
||||
private final WalletsManager walletsManager;
|
||||
private final WalletsSetup walletsSetup;
|
||||
private final CoreMoneroConnectionsService connectionService;
|
||||
private final FeeService feeService;
|
||||
private final Config config;
|
||||
private final Preferences preferences;
|
||||
|
@ -91,12 +93,14 @@ public class WalletAppSetup {
|
|||
public WalletAppSetup(CoreContext coreContext,
|
||||
WalletsManager walletsManager,
|
||||
WalletsSetup walletsSetup,
|
||||
CoreMoneroConnectionsService connectionService,
|
||||
FeeService feeService,
|
||||
Config config,
|
||||
Preferences preferences) {
|
||||
this.coreContext = coreContext;
|
||||
this.walletsManager = walletsManager;
|
||||
this.walletsSetup = walletsSetup;
|
||||
this.connectionService = connectionService;
|
||||
this.feeService = feeService;
|
||||
this.config = config;
|
||||
this.preferences = preferences;
|
||||
|
@ -115,8 +119,8 @@ public class WalletAppSetup {
|
|||
VersionMessage.BITCOINJ_VERSION, "2a80db4");
|
||||
|
||||
ObjectProperty<Throwable> walletServiceException = new SimpleObjectProperty<>();
|
||||
btcInfoBinding = EasyBind.combine(walletsSetup.downloadPercentageProperty(),
|
||||
walletsSetup.chainHeightProperty(),
|
||||
btcInfoBinding = EasyBind.combine(connectionService.downloadPercentageProperty(), // TODO (woodser): update to XMR
|
||||
connectionService.chainHeightProperty(),
|
||||
feeService.feeUpdateCounterProperty(),
|
||||
walletServiceException,
|
||||
(downloadPercentage, chainHeight, feeUpdate, exception) -> {
|
||||
|
@ -124,10 +128,8 @@ public class WalletAppSetup {
|
|||
if (exception == null) {
|
||||
double percentage = (double) downloadPercentage;
|
||||
btcSyncProgress.set(percentage);
|
||||
int bestChainHeight = walletsSetup.getChain() != null ?
|
||||
walletsSetup.getChain().getBestChainHeight() :
|
||||
0;
|
||||
String chainHeightAsString = bestChainHeight > 0 ?
|
||||
Long bestChainHeight = connectionService.getDaemon() == null ? null : connectionService.getDaemon().getInfo().getHeight();
|
||||
String chainHeightAsString = bestChainHeight != null && bestChainHeight > 0 ?
|
||||
String.valueOf(bestChainHeight) :
|
||||
"";
|
||||
if (percentage == 1) {
|
||||
|
|
|
@ -29,7 +29,7 @@ import bisq.core.proto.persistable.CorePersistenceProtoResolver;
|
|||
import bisq.core.trade.TradeModule;
|
||||
import bisq.core.user.Preferences;
|
||||
import bisq.core.user.User;
|
||||
|
||||
import bisq.core.xmr.connection.MoneroConnectionModule;
|
||||
import bisq.network.crypto.EncryptionServiceModule;
|
||||
import bisq.network.p2p.P2PModule;
|
||||
import bisq.network.p2p.network.BridgeAddressProvider;
|
||||
|
@ -41,8 +41,6 @@ import bisq.common.app.AppModule;
|
|||
import bisq.common.config.Config;
|
||||
import bisq.common.crypto.KeyRing;
|
||||
import bisq.common.crypto.KeyStorage;
|
||||
import bisq.common.crypto.PubKeyRing;
|
||||
import bisq.common.crypto.PubKeyRingProvider;
|
||||
import bisq.common.proto.network.NetworkProtoResolver;
|
||||
import bisq.common.proto.persistable.PersistenceProtoResolver;
|
||||
|
||||
|
@ -93,6 +91,6 @@ public class ModuleForAppWithP2p extends AppModule {
|
|||
install(new BitcoinModule(config));
|
||||
install(new AlertModule(config));
|
||||
install(new FilterModule(config));
|
||||
bind(PubKeyRing.class).toProvider(PubKeyRingProvider.class);
|
||||
install(new MoneroConnectionModule(config));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,12 +2,14 @@ package bisq.core.btc.model;
|
|||
|
||||
import bisq.common.crypto.CryptoException;
|
||||
import bisq.common.crypto.Encryption;
|
||||
import bisq.common.crypto.ScryptUtil;
|
||||
import bisq.common.persistence.PersistenceManager;
|
||||
import bisq.common.proto.persistable.PersistableEnvelope;
|
||||
import bisq.common.proto.persistable.PersistedDataHost;
|
||||
import bisq.core.api.CoreAccountService;
|
||||
import bisq.core.api.model.EncryptedConnection;
|
||||
import bisq.core.crypto.ScryptUtil;
|
||||
import com.google.protobuf.ByteString;
|
||||
import com.google.protobuf.Message;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.security.SecureRandom;
|
||||
import java.util.HashMap;
|
||||
|
@ -22,8 +24,6 @@ import java.util.function.Function;
|
|||
import java.util.stream.Collectors;
|
||||
import javax.crypto.SecretKey;
|
||||
import javax.inject.Inject;
|
||||
import com.google.protobuf.ByteString;
|
||||
import com.google.protobuf.Message;
|
||||
import lombok.NonNull;
|
||||
import monero.common.MoneroRpcConnection;
|
||||
import org.bitcoinj.crypto.KeyCrypterScrypt;
|
||||
|
@ -60,7 +60,7 @@ public class EncryptedConnectionList implements PersistableEnvelope, PersistedDa
|
|||
transient private PersistenceManager<EncryptedConnectionList> persistenceManager;
|
||||
|
||||
private final Map<String, EncryptedConnection> items = new HashMap<>();
|
||||
private @NonNull String currentConnectionUri = "";
|
||||
private @NonNull String currentConnectionUrl = "";
|
||||
private long refreshPeriod;
|
||||
private boolean autoSwitch;
|
||||
|
||||
|
@ -70,17 +70,16 @@ public class EncryptedConnectionList implements PersistableEnvelope, PersistedDa
|
|||
this.accountService = accountService;
|
||||
this.persistenceManager = persistenceManager;
|
||||
this.persistenceManager.initialize(this, "EncryptedConnectionList", PersistenceManager.Source.PRIVATE);
|
||||
this.accountService.addPasswordChangeListener(this::onPasswordChange);
|
||||
}
|
||||
|
||||
private EncryptedConnectionList(byte[] salt,
|
||||
List<EncryptedConnection> items,
|
||||
@NonNull String currentConnectionUri,
|
||||
@NonNull String currentConnectionUrl,
|
||||
long refreshPeriod,
|
||||
boolean autoSwitch) {
|
||||
this.keyCrypterScrypt = ScryptUtil.getKeyCrypterScrypt(salt);
|
||||
this.items.putAll(items.stream().collect(Collectors.toMap(EncryptedConnection::getUri, Function.identity())));
|
||||
this.currentConnectionUri = currentConnectionUri;
|
||||
this.items.putAll(items.stream().collect(Collectors.toMap(EncryptedConnection::getUrl, Function.identity())));
|
||||
this.currentConnectionUrl = currentConnectionUrl;
|
||||
this.refreshPeriod = refreshPeriod;
|
||||
this.autoSwitch = autoSwitch;
|
||||
}
|
||||
|
@ -93,9 +92,11 @@ public class EncryptedConnectionList implements PersistableEnvelope, PersistedDa
|
|||
initializeEncryption(persistedEncryptedConnectionList.keyCrypterScrypt);
|
||||
items.clear();
|
||||
items.putAll(persistedEncryptedConnectionList.items);
|
||||
currentConnectionUri = persistedEncryptedConnectionList.currentConnectionUri;
|
||||
currentConnectionUrl = persistedEncryptedConnectionList.currentConnectionUrl;
|
||||
refreshPeriod = persistedEncryptedConnectionList.refreshPeriod;
|
||||
autoSwitch = persistedEncryptedConnectionList.autoSwitch;
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
} finally {
|
||||
writeLock.unlock();
|
||||
}
|
||||
|
@ -104,6 +105,8 @@ public class EncryptedConnectionList implements PersistableEnvelope, PersistedDa
|
|||
writeLock.lock();
|
||||
try {
|
||||
initializeEncryption(ScryptUtil.getKeyCrypterScrypt());
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
} finally {
|
||||
writeLock.unlock();
|
||||
}
|
||||
|
@ -203,11 +206,11 @@ public class EncryptedConnectionList implements PersistableEnvelope, PersistedDa
|
|||
}
|
||||
}
|
||||
|
||||
public void setCurrentConnectionUri(String currentConnectionUri) {
|
||||
public void setCurrentConnectionUri(String currentConnectionUrl) {
|
||||
boolean changed;
|
||||
writeLock.lock();
|
||||
try {
|
||||
changed = !this.currentConnectionUri.equals(this.currentConnectionUri = currentConnectionUri == null ? "" : currentConnectionUri);
|
||||
changed = !this.currentConnectionUrl.equals(this.currentConnectionUrl = currentConnectionUrl == null ? "" : currentConnectionUrl);
|
||||
} finally {
|
||||
writeLock.unlock();
|
||||
}
|
||||
|
@ -219,17 +222,54 @@ public class EncryptedConnectionList implements PersistableEnvelope, PersistedDa
|
|||
public Optional<String> getCurrentConnectionUri() {
|
||||
readLock.lock();
|
||||
try {
|
||||
return Optional.of(currentConnectionUri).filter(s -> !s.isEmpty());
|
||||
return Optional.of(currentConnectionUrl).filter(s -> !s.isEmpty());
|
||||
} finally {
|
||||
readLock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
private void requestPersistence() {
|
||||
public void requestPersistence() {
|
||||
persistenceManager.requestPersistence();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Message toProtoMessage() {
|
||||
List<protobuf.EncryptedConnection> connections;
|
||||
ByteString saltString;
|
||||
String currentConnectionUrl;
|
||||
boolean autoSwitchEnabled;
|
||||
long refreshPeriod;
|
||||
readLock.lock();
|
||||
try {
|
||||
connections = items.values().stream()
|
||||
.map(EncryptedConnection::toProtoMessage).collect(Collectors.toList());
|
||||
saltString = keyCrypterScrypt.getScryptParameters().getSalt();
|
||||
currentConnectionUrl = this.currentConnectionUrl;
|
||||
autoSwitchEnabled = this.autoSwitch;
|
||||
refreshPeriod = this.refreshPeriod;
|
||||
} finally {
|
||||
readLock.unlock();
|
||||
}
|
||||
return protobuf.PersistableEnvelope.newBuilder()
|
||||
.setEncryptedConnectionList(protobuf.EncryptedConnectionList.newBuilder()
|
||||
.setSalt(saltString)
|
||||
.addAllItems(connections)
|
||||
.setCurrentConnectionUrl(currentConnectionUrl)
|
||||
.setRefreshPeriod(refreshPeriod)
|
||||
.setAutoSwitch(autoSwitchEnabled))
|
||||
.build();
|
||||
}
|
||||
|
||||
private void onPasswordChange(String oldPassword, String newPassword) {
|
||||
public static EncryptedConnectionList fromProto(protobuf.EncryptedConnectionList proto) {
|
||||
List<EncryptedConnection> items = proto.getItemsList().stream()
|
||||
.map(EncryptedConnection::fromProto)
|
||||
.collect(Collectors.toList());
|
||||
return new EncryptedConnectionList(proto.getSalt().toByteArray(), items, proto.getCurrentConnectionUrl(), proto.getRefreshPeriod(), proto.getAutoSwitch());
|
||||
}
|
||||
|
||||
// ----------------------------- HELPERS ----------------------------------
|
||||
|
||||
public void changePassword(String oldPassword, String newPassword) {
|
||||
writeLock.lock();
|
||||
try {
|
||||
SecretKey oldSecret = encryptionKey;
|
||||
|
@ -243,9 +283,7 @@ public class EncryptedConnectionList implements PersistableEnvelope, PersistedDa
|
|||
}
|
||||
|
||||
private SecretKey toSecretKey(String password) {
|
||||
if (password == null) {
|
||||
return null;
|
||||
}
|
||||
if (password == null) return null;
|
||||
return Encryption.getSecretKeyFromBytes(keyCrypterScrypt.deriveKey(password).getKey());
|
||||
}
|
||||
|
||||
|
@ -265,6 +303,7 @@ public class EncryptedConnectionList implements PersistableEnvelope, PersistedDa
|
|||
}
|
||||
|
||||
private static byte[] decrypt(byte[] encrypted, SecretKey secret) {
|
||||
if (secret == null) return encrypted; // no encryption
|
||||
try {
|
||||
return Encryption.decrypt(encrypted, secret);
|
||||
} catch (CryptoException e) {
|
||||
|
@ -273,6 +312,7 @@ public class EncryptedConnectionList implements PersistableEnvelope, PersistedDa
|
|||
}
|
||||
|
||||
private static byte[] encrypt(byte[] unencrypted, SecretKey secretKey) {
|
||||
if (secretKey == null) return unencrypted; // no encryption
|
||||
try {
|
||||
return Encryption.encrypt(unencrypted, secretKey);
|
||||
} catch (CryptoException e) {
|
||||
|
@ -286,7 +326,7 @@ public class EncryptedConnectionList implements PersistableEnvelope, PersistedDa
|
|||
byte[] passwordSalt = generateSalt(passwordBytes);
|
||||
byte[] encryptedPassword = encryptPassword(passwordBytes, passwordSalt);
|
||||
return EncryptedConnection.builder()
|
||||
.uri(connection.getUri())
|
||||
.url(connection.getUri())
|
||||
.username(connection.getUsername() == null ? "" : connection.getUsername())
|
||||
.encryptedPassword(encryptedPassword)
|
||||
.encryptionSalt(passwordSalt)
|
||||
|
@ -298,7 +338,7 @@ public class EncryptedConnectionList implements PersistableEnvelope, PersistedDa
|
|||
byte[] decryptedPasswordBytes = decryptPassword(connection.getEncryptedPassword(), connection.getEncryptionSalt());
|
||||
String password = decryptedPasswordBytes == null ? null : new String(decryptedPasswordBytes, StandardCharsets.UTF_8);
|
||||
String username = connection.getUsername().isEmpty() ? null : connection.getUsername();
|
||||
MoneroRpcConnection moneroRpcConnection = new MoneroRpcConnection(connection.getUri(), username, password);
|
||||
MoneroRpcConnection moneroRpcConnection = new MoneroRpcConnection(connection.getUrl(), username, password);
|
||||
moneroRpcConnection.setPriority(connection.getPriority());
|
||||
return moneroRpcConnection;
|
||||
}
|
||||
|
@ -357,39 +397,4 @@ public class EncryptedConnectionList implements PersistableEnvelope, PersistedDa
|
|||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Message toProtoMessage() {
|
||||
List<protobuf.EncryptedConnection> connections;
|
||||
ByteString saltString;
|
||||
String currentConnectionUri;
|
||||
boolean autoSwitchEnabled;
|
||||
long refreshPeriod;
|
||||
readLock.lock();
|
||||
try {
|
||||
connections = items.values().stream()
|
||||
.map(EncryptedConnection::toProtoMessage).collect(Collectors.toList());
|
||||
saltString = keyCrypterScrypt.getScryptParameters().getSalt();
|
||||
currentConnectionUri = this.currentConnectionUri;
|
||||
autoSwitchEnabled = this.autoSwitch;
|
||||
refreshPeriod = this.refreshPeriod;
|
||||
} finally {
|
||||
readLock.unlock();
|
||||
}
|
||||
return protobuf.PersistableEnvelope.newBuilder()
|
||||
.setEncryptedConnectionList(protobuf.EncryptedConnectionList.newBuilder()
|
||||
.setSalt(saltString)
|
||||
.addAllItems(connections)
|
||||
.setCurrentConnectionUri(currentConnectionUri)
|
||||
.setRefreshPeriod(refreshPeriod)
|
||||
.setAutoSwitch(autoSwitchEnabled))
|
||||
.build();
|
||||
}
|
||||
|
||||
public static EncryptedConnectionList fromProto(protobuf.EncryptedConnectionList proto) {
|
||||
List<EncryptedConnection> items = proto.getItemsList().stream()
|
||||
.map(EncryptedConnection::fromProto)
|
||||
.collect(Collectors.toList());
|
||||
return new EncryptedConnectionList(proto.getSalt().toByteArray(), items, proto.getCurrentConnectionUri(), proto.getRefreshPeriod(), proto.getAutoSwitch());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -27,8 +27,6 @@ import com.google.inject.Inject;
|
|||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
|
||||
import java.math.BigInteger;
|
||||
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.CopyOnWriteArraySet;
|
||||
import java.util.stream.Collectors;
|
||||
|
@ -37,10 +35,6 @@ import lombok.extern.slf4j.Slf4j;
|
|||
|
||||
|
||||
|
||||
import monero.wallet.MoneroWallet;
|
||||
import monero.wallet.model.MoneroOutputWallet;
|
||||
import monero.wallet.model.MoneroWalletListener;
|
||||
|
||||
/**
|
||||
* The AddressEntries was previously stored as list, now as hashSet. We still keep the old name to reflect the
|
||||
* associated protobuf message.
|
||||
|
@ -48,7 +42,6 @@ import monero.wallet.model.MoneroWalletListener;
|
|||
@Slf4j
|
||||
public final class XmrAddressEntryList implements PersistableEnvelope, PersistedDataHost {
|
||||
transient private PersistenceManager<XmrAddressEntryList> persistenceManager;
|
||||
transient private MoneroWallet wallet;
|
||||
private final Set<XmrAddressEntry> entrySet = new CopyOnWriteArraySet<>();
|
||||
|
||||
@Inject
|
||||
|
@ -100,61 +93,6 @@ public final class XmrAddressEntryList implements PersistableEnvelope, Persisted
|
|||
// API
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
public void onWalletReady(MoneroWallet wallet) {
|
||||
this.wallet = wallet;
|
||||
|
||||
if (!entrySet.isEmpty()) {
|
||||
// Set<XmrAddressEntry> toBeRemoved = new HashSet<>();
|
||||
// entrySet.forEach(addressEntry -> {
|
||||
// DeterministicKey keyFromPubHash = (DeterministicKey) wallet.findKeyFromPubKeyHash(
|
||||
// addressEntry.getPubKeyHash(),
|
||||
// Script.ScriptType.P2PKH);
|
||||
// if (keyFromPubHash != null) {
|
||||
// Address addressFromKey = LegacyAddress.fromKey(Config.baseCurrencyNetworkParameters(), keyFromPubHash);
|
||||
// // We want to ensure key and address matches in case we have address in entry available already
|
||||
// if (addressEntry.getAddress() == null || addressFromKey.equals(addressEntry.getAddress())) {
|
||||
// addressEntry.setDeterministicKey(keyFromPubHash);
|
||||
// } else {
|
||||
// log.error("We found an address entry without key but cannot apply the key as the address " +
|
||||
// "is not matching. " +
|
||||
// "We remove that entry as it seems it is not compatible with our wallet. " +
|
||||
// "addressFromKey={}, addressEntry.getAddress()={}",
|
||||
// addressFromKey, addressEntry.getAddress());
|
||||
// toBeRemoved.add(addressEntry);
|
||||
// }
|
||||
// } else {
|
||||
// log.error("Key from addressEntry {} not found in that wallet. We remove that entry. " +
|
||||
// "This is expected at restore from seeds.", addressEntry.toString());
|
||||
// toBeRemoved.add(addressEntry);
|
||||
// }
|
||||
// });
|
||||
//
|
||||
// toBeRemoved.forEach(entrySet::remove);
|
||||
}
|
||||
|
||||
// In case we restore from seed words and have balance we need to add the relevant addresses to our list.
|
||||
// IssuedReceiveAddresses does not contain all addresses where we expect balance so we need to listen to
|
||||
// incoming txs at blockchain sync to add the rest.
|
||||
if (wallet.getBalance().compareTo(new BigInteger("0")) > 0) {
|
||||
wallet.getAccounts().forEach(acct -> {
|
||||
log.info("Create XmrAddressEntry for IssuedReceiveAddress. address={}", acct.getPrimaryAddress());
|
||||
if (acct.getIndex() != 0) entrySet.add(new XmrAddressEntry(acct.getIndex(), acct.getPrimaryAddress(), XmrAddressEntry.Context.AVAILABLE));
|
||||
});
|
||||
}
|
||||
|
||||
// We add those listeners to get notified about potential new transactions and
|
||||
// add an address entry list in case it does not exist yet. This is mainly needed for restore from seed words
|
||||
// but can help as well in case the addressEntry list would miss an address where the wallet was received
|
||||
// funds (e.g. if the user sends funds to an address which has not been provided in the main UI - like from the
|
||||
// wallet details window).
|
||||
wallet.addListener(new MoneroWalletListener() {
|
||||
@Override public void onOutputReceived(MoneroOutputWallet output) { maybeAddNewAddressEntry(output); }
|
||||
@Override public void onOutputSpent(MoneroOutputWallet output) { maybeAddNewAddressEntry(output); }
|
||||
});
|
||||
|
||||
requestPersistence();
|
||||
}
|
||||
|
||||
public ImmutableList<XmrAddressEntry> getAddressEntriesAsListImmutable() {
|
||||
return ImmutableList.copyOf(entrySet);
|
||||
}
|
||||
|
@ -202,25 +140,6 @@ public final class XmrAddressEntryList implements PersistableEnvelope, Persisted
|
|||
persistenceManager.requestPersistence();
|
||||
}
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Private
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
// TODO (woodser): this should be removed since only using account 0
|
||||
private void maybeAddNewAddressEntry(MoneroOutputWallet output) {
|
||||
if (output.getAccountIndex() == 0) return;
|
||||
String address = wallet.getAddress(output.getAccountIndex(), output.getSubaddressIndex());
|
||||
if (!isAddressInEntries(address)) addAddressEntry(new XmrAddressEntry(output.getAccountIndex(), address, XmrAddressEntry.Context.AVAILABLE));
|
||||
}
|
||||
|
||||
private boolean isAddressInEntries(String address) {
|
||||
for (XmrAddressEntry entry : entrySet) {
|
||||
if (entry.getAddressString().equals(address)) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "XmrAddressEntryList{" +
|
||||
|
|
|
@ -8,14 +8,14 @@ import javafx.beans.property.SimpleDoubleProperty;
|
|||
|
||||
import java.util.Date;
|
||||
|
||||
class DownloadListener {
|
||||
public class DownloadListener {
|
||||
private final DoubleProperty percentage = new SimpleDoubleProperty(-1);
|
||||
|
||||
protected void progress(double percentage, int blocksLeft, Date date) {
|
||||
public void progress(double percentage, int blocksLeft, Date date) {
|
||||
UserThread.execute(() -> this.percentage.set(percentage / 100d));
|
||||
}
|
||||
|
||||
protected void doneDownload() {
|
||||
public void doneDownload() {
|
||||
UserThread.execute(() -> this.percentage.set(1d));
|
||||
}
|
||||
|
||||
|
|
|
@ -17,7 +17,6 @@
|
|||
|
||||
package bisq.core.btc.setup;
|
||||
|
||||
import bisq.core.api.CoreMoneroConnectionsService;
|
||||
import bisq.core.btc.nodes.LocalBitcoinNode;
|
||||
import bisq.core.btc.nodes.ProxySocketFactory;
|
||||
import bisq.core.btc.wallet.HavenoRiskAnalysis;
|
||||
|
@ -72,9 +71,6 @@ import java.io.FileInputStream;
|
|||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
|
@ -89,15 +85,6 @@ import static bisq.common.util.Preconditions.checkDir;
|
|||
import static com.google.common.base.Preconditions.checkNotNull;
|
||||
import static com.google.common.base.Preconditions.checkState;
|
||||
|
||||
import monero.common.MoneroRpcConnection;
|
||||
import monero.common.MoneroUtils;
|
||||
import monero.daemon.MoneroDaemon;
|
||||
import monero.daemon.MoneroDaemonRpc;
|
||||
import monero.daemon.model.MoneroNetworkType;
|
||||
import monero.wallet.MoneroWallet;
|
||||
import monero.wallet.MoneroWalletRpc;
|
||||
import monero.wallet.model.MoneroWalletConfig;
|
||||
|
||||
/**
|
||||
* <p>Utility class that wraps the boilerplate needed to set up a new SPV bitcoinj app. Instantiate it with a directory
|
||||
* and file prefix, optionally configure a few things, then use startAsync and optionally awaitRunning. The object will
|
||||
|
@ -125,27 +112,13 @@ public class WalletConfig extends AbstractIdleService {
|
|||
|
||||
protected static final Logger log = LoggerFactory.getLogger(WalletConfig.class);
|
||||
|
||||
// Monero configuration
|
||||
// TODO: don't hard code configuration, inject into classes?
|
||||
private static final MoneroNetworkType MONERO_NETWORK_TYPE = MoneroNetworkType.STAGENET;
|
||||
private static final MoneroWalletRpcManager MONERO_WALLET_RPC_MANAGER = new MoneroWalletRpcManager();
|
||||
private static final String MONERO_WALLET_RPC_DIR = System.getProperty("user.dir") + File.separator + ".localnet"; // .localnet contains monero-wallet-rpc and wallet files
|
||||
private static final String MONERO_WALLET_RPC_PATH = MONERO_WALLET_RPC_DIR + File.separator + "monero-wallet-rpc";
|
||||
private static final String MONERO_WALLET_RPC_USERNAME = "rpc_user";
|
||||
private static final String MONERO_WALLET_RPC_PASSWORD = "abc123";
|
||||
private static final long MONERO_WALLET_SYNC_RATE = 5000l;
|
||||
|
||||
protected final NetworkParameters params;
|
||||
protected final String filePrefix;
|
||||
protected final CoreMoneroConnectionsService moneroConnectionsManager;
|
||||
protected volatile BlockChain vChain;
|
||||
protected volatile SPVBlockStore vStore;
|
||||
protected volatile MoneroDaemonRpc vXmrDaemon;
|
||||
protected volatile MoneroWalletRpc vXmrWallet;
|
||||
protected volatile Wallet vBtcWallet;
|
||||
protected volatile PeerGroup vPeerGroup;
|
||||
|
||||
protected final int rpcBindPort;
|
||||
protected final File directory;
|
||||
protected volatile File vXmrWalletFile;
|
||||
protected volatile File vBtcWalletFile;
|
||||
|
@ -176,21 +149,17 @@ public class WalletConfig extends AbstractIdleService {
|
|||
*/
|
||||
public WalletConfig(NetworkParameters params,
|
||||
File directory,
|
||||
int rpcBindPort,
|
||||
CoreMoneroConnectionsService connectionsManager,
|
||||
String filePrefix) {
|
||||
this(new Context(params), directory, rpcBindPort, connectionsManager, filePrefix);
|
||||
this(new Context(params), directory, filePrefix);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new WalletConfig, with the given {@link Context}. Files will be stored in the given directory.
|
||||
*/
|
||||
private WalletConfig(Context context, File directory, int rpcBindPort, CoreMoneroConnectionsService connectionsManager, String filePrefix) {
|
||||
private WalletConfig(Context context, File directory, String filePrefix) {
|
||||
this.context = context;
|
||||
this.params = checkNotNull(context.getParams());
|
||||
this.directory = checkDir(directory);
|
||||
this.rpcBindPort = rpcBindPort;
|
||||
this.moneroConnectionsManager = connectionsManager;
|
||||
this.filePrefix = checkNotNull(filePrefix);
|
||||
}
|
||||
|
||||
|
@ -293,85 +262,6 @@ public class WalletConfig extends AbstractIdleService {
|
|||
// Meant to be overridden by subclasses
|
||||
}
|
||||
|
||||
public boolean walletExists(String walletName) {
|
||||
String path = directory.toString() + File.separator + walletName;
|
||||
return new File(path + ".keys").exists();
|
||||
}
|
||||
|
||||
public MoneroWalletRpc createWallet(MoneroWalletConfig config, Integer port) {
|
||||
|
||||
// start monero-wallet-rpc instance
|
||||
MoneroWalletRpc walletRpc = startWalletRpcInstance(port);
|
||||
|
||||
// create wallet
|
||||
try {
|
||||
walletRpc.createWallet(config);
|
||||
walletRpc.startSyncing(MONERO_WALLET_SYNC_RATE);
|
||||
return walletRpc;
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
WalletConfig.MONERO_WALLET_RPC_MANAGER.stopInstance(walletRpc, false);
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
public MoneroWalletRpc openWallet(MoneroWalletConfig config, Integer port) {
|
||||
|
||||
// start monero-wallet-rpc instance
|
||||
MoneroWalletRpc walletRpc = startWalletRpcInstance(port);
|
||||
|
||||
// open wallet
|
||||
try {
|
||||
walletRpc.openWallet(config);
|
||||
walletRpc.startSyncing(MONERO_WALLET_SYNC_RATE);
|
||||
return walletRpc;
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
WalletConfig.MONERO_WALLET_RPC_MANAGER.stopInstance(walletRpc, false);
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
private MoneroWalletRpc startWalletRpcInstance(Integer port) {
|
||||
|
||||
// check if monero-wallet-rpc exists
|
||||
if (!new File(MONERO_WALLET_RPC_PATH).exists()) throw new Error("monero-wallet-rpc executable doesn't exist at path " + MONERO_WALLET_RPC_PATH + "; copy monero-wallet-rpc to the project root or set WalletConfig.java MONERO_WALLET_RPC_PATH for your system");
|
||||
|
||||
// get app's current daemon connection
|
||||
MoneroRpcConnection connection = moneroConnectionsManager.getConnection();
|
||||
|
||||
// start monero-wallet-rpc instance and return connected client
|
||||
List<String> cmd = new ArrayList<>(Arrays.asList( // modifiable list
|
||||
MONERO_WALLET_RPC_PATH,
|
||||
"--" + MONERO_NETWORK_TYPE.toString().toLowerCase(),
|
||||
"--daemon-address", connection.getUri(),
|
||||
"--rpc-login", MONERO_WALLET_RPC_USERNAME + ":" + MONERO_WALLET_RPC_PASSWORD,
|
||||
"--wallet-dir", directory.toString()
|
||||
));
|
||||
if (connection.getUsername() != null) {
|
||||
cmd.add("--daemon-login");
|
||||
cmd.add(connection.getUsername() + ":" + connection.getPassword());
|
||||
}
|
||||
if (port != null && port > 0) {
|
||||
cmd.add("--rpc-bind-port");
|
||||
cmd.add(Integer.toString(port));
|
||||
}
|
||||
return WalletConfig.MONERO_WALLET_RPC_MANAGER.startInstance(cmd);
|
||||
}
|
||||
|
||||
public void closeWallet(MoneroWallet walletRpc, boolean save) {
|
||||
WalletConfig.MONERO_WALLET_RPC_MANAGER.stopInstance((MoneroWalletRpc) walletRpc, save);
|
||||
}
|
||||
|
||||
public void deleteWallet(String walletName) {
|
||||
if (!walletExists(walletName)) throw new Error("Wallet does not exist at path: " + walletName);
|
||||
String path = directory.toString() + File.separator + walletName;
|
||||
if (!new File(path).delete()) throw new RuntimeException("Failed to delete wallet file: " + path);
|
||||
if (!new File(path + ".keys").delete()) throw new RuntimeException("Failed to delete wallet file: " + path);
|
||||
if (!new File(path + ".address.txt").delete()) throw new RuntimeException("Failed to delete wallet file: " + path);
|
||||
//WalletsSetup.deleteRollingBackup(walletName); // TODO (woodser): necessary to delete rolling backup?
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void startUp() throws Exception {
|
||||
// Runs in a separate thread.
|
||||
|
@ -380,31 +270,6 @@ public class WalletConfig extends AbstractIdleService {
|
|||
File chainFile = new File(directory, filePrefix + ".spvchain");
|
||||
boolean chainFileExists = chainFile.exists();
|
||||
|
||||
// set XMR daemon and listen for updates
|
||||
vXmrDaemon = new MoneroDaemonRpc(moneroConnectionsManager.getConnection());
|
||||
moneroConnectionsManager.addConnectionListener(newConnection -> {
|
||||
vXmrDaemon = newConnection == null ? null : new MoneroDaemonRpc(newConnection);
|
||||
});
|
||||
|
||||
// XMR wallet
|
||||
String xmrPrefix = "_XMR";
|
||||
vXmrWalletFile = new File(directory, filePrefix + xmrPrefix);
|
||||
if (MoneroUtils.walletExists(vXmrWalletFile.getPath())) {
|
||||
vXmrWallet = openWallet(new MoneroWalletConfig().setPath(filePrefix + xmrPrefix).setPassword("abctesting123"), rpcBindPort);
|
||||
} else {
|
||||
vXmrWallet = createWallet(new MoneroWalletConfig().setPath(filePrefix + xmrPrefix).setPassword("abctesting123"), rpcBindPort);
|
||||
}
|
||||
System.out.println("Monero wallet path: " + vXmrWallet.getPath());
|
||||
System.out.println("Monero wallet address: " + vXmrWallet.getPrimaryAddress());
|
||||
System.out.println("Monero wallet uri: " + vXmrWallet.getRpcConnection().getUri());
|
||||
// vXmrWallet.rescanSpent();
|
||||
// vXmrWallet.rescanBlockchain();
|
||||
vXmrWallet.sync(); // blocking
|
||||
downloadListener.doneDownload();
|
||||
vXmrWallet.save();
|
||||
System.out.println("Loaded wallet balance: " + vXmrWallet.getBalance(0));
|
||||
System.out.println("Loaded wallet unlocked balance: " + vXmrWallet.getUnlockedBalance(0));
|
||||
|
||||
String btcPrefix = "_BTC";
|
||||
vBtcWalletFile = new File(directory, filePrefix + btcPrefix + ".wallet");
|
||||
boolean shouldReplayWallet = (vBtcWalletFile.exists() && !chainFileExists) || restoreFromSeed != null;
|
||||
|
@ -647,16 +512,6 @@ public class WalletConfig extends AbstractIdleService {
|
|||
return vBtcWallet;
|
||||
}
|
||||
|
||||
public MoneroDaemon getXmrDaemon() {
|
||||
checkState(state() == State.STARTING || state() == State.RUNNING, "Cannot call until startup is complete");
|
||||
return vXmrDaemon;
|
||||
}
|
||||
|
||||
public MoneroWallet getXmrWallet() {
|
||||
checkState(state() == State.STARTING || state() == State.RUNNING, "Cannot call until startup is complete");
|
||||
return vXmrWallet;
|
||||
}
|
||||
|
||||
public PeerGroup peerGroup() {
|
||||
checkState(state() == State.STARTING || state() == State.RUNNING, "Cannot call until startup is complete");
|
||||
return vPeerGroup;
|
||||
|
|
|
@ -17,12 +17,10 @@
|
|||
|
||||
package bisq.core.btc.setup;
|
||||
|
||||
import bisq.core.api.CoreMoneroConnectionsService;
|
||||
import bisq.core.btc.exceptions.InvalidHostException;
|
||||
import bisq.core.btc.exceptions.RejectedTxException;
|
||||
import bisq.core.btc.model.AddressEntry;
|
||||
import bisq.core.btc.model.AddressEntryList;
|
||||
import bisq.core.btc.model.XmrAddressEntryList;
|
||||
import bisq.core.btc.nodes.BtcNetworkConfig;
|
||||
import bisq.core.btc.nodes.BtcNodes;
|
||||
import bisq.core.btc.nodes.BtcNodes.BtcNode;
|
||||
|
@ -66,14 +64,11 @@ import org.apache.commons.lang3.StringUtils;
|
|||
import javafx.beans.property.BooleanProperty;
|
||||
import javafx.beans.property.IntegerProperty;
|
||||
import javafx.beans.property.LongProperty;
|
||||
import javafx.beans.property.ObjectProperty;
|
||||
import javafx.beans.property.ReadOnlyDoubleProperty;
|
||||
import javafx.beans.property.ReadOnlyIntegerProperty;
|
||||
import javafx.beans.property.ReadOnlyObjectProperty;
|
||||
import javafx.beans.property.SimpleBooleanProperty;
|
||||
import javafx.beans.property.SimpleIntegerProperty;
|
||||
import javafx.beans.property.SimpleLongProperty;
|
||||
import javafx.beans.property.SimpleObjectProperty;
|
||||
|
||||
import java.net.InetAddress;
|
||||
import java.net.UnknownHostException;
|
||||
|
@ -100,9 +95,6 @@ import javax.annotation.Nullable;
|
|||
import static com.google.common.base.Preconditions.checkNotNull;
|
||||
|
||||
|
||||
import monero.daemon.MoneroDaemon;
|
||||
import monero.daemon.model.MoneroPeer;
|
||||
import monero.wallet.MoneroWallet;
|
||||
|
||||
// Setup wallets and use WalletConfig for BitcoinJ wiring.
|
||||
// Other like WalletConfig we are here always on the user thread. That is one reason why we do not
|
||||
|
@ -111,8 +103,7 @@ import monero.wallet.MoneroWallet;
|
|||
public class WalletsSetup {
|
||||
|
||||
public static final String PRE_SEGWIT_WALLET_BACKUP = "pre_segwit_haveno_BTC.wallet.backup";
|
||||
private static final int MIN_BROADCAST_CONNECTIONS = 2;
|
||||
private static final long DAEMON_POLL_INTERVAL_SECONDS = 20;
|
||||
private static final int MIN_BROADCAST_CONNECTIONS = 0;
|
||||
|
||||
@Getter
|
||||
public final BooleanProperty walletsSetupFailed = new SimpleBooleanProperty();
|
||||
|
@ -122,25 +113,20 @@ public class WalletsSetup {
|
|||
|
||||
private final RegTestHost regTestHost;
|
||||
private final AddressEntryList addressEntryList;
|
||||
private final XmrAddressEntryList xmrAddressEntryList;
|
||||
private final Preferences preferences;
|
||||
private final Socks5ProxyProvider socks5ProxyProvider;
|
||||
private final Config config;
|
||||
private final LocalBitcoinNode localBitcoinNode;
|
||||
private final BtcNodes btcNodes;
|
||||
@Getter
|
||||
private final CoreMoneroConnectionsService moneroConnectionsManager;
|
||||
private final String xmrWalletFileName;
|
||||
private final int numConnectionsForBtc;
|
||||
private final String userAgent;
|
||||
private final NetworkParameters params;
|
||||
private final File walletDir;
|
||||
private final int walletRpcBindPort;
|
||||
private final int socks5DiscoverMode;
|
||||
private final IntegerProperty numPeers = new SimpleIntegerProperty(0);
|
||||
private final LongProperty chainHeight = new SimpleLongProperty(0);
|
||||
private final ObjectProperty<List<MoneroPeer>> peers = new SimpleObjectProperty<>();
|
||||
private final DownloadListener downloadListener = new DownloadListener();
|
||||
private final List<Runnable> setupTaskHandlers = new ArrayList<>();
|
||||
private final List<Runnable> setupCompletedHandlers = new ArrayList<>();
|
||||
public final BooleanProperty shutDownComplete = new SimpleBooleanProperty();
|
||||
private final boolean useAllProvidedNodes;
|
||||
|
@ -153,36 +139,29 @@ public class WalletsSetup {
|
|||
@Inject
|
||||
public WalletsSetup(RegTestHost regTestHost,
|
||||
AddressEntryList addressEntryList,
|
||||
XmrAddressEntryList xmrAddressEntryList,
|
||||
Preferences preferences,
|
||||
Socks5ProxyProvider socks5ProxyProvider,
|
||||
Config config,
|
||||
LocalBitcoinNode localBitcoinNode,
|
||||
BtcNodes btcNodes,
|
||||
CoreMoneroConnectionsService moneroConnectionsManager,
|
||||
@Named(Config.USER_AGENT) String userAgent,
|
||||
@Named(Config.WALLET_DIR) File walletDir,
|
||||
@Named(Config.WALLET_RPC_BIND_PORT) int walletRpcBindPort,
|
||||
@Named(Config.USE_ALL_PROVIDED_NODES) boolean useAllProvidedNodes,
|
||||
@Named(Config.NUM_CONNECTIONS_FOR_BTC) int numConnectionsForBtc,
|
||||
@Named(Config.SOCKS5_DISCOVER_MODE) String socks5DiscoverModeString) {
|
||||
this.regTestHost = regTestHost;
|
||||
this.addressEntryList = addressEntryList;
|
||||
this.xmrAddressEntryList = xmrAddressEntryList;
|
||||
this.preferences = preferences;
|
||||
this.socks5ProxyProvider = socks5ProxyProvider;
|
||||
this.config = config;
|
||||
this.localBitcoinNode = localBitcoinNode;
|
||||
this.btcNodes = btcNodes;
|
||||
this.moneroConnectionsManager = moneroConnectionsManager;
|
||||
this.numConnectionsForBtc = numConnectionsForBtc;
|
||||
this.useAllProvidedNodes = useAllProvidedNodes;
|
||||
this.userAgent = userAgent;
|
||||
this.socks5DiscoverMode = evaluateMode(socks5DiscoverModeString);
|
||||
this.walletDir = walletDir;
|
||||
this.walletRpcBindPort = walletRpcBindPort;
|
||||
|
||||
xmrWalletFileName = "haveno_" + config.baseCurrencyNetwork.getCurrencyCode();
|
||||
params = Config.baseCurrencyNetworkParameters();
|
||||
PeerGroup.setIgnoreHttpSeeds(true);
|
||||
}
|
||||
|
@ -206,31 +185,23 @@ public class WalletsSetup {
|
|||
exceptionHandler.handleException(new TimeoutException("Wallet did not initialize in " +
|
||||
STARTUP_TIMEOUT + " seconds.")), STARTUP_TIMEOUT);
|
||||
|
||||
// initialize Monero connection manager
|
||||
moneroConnectionsManager.initialize();
|
||||
|
||||
backupWallets();
|
||||
|
||||
final Socks5Proxy socks5Proxy = preferences.getUseTorForBitcoinJ() ? socks5ProxyProvider.getSocks5Proxy() : null;
|
||||
log.info("Socks5Proxy for bitcoinj: socks5Proxy=" + socks5Proxy);
|
||||
|
||||
walletConfig = new WalletConfig(params, walletDir, walletRpcBindPort, moneroConnectionsManager, "haveno") {
|
||||
walletConfig = new WalletConfig(params, walletDir, "haveno") {
|
||||
@Override
|
||||
protected void onSetupCompleted() {
|
||||
//We are here in the btcj thread Thread[ STARTING,5,main]
|
||||
super.onSetupCompleted();
|
||||
|
||||
final PeerGroup peerGroup = walletConfig.peerGroup();
|
||||
final BlockChain chain = walletConfig.chain();
|
||||
|
||||
// We don't want to get our node white list polluted with nodes from AddressMessage calls.
|
||||
if (preferences.getBitcoinNodes() != null && !preferences.getBitcoinNodes().isEmpty())
|
||||
peerGroup.setAddPeersFromAddressMessage(false);
|
||||
|
||||
UserThread.runPeriodically(() -> {
|
||||
updateDaemonInfo();
|
||||
}, DAEMON_POLL_INTERVAL_SECONDS);
|
||||
|
||||
// Need to be Threading.SAME_THREAD executor otherwise BitcoinJ will skip that listener
|
||||
peerGroup.addPreMessageReceivedEventListener(Threading.SAME_THREAD, (peer, message) -> {
|
||||
if (message instanceof RejectMessage) {
|
||||
|
@ -244,11 +215,12 @@ public class WalletsSetup {
|
|||
return message;
|
||||
});
|
||||
|
||||
// run external startup handlers
|
||||
setupTaskHandlers.forEach(Runnable::run);
|
||||
|
||||
// Map to user thread
|
||||
UserThread.execute(() -> {
|
||||
updateDaemonInfo();
|
||||
addressEntryList.onWalletReady(walletConfig.btcWallet());
|
||||
xmrAddressEntryList.onWalletReady(walletConfig.getXmrWallet());
|
||||
timeoutTimer.stop();
|
||||
setupCompletedHandlers.forEach(Runnable::run);
|
||||
});
|
||||
|
@ -256,23 +228,6 @@ public class WalletsSetup {
|
|||
// onSetupCompleted in walletAppKit is not the called on the last invocations, so we add a bit of delay
|
||||
UserThread.runAfter(resultHandler::handleResult, 100, TimeUnit.MILLISECONDS);
|
||||
}
|
||||
|
||||
private void updateDaemonInfo() {
|
||||
try {
|
||||
if (vXmrDaemon == null) throw new RuntimeException("No daemon connection");
|
||||
peers.set(getOnlinePeers());
|
||||
numPeers.set(peers.get().size());
|
||||
chainHeight.set(vXmrDaemon.getHeight());
|
||||
} catch (Exception e) {
|
||||
log.warn("Could not update daemon info: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
private List<MoneroPeer> getOnlinePeers() {
|
||||
return vXmrDaemon.getPeers().stream()
|
||||
.filter(peer -> peer.isOnline())
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
};
|
||||
walletConfig.setSocks5Proxy(socks5Proxy);
|
||||
walletConfig.setConfig(config);
|
||||
|
@ -427,9 +382,7 @@ public class WalletsSetup {
|
|||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
public void backupWallets() {
|
||||
FileUtil.rollingBackup(walletDir, xmrWalletFileName, 20);
|
||||
FileUtil.rollingBackup(walletDir, xmrWalletFileName + ".keys", 20);
|
||||
FileUtil.rollingBackup(walletDir, xmrWalletFileName + ".address.txt", 20);
|
||||
// TODO: remove?
|
||||
}
|
||||
|
||||
public void clearBackups() {
|
||||
|
@ -479,6 +432,10 @@ public class WalletsSetup {
|
|||
// Handlers
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
public void addSetupTaskHandler(Runnable handler) {
|
||||
setupTaskHandlers.add(handler);
|
||||
}
|
||||
|
||||
public void addSetupCompletedHandler(Runnable handler) {
|
||||
setupCompletedHandlers.add(handler);
|
||||
}
|
||||
|
@ -492,14 +449,6 @@ public class WalletsSetup {
|
|||
return walletConfig.btcWallet();
|
||||
}
|
||||
|
||||
public MoneroDaemon getXmrDaemon() {
|
||||
return walletConfig.getXmrDaemon();
|
||||
}
|
||||
|
||||
public MoneroWallet getXmrWallet() {
|
||||
return walletConfig.getXmrWallet();
|
||||
}
|
||||
|
||||
public NetworkParameters getParams() {
|
||||
return params;
|
||||
}
|
||||
|
@ -521,10 +470,6 @@ public class WalletsSetup {
|
|||
return numPeers;
|
||||
}
|
||||
|
||||
public ReadOnlyObjectProperty<List<MoneroPeer>> peerConnectionsProperty() {
|
||||
return peers;
|
||||
}
|
||||
|
||||
public LongProperty chainHeightProperty() {
|
||||
return chainHeight;
|
||||
}
|
||||
|
@ -538,14 +483,7 @@ public class WalletsSetup {
|
|||
}
|
||||
|
||||
public boolean isChainHeightSyncedWithinTolerance() {
|
||||
Long peersChainHeight = walletConfig.vXmrDaemon.getSyncInfo().getTargetHeight();
|
||||
if (peersChainHeight == 0) return true; // monero-daemon-rpc sync_info's target_height returns 0 when node is fully synced
|
||||
long bestChainHeight = chainHeight.get();
|
||||
if (Math.abs(peersChainHeight - bestChainHeight) <= 3) {
|
||||
return true;
|
||||
}
|
||||
log.warn("Our chain height: {} is out of sync with peer nodes chain height: {}", chainHeight.get(), peersChainHeight);
|
||||
return false;
|
||||
throw new RuntimeException("WalletsSetup.isChainHeightSyncedWithinTolerance() not implemented for BTC");
|
||||
}
|
||||
|
||||
public Set<Address> getAddressesByContext(@SuppressWarnings("SameParameterValue") AddressEntry.Context context) {
|
||||
|
|
|
@ -20,15 +20,12 @@ package bisq.core.btc.wallet;
|
|||
import bisq.core.btc.exceptions.SigningException;
|
||||
import bisq.core.btc.exceptions.TransactionVerificationException;
|
||||
import bisq.core.btc.exceptions.WalletException;
|
||||
import bisq.core.btc.model.AddressEntry;
|
||||
import bisq.core.btc.model.InputsAndChangeOutput;
|
||||
import bisq.core.btc.model.PreparedDepositTxAndMakerInputs;
|
||||
import bisq.core.btc.model.RawTransactionInput;
|
||||
import bisq.core.btc.setup.WalletConfig;
|
||||
import bisq.core.btc.setup.WalletsSetup;
|
||||
import bisq.core.locale.Res;
|
||||
import bisq.core.user.Preferences;
|
||||
import bisq.core.util.ParsingUtils;
|
||||
|
||||
import bisq.common.config.Config;
|
||||
import bisq.common.util.Tuple2;
|
||||
|
@ -37,7 +34,6 @@ import org.bitcoinj.core.Address;
|
|||
import org.bitcoinj.core.AddressFormatException;
|
||||
import org.bitcoinj.core.Coin;
|
||||
import org.bitcoinj.core.ECKey;
|
||||
import org.bitcoinj.core.InsufficientMoneyException;
|
||||
import org.bitcoinj.core.NetworkParameters;
|
||||
import org.bitcoinj.core.SegwitAddress;
|
||||
import org.bitcoinj.core.Sha256Hash;
|
||||
|
@ -78,11 +74,6 @@ import static com.google.common.base.Preconditions.checkNotNull;
|
|||
|
||||
|
||||
|
||||
import monero.wallet.MoneroWallet;
|
||||
import monero.wallet.model.MoneroDestination;
|
||||
import monero.wallet.model.MoneroTxConfig;
|
||||
import monero.wallet.model.MoneroTxWallet;
|
||||
|
||||
public class TradeWalletService {
|
||||
private static final Logger log = LoggerFactory.getLogger(TradeWalletService.class);
|
||||
private static final Coin MIN_DELAYED_PAYOUT_TX_FEE = Coin.valueOf(1000);
|
||||
|
@ -94,8 +85,6 @@ public class TradeWalletService {
|
|||
@Nullable
|
||||
private Wallet wallet;
|
||||
@Nullable
|
||||
private MoneroWallet xmrWallet;
|
||||
@Nullable
|
||||
private WalletConfig walletConfig;
|
||||
@Nullable
|
||||
private KeyParameter aesKey;
|
||||
|
@ -113,7 +102,6 @@ public class TradeWalletService {
|
|||
walletsSetup.addSetupCompletedHandler(() -> {
|
||||
walletConfig = walletsSetup.getWalletConfig();
|
||||
wallet = walletsSetup.getBtcWallet();
|
||||
xmrWallet = walletsSetup.getXmrWallet();
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -132,25 +120,6 @@ public class TradeWalletService {
|
|||
}
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Trade fee
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
public MoneroTxWallet createXmrTradingFeeTx(
|
||||
String reservedForTradeAddress,
|
||||
Coin reservedFundsForOffer,
|
||||
Coin makerFee,
|
||||
Coin txFee,
|
||||
String feeReceiver,
|
||||
boolean broadcastTx) {
|
||||
return xmrWallet.createTx(new MoneroTxConfig()
|
||||
.setAccountIndex(0)
|
||||
.setDestinations(
|
||||
new MoneroDestination(feeReceiver, ParsingUtils.coinToAtomicUnits(makerFee)),
|
||||
new MoneroDestination(reservedForTradeAddress, ParsingUtils.coinToAtomicUnits(reservedFundsForOffer)))
|
||||
.setRelay(broadcastTx));
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Deposit tx
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
@ -1061,16 +1030,6 @@ public class TradeWalletService {
|
|||
// Misc
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
/**
|
||||
* Returns the local existing wallet transaction with the given ID, or {@code null} if missing.
|
||||
*
|
||||
* @param txHash the transaction hash of the transaction we want to lookup
|
||||
*/
|
||||
public MoneroTxWallet getWalletTx(String txHash) {
|
||||
checkNotNull(xmrWallet);
|
||||
return xmrWallet.getTx(txHash);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the local existing wallet transaction with the given ID, or {@code null} if missing.
|
||||
*
|
||||
|
|
|
@ -18,9 +18,8 @@
|
|||
package bisq.core.btc.wallet;
|
||||
|
||||
import bisq.core.btc.setup.WalletsSetup;
|
||||
import bisq.core.crypto.ScryptUtil;
|
||||
import bisq.core.locale.Res;
|
||||
|
||||
import bisq.common.crypto.ScryptUtil;
|
||||
import bisq.common.handlers.ExceptionHandler;
|
||||
import bisq.common.handlers.ResultHandler;
|
||||
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -1,79 +0,0 @@
|
|||
/*
|
||||
* This file is part of Haveno.
|
||||
*
|
||||
* Haveno is free software: you can redistribute it and/or modify it
|
||||
* under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or (at
|
||||
* your option) any later version.
|
||||
*
|
||||
* Haveno is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
|
||||
* License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with Haveno. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package bisq.core.crypto;
|
||||
|
||||
import bisq.common.UserThread;
|
||||
import bisq.common.util.Utilities;
|
||||
|
||||
import com.google.protobuf.ByteString;
|
||||
|
||||
import org.bitcoinj.crypto.KeyCrypterScrypt;
|
||||
import org.bitcoinj.wallet.Protos;
|
||||
|
||||
import org.bouncycastle.crypto.params.KeyParameter;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
//TODO: Borrowed form BitcoinJ/Lighthouse. Remove Protos dependency, check complete code logic.
|
||||
public class ScryptUtil {
|
||||
private static final Logger log = LoggerFactory.getLogger(ScryptUtil.class);
|
||||
|
||||
public interface DeriveKeyResultHandler {
|
||||
void handleResult(KeyParameter aesKey);
|
||||
}
|
||||
|
||||
public static KeyCrypterScrypt getKeyCrypterScrypt() {
|
||||
return getKeyCrypterScrypt(KeyCrypterScrypt.randomSalt());
|
||||
}
|
||||
|
||||
public static KeyCrypterScrypt getKeyCrypterScrypt(byte[] salt) {
|
||||
Protos.ScryptParameters scryptParameters = Protos.ScryptParameters.newBuilder()
|
||||
.setP(6)
|
||||
.setR(8)
|
||||
.setN(32768)
|
||||
.setSalt(ByteString.copyFrom(salt))
|
||||
.build();
|
||||
return new KeyCrypterScrypt(scryptParameters);
|
||||
}
|
||||
|
||||
public static void deriveKeyWithScrypt(KeyCrypterScrypt keyCrypterScrypt, String password, DeriveKeyResultHandler resultHandler) {
|
||||
Utilities.getThreadPoolExecutor("ScryptUtil:deriveKeyWithScrypt-%d", 1, 2, 5L).submit(() -> {
|
||||
try {
|
||||
log.debug("Doing key derivation");
|
||||
long start = System.currentTimeMillis();
|
||||
KeyParameter aesKey = keyCrypterScrypt.deriveKey(password);
|
||||
long duration = System.currentTimeMillis() - start;
|
||||
log.debug("Key derivation took {} msec", duration);
|
||||
UserThread.execute(() -> {
|
||||
try {
|
||||
resultHandler.handleResult(aesKey);
|
||||
} catch (Throwable t) {
|
||||
t.printStackTrace();
|
||||
log.error("Executing task failed. " + t.getMessage());
|
||||
throw t;
|
||||
}
|
||||
});
|
||||
} catch (Throwable t) {
|
||||
t.printStackTrace();
|
||||
log.error("Executing task failed. " + t.getMessage());
|
||||
throw t;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
|
@ -24,8 +24,7 @@ import bisq.core.notifications.MobileNotificationService;
|
|||
import bisq.core.trade.Trade;
|
||||
import bisq.core.trade.TradeManager;
|
||||
|
||||
import bisq.common.crypto.KeyRing;
|
||||
import bisq.common.crypto.PubKeyRing;
|
||||
import bisq.common.crypto.PubKeyRingProvider;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Singleton;
|
||||
|
@ -41,15 +40,15 @@ import lombok.extern.slf4j.Slf4j;
|
|||
@Slf4j
|
||||
@Singleton
|
||||
public class TradeEvents {
|
||||
private final PubKeyRing pubKeyRing;
|
||||
private final PubKeyRingProvider pubKeyRingProvider;
|
||||
private final TradeManager tradeManager;
|
||||
private final MobileNotificationService mobileNotificationService;
|
||||
|
||||
@Inject
|
||||
public TradeEvents(TradeManager tradeManager, KeyRing keyRing, MobileNotificationService mobileNotificationService) {
|
||||
public TradeEvents(TradeManager tradeManager, PubKeyRingProvider pubKeyRingProvider, MobileNotificationService mobileNotificationService) {
|
||||
this.tradeManager = tradeManager;
|
||||
this.mobileNotificationService = mobileNotificationService;
|
||||
this.pubKeyRing = keyRing.getPubKeyRing();
|
||||
this.pubKeyRingProvider = pubKeyRingProvider;
|
||||
}
|
||||
|
||||
public void onAllServicesInitialized() {
|
||||
|
@ -74,19 +73,19 @@ public class TradeEvents {
|
|||
case DEPOSIT_PUBLISHED:
|
||||
break;
|
||||
case DEPOSIT_CONFIRMED:
|
||||
if (trade.getContract() != null && pubKeyRing.equals(trade.getContract().getBuyerPubKeyRing()))
|
||||
if (trade.getContract() != null && pubKeyRingProvider.get().equals(trade.getContract().getBuyerPubKeyRing()))
|
||||
msg = Res.get("account.notifications.trade.message.msg.conf", shortId);
|
||||
break;
|
||||
case FIAT_SENT:
|
||||
// We only notify the seller
|
||||
if (trade.getContract() != null && pubKeyRing.equals(trade.getContract().getSellerPubKeyRing()))
|
||||
if (trade.getContract() != null && pubKeyRingProvider.get().equals(trade.getContract().getSellerPubKeyRing()))
|
||||
msg = Res.get("account.notifications.trade.message.msg.started", shortId);
|
||||
break;
|
||||
case FIAT_RECEIVED:
|
||||
break;
|
||||
case PAYOUT_PUBLISHED:
|
||||
// We only notify the buyer
|
||||
if (trade.getContract() != null && pubKeyRing.equals(trade.getContract().getBuyerPubKeyRing()))
|
||||
if (trade.getContract() != null && pubKeyRingProvider.get().equals(trade.getContract().getBuyerPubKeyRing()))
|
||||
msg = Res.get("account.notifications.trade.message.msg.completed", shortId);
|
||||
break;
|
||||
case WITHDRAWN:
|
||||
|
|
|
@ -39,7 +39,7 @@ import bisq.network.p2p.NodeAddress;
|
|||
import bisq.network.p2p.P2PService;
|
||||
|
||||
import bisq.common.app.Version;
|
||||
import bisq.common.crypto.PubKeyRing;
|
||||
import bisq.common.crypto.PubKeyRingProvider;
|
||||
import bisq.common.util.Tuple2;
|
||||
import bisq.common.util.Utilities;
|
||||
|
||||
|
@ -65,7 +65,7 @@ public class CreateOfferService {
|
|||
private final TxFeeEstimationService txFeeEstimationService;
|
||||
private final PriceFeedService priceFeedService;
|
||||
private final P2PService p2PService;
|
||||
private final PubKeyRing pubKeyRing;
|
||||
private final PubKeyRingProvider pubKeyRingProvider;
|
||||
private final User user;
|
||||
private final BtcWalletService btcWalletService;
|
||||
private final TradeStatisticsManager tradeStatisticsManager;
|
||||
|
@ -81,7 +81,7 @@ public class CreateOfferService {
|
|||
TxFeeEstimationService txFeeEstimationService,
|
||||
PriceFeedService priceFeedService,
|
||||
P2PService p2PService,
|
||||
PubKeyRing pubKeyRing,
|
||||
PubKeyRingProvider pubKeyRingProvider,
|
||||
User user,
|
||||
BtcWalletService btcWalletService,
|
||||
TradeStatisticsManager tradeStatisticsManager,
|
||||
|
@ -90,7 +90,7 @@ public class CreateOfferService {
|
|||
this.txFeeEstimationService = txFeeEstimationService;
|
||||
this.priceFeedService = priceFeedService;
|
||||
this.p2PService = p2PService;
|
||||
this.pubKeyRing = pubKeyRing;
|
||||
this.pubKeyRingProvider = pubKeyRingProvider;
|
||||
this.user = user;
|
||||
this.btcWalletService = btcWalletService;
|
||||
this.tradeStatisticsManager = tradeStatisticsManager;
|
||||
|
@ -190,14 +190,14 @@ public class CreateOfferService {
|
|||
paymentAccount,
|
||||
currencyCode,
|
||||
makerFeeAsCoin);
|
||||
|
||||
|
||||
// select signing arbitrator
|
||||
Mediator arbitrator = DisputeAgentSelection.getLeastUsedArbitrator(tradeStatisticsManager, mediatorManager); // TODO (woodser): using mediator manager for arbitrators
|
||||
|
||||
OfferPayload offerPayload = new OfferPayload(offerId,
|
||||
creationTime,
|
||||
makerAddress,
|
||||
pubKeyRing,
|
||||
pubKeyRingProvider.get(),
|
||||
OfferPayload.Direction.valueOf(direction.name()),
|
||||
priceAsLong,
|
||||
marketPriceMarginParam,
|
||||
|
|
|
@ -63,7 +63,7 @@ public class OfferFilter {
|
|||
this.filterManager = filterManager;
|
||||
this.accountAgeWitnessService = accountAgeWitnessService;
|
||||
|
||||
if (user != null) {
|
||||
if (user != null && user.getPaymentAccountsAsObservable() != null) {
|
||||
// If our accounts have changed we reset our myInsufficientTradeLimitCache as it depends on account data
|
||||
user.getPaymentAccountsAsObservable().addListener((SetChangeListener<PaymentAccount>) c ->
|
||||
myInsufficientTradeLimitCache.clear());
|
||||
|
@ -212,13 +212,13 @@ public class OfferFilter {
|
|||
myInsufficientTradeLimitCache.put(offerId, result);
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
public boolean hasValidSignature(Offer offer) {
|
||||
|
||||
|
||||
// get arbitrator
|
||||
Mediator arbitrator = user.getAcceptedMediatorByAddress(offer.getOfferPayload().getArbitratorSigner());
|
||||
if (arbitrator == null) return false; // invalid arbitrator
|
||||
|
||||
|
||||
// validate arbitrator signature
|
||||
return TradeUtils.isArbitratorSignatureValid(offer.getOfferPayload(), arbitrator);
|
||||
}
|
||||
|
|
|
@ -18,6 +18,7 @@
|
|||
package bisq.core.offer;
|
||||
|
||||
import bisq.core.api.CoreContext;
|
||||
import bisq.core.api.CoreMoneroConnectionsService;
|
||||
import bisq.core.btc.wallet.BtcWalletService;
|
||||
import bisq.core.btc.wallet.TradeWalletService;
|
||||
import bisq.core.btc.wallet.XmrWalletService;
|
||||
|
@ -110,6 +111,7 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
|
|||
private final KeyRing keyRing;
|
||||
private final User user;
|
||||
private final P2PService p2PService;
|
||||
private final CoreMoneroConnectionsService connectionService;
|
||||
private final BtcWalletService btcWalletService;
|
||||
private final XmrWalletService xmrWalletService;
|
||||
private final TradeWalletService tradeWalletService;
|
||||
|
@ -144,6 +146,7 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
|
|||
KeyRing keyRing,
|
||||
User user,
|
||||
P2PService p2PService,
|
||||
CoreMoneroConnectionsService connectionService,
|
||||
BtcWalletService btcWalletService,
|
||||
XmrWalletService xmrWalletService,
|
||||
TradeWalletService tradeWalletService,
|
||||
|
@ -163,6 +166,7 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
|
|||
this.keyRing = keyRing;
|
||||
this.user = user;
|
||||
this.p2PService = p2PService;
|
||||
this.connectionService = connectionService;
|
||||
this.btcWalletService = btcWalletService;
|
||||
this.xmrWalletService = xmrWalletService;
|
||||
this.tradeWalletService = tradeWalletService;
|
||||
|
@ -751,8 +755,8 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
|
|||
return;
|
||||
}
|
||||
|
||||
// Don't allow trade start if BitcoinJ is not fully synced (bisq issue #4764)
|
||||
if (!btcWalletService.isChainHeightSyncedWithinTolerance()) {
|
||||
// Don't allow trade start if Monero node is not fully synced
|
||||
if (!connectionService.isChainHeightSyncedWithinTolerance()) {
|
||||
errorMessage = "We got a handleOfferAvailabilityRequest but our chain is not synced.";
|
||||
log.info(errorMessage);
|
||||
sendAckMessage(request.getClass(), peer, request.getPubKeyRing(), request.getOfferId(), request.getUid(), false, errorMessage);
|
||||
|
|
|
@ -17,7 +17,7 @@
|
|||
|
||||
package bisq.core.support;
|
||||
|
||||
import bisq.core.btc.setup.WalletsSetup;
|
||||
import bisq.core.api.CoreMoneroConnectionsService;
|
||||
import bisq.core.locale.Res;
|
||||
import bisq.core.support.messages.ChatMessage;
|
||||
import bisq.core.support.messages.SupportMessage;
|
||||
|
@ -47,7 +47,7 @@ import javax.annotation.Nullable;
|
|||
@Slf4j
|
||||
public abstract class SupportManager {
|
||||
protected final P2PService p2PService;
|
||||
protected final WalletsSetup walletsSetup;
|
||||
protected final CoreMoneroConnectionsService connectionService;
|
||||
protected final Map<String, Timer> delayMsgMap = new HashMap<>();
|
||||
private final CopyOnWriteArraySet<DecryptedMessageWithPubKey> decryptedMailboxMessageWithPubKeys = new CopyOnWriteArraySet<>();
|
||||
private final CopyOnWriteArraySet<DecryptedMessageWithPubKey> decryptedDirectMessageWithPubKeys = new CopyOnWriteArraySet<>();
|
||||
|
@ -59,12 +59,11 @@ public abstract class SupportManager {
|
|||
// Constructor
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
public SupportManager(P2PService p2PService, WalletsSetup walletsSetup) {
|
||||
public SupportManager(P2PService p2PService, CoreMoneroConnectionsService connectionService) {
|
||||
this.p2PService = p2PService;
|
||||
this.connectionService = connectionService;
|
||||
mailboxMessageService = p2PService.getMailboxMessageService();
|
||||
|
||||
this.walletsSetup = walletsSetup;
|
||||
|
||||
// We get first the message handler called then the onBootstrapped
|
||||
p2PService.addDecryptedDirectMessageListener((decryptedMessageWithPubKey, senderAddress) -> {
|
||||
// As decryptedDirectMessageWithPubKeys is a CopyOnWriteArraySet we do not need to check if it was
|
||||
|
@ -293,8 +292,8 @@ public abstract class SupportManager {
|
|||
private boolean isReady() {
|
||||
return allServicesInitialized &&
|
||||
p2PService.isBootstrapped() &&
|
||||
walletsSetup.isDownloadComplete() &&
|
||||
walletsSetup.hasSufficientPeersForBroadcast();
|
||||
connectionService.isDownloadComplete() &&
|
||||
connectionService.hasSufficientPeersForBroadcast();
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -17,7 +17,7 @@
|
|||
|
||||
package bisq.core.support.dispute;
|
||||
|
||||
import bisq.core.btc.setup.WalletsSetup;
|
||||
import bisq.core.api.CoreMoneroConnectionsService;
|
||||
import bisq.core.btc.wallet.Restrictions;
|
||||
import bisq.core.btc.wallet.TradeWalletService;
|
||||
import bisq.core.btc.wallet.XmrWalletService;
|
||||
|
@ -39,7 +39,6 @@ import bisq.core.trade.Trade;
|
|||
import bisq.core.trade.TradeDataValidation;
|
||||
import bisq.core.trade.TradeManager;
|
||||
import bisq.core.trade.closed.ClosedTradableManager;
|
||||
|
||||
import bisq.network.p2p.BootstrapListener;
|
||||
import bisq.network.p2p.NodeAddress;
|
||||
import bisq.network.p2p.P2PService;
|
||||
|
@ -111,7 +110,7 @@ public abstract class DisputeManager<T extends DisputeList<Dispute>> extends Sup
|
|||
public DisputeManager(P2PService p2PService,
|
||||
TradeWalletService tradeWalletService,
|
||||
XmrWalletService xmrWalletService,
|
||||
WalletsSetup walletsSetup,
|
||||
CoreMoneroConnectionsService connectionService,
|
||||
TradeManager tradeManager,
|
||||
ClosedTradableManager closedTradableManager,
|
||||
OpenOfferManager openOfferManager,
|
||||
|
@ -119,7 +118,7 @@ public abstract class DisputeManager<T extends DisputeList<Dispute>> extends Sup
|
|||
DisputeListService<T> disputeListService,
|
||||
Config config,
|
||||
PriceFeedService priceFeedService) {
|
||||
super(p2PService, walletsSetup);
|
||||
super(p2PService, connectionService);
|
||||
|
||||
this.tradeWalletService = tradeWalletService;
|
||||
this.xmrWalletService = xmrWalletService;
|
||||
|
@ -252,13 +251,13 @@ public abstract class DisputeManager<T extends DisputeList<Dispute>> extends Sup
|
|||
}
|
||||
});
|
||||
|
||||
walletsSetup.downloadPercentageProperty().addListener((observable, oldValue, newValue) -> {
|
||||
if (walletsSetup.isDownloadComplete())
|
||||
connectionService.downloadPercentageProperty().addListener((observable, oldValue, newValue) -> {
|
||||
if (connectionService.isDownloadComplete())
|
||||
tryApplyMessages();
|
||||
});
|
||||
|
||||
walletsSetup.numPeersProperty().addListener((observable, oldValue, newValue) -> {
|
||||
if (walletsSetup.hasSufficientPeersForBroadcast())
|
||||
connectionService.numPeersProperty().addListener((observable, oldValue, newValue) -> {
|
||||
if (connectionService.hasSufficientPeersForBroadcast())
|
||||
tryApplyMessages();
|
||||
});
|
||||
|
||||
|
|
|
@ -17,7 +17,7 @@
|
|||
|
||||
package bisq.core.support.dispute.arbitration;
|
||||
|
||||
import bisq.core.btc.setup.WalletsSetup;
|
||||
import bisq.core.api.CoreMoneroConnectionsService;
|
||||
import bisq.core.btc.wallet.TradeWalletService;
|
||||
import bisq.core.btc.wallet.XmrWalletService;
|
||||
import bisq.core.locale.Res;
|
||||
|
@ -95,7 +95,7 @@ public final class ArbitrationManager extends DisputeManager<ArbitrationDisputeL
|
|||
public ArbitrationManager(P2PService p2PService,
|
||||
TradeWalletService tradeWalletService,
|
||||
XmrWalletService walletService,
|
||||
WalletsSetup walletsSetup,
|
||||
CoreMoneroConnectionsService connectionService,
|
||||
TradeManager tradeManager,
|
||||
ClosedTradableManager closedTradableManager,
|
||||
OpenOfferManager openOfferManager,
|
||||
|
@ -103,7 +103,7 @@ public final class ArbitrationManager extends DisputeManager<ArbitrationDisputeL
|
|||
ArbitrationDisputeListService arbitrationDisputeListService,
|
||||
Config config,
|
||||
PriceFeedService priceFeedService) {
|
||||
super(p2PService, tradeWalletService, walletService, walletsSetup, tradeManager, closedTradableManager,
|
||||
super(p2PService, tradeWalletService, walletService, connectionService, tradeManager, closedTradableManager,
|
||||
openOfferManager, keyRing, arbitrationDisputeListService, config, priceFeedService);
|
||||
}
|
||||
|
||||
|
@ -365,19 +365,19 @@ public final class ArbitrationManager extends DisputeManager<ArbitrationDisputeL
|
|||
cleanupRetryMap(uid);
|
||||
|
||||
// update multisig wallet
|
||||
// TODO: multisig wallet may already be deleted if peer completed trade with arbitrator. refactor trade completion?
|
||||
MoneroWallet multisigWallet = xmrWalletService.getMultisigWallet(dispute.getTradeId());
|
||||
multisigWallet.importMultisigHex(Arrays.asList(peerPublishedDisputePayoutTxMessage.getUpdatedMultisigHex()));
|
||||
|
||||
// parse payout tx
|
||||
MoneroTxWallet parsedPayoutTx = multisigWallet.describeTxSet(new MoneroTxSet().setMultisigTxHex(peerPublishedDisputePayoutTxMessage.getPayoutTxHex())).getTxs().get(0);
|
||||
if (multisigWallet != null) {
|
||||
multisigWallet.importMultisigHex(Arrays.asList(peerPublishedDisputePayoutTxMessage.getUpdatedMultisigHex()));
|
||||
MoneroTxWallet parsedPayoutTx = multisigWallet.describeTxSet(new MoneroTxSet().setMultisigTxHex(peerPublishedDisputePayoutTxMessage.getPayoutTxHex())).getTxs().get(0);
|
||||
dispute.setDisputePayoutTxId(parsedPayoutTx.getHash());
|
||||
XmrWalletService.printTxs("Disputed payoutTx received from peer", parsedPayoutTx);
|
||||
}
|
||||
|
||||
// System.out.println("LOSER'S VIEW OF MULTISIG WALLET (SHOULD INCLUDE PAYOUT TX):\n" + multisigWallet.getTxs());
|
||||
// if (multisigWallet.getTxs().size() != 3) throw new RuntimeException("Loser's multisig wallet does not include record of payout tx");
|
||||
// Transaction committedDisputePayoutTx = WalletService.maybeAddNetworkTxToWallet(peerPublishedDisputePayoutTxMessage.getTransaction(), btcWalletService.getWallet());
|
||||
|
||||
dispute.setDisputePayoutTxId(parsedPayoutTx.getHash());
|
||||
XmrWalletService.printTxs("Disputed payoutTx received from peer", parsedPayoutTx);
|
||||
|
||||
// We can only send the ack msg if we have the peersPubKeyRing which requires the dispute
|
||||
sendAckMessage(peerPublishedDisputePayoutTxMessage, peersPubKeyRing, true, null);
|
||||
requestPersistence();
|
||||
|
@ -436,7 +436,7 @@ public final class ArbitrationManager extends DisputeManager<ArbitrationDisputeL
|
|||
UUID.randomUUID().toString(),
|
||||
SupportType.ARBITRATION,
|
||||
payoutTx.getTxSet().getMultisigTxHex());
|
||||
log.info("Send {} to peer {}. tradeId={}, uid={}", response.getClass().getSimpleName(), contract.getArbitratorNodeAddress(), dispute.getTradeId(), response.getUid());
|
||||
log.info("Send {} to peer {}. tradeId={}, uid={}", response.getClass().getSimpleName(), request.getSenderNodeAddress(), dispute.getTradeId(), response.getUid());
|
||||
p2PService.sendEncryptedDirectMessage(request.getSenderNodeAddress(),
|
||||
senderPubKeyRing,
|
||||
response,
|
||||
|
@ -644,8 +644,6 @@ public final class ArbitrationManager extends DisputeManager<ArbitrationDisputeL
|
|||
numAttempts++;
|
||||
payoutTx = multisigWallet.createTx(txConfig);
|
||||
} catch (MoneroError e) {
|
||||
System.out.println(e.toString());
|
||||
System.out.println(e.getStackTrace());
|
||||
// exception expected // TODO: better way of estimating fee?
|
||||
}
|
||||
}
|
||||
|
@ -716,7 +714,6 @@ public final class ArbitrationManager extends DisputeManager<ArbitrationDisputeL
|
|||
|
||||
// TODO (woodser): VERIFY PAYOUT TX AMOUNTS WHICH CONSIDERS FEE IF LONG TRADE, EXACT AMOUNT IF SHORT TRADE
|
||||
|
||||
|
||||
// if (!buyerPayoutDestination.getAmount().equals(expectedBuyerPayout)) throw new RuntimeException("Buyer destination amount is not payout amount - 1/2 tx costs, " + buyerPayoutDestination.getAmount() + " vs " + expectedBuyerPayout);
|
||||
|
||||
// verify seller destination amount is payout amount - 1/2 tx costs
|
||||
|
|
|
@ -17,7 +17,7 @@
|
|||
|
||||
package bisq.core.support.dispute.mediation;
|
||||
|
||||
import bisq.core.btc.setup.WalletsSetup;
|
||||
import bisq.core.api.CoreMoneroConnectionsService;
|
||||
import bisq.core.btc.wallet.TradeWalletService;
|
||||
import bisq.core.btc.wallet.XmrWalletService;
|
||||
import bisq.core.locale.Res;
|
||||
|
@ -77,7 +77,7 @@ public final class MediationManager extends DisputeManager<MediationDisputeList>
|
|||
public MediationManager(P2PService p2PService,
|
||||
TradeWalletService tradeWalletService,
|
||||
XmrWalletService walletService,
|
||||
WalletsSetup walletsSetup,
|
||||
CoreMoneroConnectionsService connectionService,
|
||||
TradeManager tradeManager,
|
||||
ClosedTradableManager closedTradableManager,
|
||||
OpenOfferManager openOfferManager,
|
||||
|
@ -85,7 +85,7 @@ public final class MediationManager extends DisputeManager<MediationDisputeList>
|
|||
MediationDisputeListService mediationDisputeListService,
|
||||
Config config,
|
||||
PriceFeedService priceFeedService) {
|
||||
super(p2PService, tradeWalletService, walletService, walletsSetup, tradeManager, closedTradableManager,
|
||||
super(p2PService, tradeWalletService, walletService, connectionService, tradeManager, closedTradableManager,
|
||||
openOfferManager, keyRing, mediationDisputeListService, config, priceFeedService);
|
||||
}
|
||||
|
||||
|
|
|
@ -17,7 +17,7 @@
|
|||
|
||||
package bisq.core.support.dispute.refund;
|
||||
|
||||
import bisq.core.btc.setup.WalletsSetup;
|
||||
import bisq.core.api.CoreMoneroConnectionsService;
|
||||
import bisq.core.btc.wallet.TradeWalletService;
|
||||
import bisq.core.btc.wallet.XmrWalletService;
|
||||
import bisq.core.locale.Res;
|
||||
|
@ -71,7 +71,7 @@ public final class RefundManager extends DisputeManager<RefundDisputeList> {
|
|||
public RefundManager(P2PService p2PService,
|
||||
TradeWalletService tradeWalletService,
|
||||
XmrWalletService walletService,
|
||||
WalletsSetup walletsSetup,
|
||||
CoreMoneroConnectionsService connectionService,
|
||||
TradeManager tradeManager,
|
||||
ClosedTradableManager closedTradableManager,
|
||||
OpenOfferManager openOfferManager,
|
||||
|
@ -80,7 +80,7 @@ public final class RefundManager extends DisputeManager<RefundDisputeList> {
|
|||
RefundDisputeListService refundDisputeListService,
|
||||
Config config,
|
||||
PriceFeedService priceFeedService) {
|
||||
super(p2PService, tradeWalletService, walletService, walletsSetup, tradeManager, closedTradableManager,
|
||||
super(p2PService, tradeWalletService, walletService, connectionService, tradeManager, closedTradableManager,
|
||||
openOfferManager, keyRing, refundDisputeListService, config, priceFeedService);
|
||||
}
|
||||
|
||||
|
|
|
@ -17,7 +17,7 @@
|
|||
|
||||
package bisq.core.support.traderchat;
|
||||
|
||||
import bisq.core.btc.setup.WalletsSetup;
|
||||
import bisq.core.api.CoreMoneroConnectionsService;
|
||||
import bisq.core.locale.Res;
|
||||
import bisq.core.support.SupportManager;
|
||||
import bisq.core.support.SupportType;
|
||||
|
@ -31,6 +31,7 @@ import bisq.network.p2p.NodeAddress;
|
|||
import bisq.network.p2p.P2PService;
|
||||
|
||||
import bisq.common.crypto.PubKeyRing;
|
||||
import bisq.common.crypto.PubKeyRingProvider;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Singleton;
|
||||
|
@ -46,7 +47,7 @@ import lombok.extern.slf4j.Slf4j;
|
|||
@Singleton
|
||||
public class TraderChatManager extends SupportManager {
|
||||
private final TradeManager tradeManager;
|
||||
private final PubKeyRing pubKeyRing;
|
||||
private final PubKeyRingProvider pubKeyRingProvider;
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
@ -55,12 +56,12 @@ public class TraderChatManager extends SupportManager {
|
|||
|
||||
@Inject
|
||||
public TraderChatManager(P2PService p2PService,
|
||||
WalletsSetup walletsSetup,
|
||||
CoreMoneroConnectionsService connectionService,
|
||||
TradeManager tradeManager,
|
||||
PubKeyRing pubKeyRing) {
|
||||
super(p2PService, walletsSetup);
|
||||
PubKeyRingProvider pubKeyRingProvider) {
|
||||
super(p2PService, connectionService);
|
||||
this.tradeManager = tradeManager;
|
||||
this.pubKeyRing = pubKeyRing;
|
||||
this.pubKeyRingProvider = pubKeyRingProvider;
|
||||
}
|
||||
|
||||
|
||||
|
@ -82,7 +83,7 @@ public class TraderChatManager extends SupportManager {
|
|||
public NodeAddress getPeerNodeAddress(ChatMessage message) {
|
||||
return tradeManager.getTradeById(message.getTradeId()).map(trade -> {
|
||||
if (trade.getContract() != null) {
|
||||
return trade.getContract().getPeersNodeAddress(pubKeyRing);
|
||||
return trade.getContract().getPeersNodeAddress(pubKeyRingProvider.get());
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
|
@ -93,7 +94,7 @@ public class TraderChatManager extends SupportManager {
|
|||
public PubKeyRing getPeerPubKeyRing(ChatMessage message) {
|
||||
return tradeManager.getTradeById(message.getTradeId()).map(trade -> {
|
||||
if (trade.getContract() != null) {
|
||||
return trade.getContract().getPeersPubKeyRing(pubKeyRing);
|
||||
return trade.getContract().getPeersPubKeyRing(pubKeyRingProvider.get());
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
|
@ -139,11 +140,13 @@ public class TraderChatManager extends SupportManager {
|
|||
// API
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
@Override
|
||||
public void onAllServicesInitialized() {
|
||||
super.onAllServicesInitialized();
|
||||
tryApplyMessages();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSupportMessage(SupportMessage message) {
|
||||
if (canProcessMessage(message)) {
|
||||
log.info("Received {} with tradeId {} and uid {}",
|
||||
|
|
|
@ -1108,7 +1108,8 @@ public abstract class Trade implements Tradable, Model {
|
|||
return false;
|
||||
}
|
||||
|
||||
// Legacy arbitration is not handled anymore as not used anymore.
|
||||
// check for closed disputed case
|
||||
if (disputeState == DisputeState.DISPUTE_CLOSED) return false;
|
||||
|
||||
// In mediation case we check for the mediationResultState. As there are multiple sub-states we use ordinal.
|
||||
if (disputeState == DisputeState.MEDIATION_CLOSED) {
|
||||
|
|
|
@ -118,10 +118,9 @@ import javax.annotation.Nullable;
|
|||
import static com.google.common.base.Preconditions.checkArgument;
|
||||
import static com.google.common.base.Preconditions.checkNotNull;
|
||||
|
||||
|
||||
|
||||
import monero.wallet.model.MoneroTxWallet;
|
||||
|
||||
|
||||
public class TradeManager implements PersistedDataHost, DecryptedDirectMessageListener {
|
||||
private static final Logger log = LoggerFactory.getLogger(TradeManager.class);
|
||||
|
||||
|
@ -283,6 +282,7 @@ public class TradeManager implements PersistedDataHost, DecryptedDirectMessageLi
|
|||
getObservableList().addListener((ListChangeListener<Trade>) change -> onTradesChanged());
|
||||
onTradesChanged();
|
||||
|
||||
xmrWalletService.setTradeManager(this);
|
||||
xmrWalletService.getAddressEntriesForAvailableBalanceStream()
|
||||
.filter(addressEntry -> addressEntry.getOfferId() != null)
|
||||
.forEach(addressEntry -> {
|
||||
|
@ -1014,7 +1014,7 @@ public class TradeManager implements PersistedDataHost, DecryptedDirectMessageLi
|
|||
}
|
||||
|
||||
p2PService.removeDecryptedDirectMessageListener(getTradeProtocol(trade));
|
||||
xmrWalletService.deleteMultisigWallet(trade.getId());
|
||||
xmrWalletService.deleteMultisigWallet(trade.getId()); // TODO (woodser): don't delete multisig wallet until payout tx unlocked?
|
||||
requestPersistence();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -17,6 +17,7 @@
|
|||
|
||||
package bisq.core.trade.protocol.tasks;
|
||||
|
||||
import bisq.core.btc.wallet.XmrWalletService;
|
||||
import bisq.core.trade.ArbitratorTrade;
|
||||
import bisq.core.trade.MakerTrade;
|
||||
import bisq.core.trade.TakerTrade;
|
||||
|
@ -68,6 +69,7 @@ public class ProcessInitMultisigRequest extends TradeTask {
|
|||
InitMultisigRequest request = (InitMultisigRequest) processModel.getTradeMessage();
|
||||
checkNotNull(request);
|
||||
checkTradeId(processModel.getOfferId(), request);
|
||||
XmrWalletService xmrWalletService = processModel.getProvider().getXmrWalletService();
|
||||
|
||||
System.out.println("PROCESS MULTISIG MESSAGE");
|
||||
System.out.println(request);
|
||||
|
@ -98,18 +100,18 @@ public class ProcessInitMultisigRequest extends TradeTask {
|
|||
boolean updateParticipants = false;
|
||||
if (processModel.getPreparedMultisigHex() == null) {
|
||||
System.out.println("Preparing multisig wallet!");
|
||||
multisigWallet = processModel.getProvider().getXmrWalletService().createMultisigWallet(trade.getId());
|
||||
multisigWallet = xmrWalletService.createMultisigWallet(trade.getId());
|
||||
processModel.setPreparedMultisigHex(multisigWallet.prepareMultisig());
|
||||
updateParticipants = true;
|
||||
} else {
|
||||
multisigWallet = processModel.getProvider().getXmrWalletService().getMultisigWallet(trade.getId());
|
||||
multisigWallet = xmrWalletService.getMultisigWallet(trade.getId());
|
||||
}
|
||||
|
||||
// make multisig if applicable
|
||||
TradingPeer[] peers = getMultisigPeers();
|
||||
if (processModel.getMadeMultisigHex() == null && peers[0].getPreparedMultisigHex() != null && peers[1].getPreparedMultisigHex() != null) {
|
||||
System.out.println("Making multisig wallet!");
|
||||
MoneroMultisigInitResult result = multisigWallet.makeMultisig(Arrays.asList(peers[0].getPreparedMultisigHex(), peers[1].getPreparedMultisigHex()), 2, "abctesting123"); // TODO (woodser): move this to config
|
||||
MoneroMultisigInitResult result = multisigWallet.makeMultisig(Arrays.asList(peers[0].getPreparedMultisigHex(), peers[1].getPreparedMultisigHex()), 2, xmrWalletService.getWalletPassword()); // TODO (woodser): xmrWalletService.makeMultisig(tradeId, multisigHexes, threshold)?
|
||||
processModel.setMadeMultisigHex(result.getMultisigHex());
|
||||
updateParticipants = true;
|
||||
}
|
||||
|
@ -117,7 +119,7 @@ public class ProcessInitMultisigRequest extends TradeTask {
|
|||
// exchange multisig keys if applicable
|
||||
if (!processModel.isMultisigSetupComplete() && peers[0].getMadeMultisigHex() != null && peers[1].getMadeMultisigHex() != null) {
|
||||
System.out.println("Exchanging multisig wallet!");
|
||||
multisigWallet.exchangeMultisigKeys(Arrays.asList(peers[0].getMadeMultisigHex(), peers[1].getMadeMultisigHex()), "abctesting123"); // TODO (woodser): move this to config
|
||||
multisigWallet.exchangeMultisigKeys(Arrays.asList(peers[0].getMadeMultisigHex(), peers[1].getMadeMultisigHex()), xmrWalletService.getWalletPassword());
|
||||
processModel.setMultisigSetupComplete(true);
|
||||
}
|
||||
|
||||
|
|
|
@ -1,71 +0,0 @@
|
|||
/*
|
||||
* This file is part of Haveno.
|
||||
*
|
||||
* Haveno is free software: you can redistribute it and/or modify it
|
||||
* under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or (at
|
||||
* your option) any later version.
|
||||
*
|
||||
* Haveno is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
|
||||
* License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with Haveno. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package bisq.core.trade.protocol.tasks.taker;
|
||||
|
||||
import bisq.core.btc.model.XmrAddressEntry;
|
||||
import bisq.core.btc.wallet.TradeWalletService;
|
||||
import bisq.core.btc.wallet.XmrWalletService;
|
||||
import bisq.core.trade.Trade;
|
||||
import bisq.core.trade.protocol.tasks.TradeTask;
|
||||
|
||||
import bisq.common.taskrunner.TaskRunner;
|
||||
|
||||
import org.bitcoinj.core.Coin;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
|
||||
|
||||
import monero.wallet.model.MoneroTxWallet;
|
||||
|
||||
// TODO (woodser): rename this to TakerCreateFeeTx or rename TakerPublishFeeTx to TakerPublishReserveTradeTx for consistency
|
||||
@Slf4j
|
||||
public class TakerCreateFeeTx extends TradeTask {
|
||||
@SuppressWarnings({ "unused" })
|
||||
public TakerCreateFeeTx(TaskRunner taskHandler, Trade trade) {
|
||||
super(taskHandler, trade);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void run() {
|
||||
try {
|
||||
runInterceptHook();
|
||||
|
||||
XmrWalletService walletService = processModel.getProvider().getXmrWalletService();
|
||||
String id = processModel.getOffer().getId();
|
||||
XmrAddressEntry reservedForTradeAddressEntry = walletService.getOrCreateAddressEntry(id, XmrAddressEntry.Context.RESERVED_FOR_TRADE);
|
||||
TradeWalletService tradeWalletService = processModel.getTradeWalletService();
|
||||
String feeReceiver = "52FnB7ABUrKJzVQRpbMNrqDFWbcKLjFUq8Rgek7jZEuB6WE2ZggXaTf4FK6H8gQymvSrruHHrEuKhMN3qTMiBYzREKsmRKM"; // TODO (woodser): don't hardcode
|
||||
|
||||
// pay trade fee to reserve trade
|
||||
MoneroTxWallet tx = tradeWalletService.createXmrTradingFeeTx(
|
||||
reservedForTradeAddressEntry.getAddressString(),
|
||||
Coin.valueOf(processModel.getFundsNeededForTradeAsLong()),
|
||||
trade.getTakerFee(),
|
||||
trade.getTxFee(),
|
||||
feeReceiver,
|
||||
false);
|
||||
|
||||
trade.setTakerFeeTxId(tx.getHash());
|
||||
processModel.setTakeOfferFeeTx(tx);
|
||||
complete();
|
||||
} catch (Throwable t) {
|
||||
failed(t);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -17,7 +17,7 @@
|
|||
|
||||
package bisq.core.trade.txproof.xmr;
|
||||
|
||||
import bisq.core.btc.setup.WalletsSetup;
|
||||
import bisq.core.api.CoreMoneroConnectionsService;
|
||||
import bisq.core.filter.FilterManager;
|
||||
import bisq.core.locale.Res;
|
||||
import bisq.core.support.dispute.mediation.MediationManager;
|
||||
|
@ -76,7 +76,7 @@ public class XmrTxProofService implements AssetTxProofService {
|
|||
private final MediationManager mediationManager;
|
||||
private final RefundManager refundManager;
|
||||
private final P2PService p2PService;
|
||||
private final WalletsSetup walletsSetup;
|
||||
private final CoreMoneroConnectionsService connectionService;
|
||||
private final Socks5ProxyProvider socks5ProxyProvider;
|
||||
private final Map<String, XmrTxProofRequestsPerTrade> servicesByTradeId = new HashMap<>();
|
||||
private AutoConfirmSettings autoConfirmSettings;
|
||||
|
@ -101,7 +101,7 @@ public class XmrTxProofService implements AssetTxProofService {
|
|||
MediationManager mediationManager,
|
||||
RefundManager refundManager,
|
||||
P2PService p2PService,
|
||||
WalletsSetup walletsSetup,
|
||||
CoreMoneroConnectionsService connectionService,
|
||||
Socks5ProxyProvider socks5ProxyProvider) {
|
||||
this.filterManager = filterManager;
|
||||
this.preferences = preferences;
|
||||
|
@ -111,7 +111,7 @@ public class XmrTxProofService implements AssetTxProofService {
|
|||
this.mediationManager = mediationManager;
|
||||
this.refundManager = refundManager;
|
||||
this.p2PService = p2PService;
|
||||
this.walletsSetup = walletsSetup;
|
||||
this.connectionService = connectionService;
|
||||
this.socks5ProxyProvider = socks5ProxyProvider;
|
||||
}
|
||||
|
||||
|
@ -289,32 +289,32 @@ public class XmrTxProofService implements AssetTxProofService {
|
|||
|
||||
private BooleanProperty isXmrBlockDownloadComplete() {
|
||||
BooleanProperty result = new SimpleBooleanProperty();
|
||||
if (walletsSetup.isDownloadComplete()) {
|
||||
if (connectionService.isDownloadComplete()) {
|
||||
result.set(true);
|
||||
} else {
|
||||
xmrBlockListener = (observable, oldValue, newValue) -> {
|
||||
if (walletsSetup.isDownloadComplete()) {
|
||||
walletsSetup.downloadPercentageProperty().removeListener(xmrBlockListener);
|
||||
if (connectionService.isDownloadComplete()) {
|
||||
connectionService.downloadPercentageProperty().removeListener(xmrBlockListener);
|
||||
result.set(true);
|
||||
}
|
||||
};
|
||||
walletsSetup.downloadPercentageProperty().addListener(xmrBlockListener);
|
||||
connectionService.downloadPercentageProperty().addListener(xmrBlockListener);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private BooleanProperty hasSufficientXmrPeers() {
|
||||
BooleanProperty result = new SimpleBooleanProperty();
|
||||
if (walletsSetup.hasSufficientPeersForBroadcast()) {
|
||||
if (connectionService.hasSufficientPeersForBroadcast()) {
|
||||
result.set(true);
|
||||
} else {
|
||||
xmrPeersListener = (observable, oldValue, newValue) -> {
|
||||
if (walletsSetup.hasSufficientPeersForBroadcast()) {
|
||||
walletsSetup.numPeersProperty().removeListener(xmrPeersListener);
|
||||
if (connectionService.hasSufficientPeersForBroadcast()) {
|
||||
connectionService.numPeersProperty().removeListener(xmrPeersListener);
|
||||
result.set(true);
|
||||
}
|
||||
};
|
||||
walletsSetup.numPeersProperty().addListener(xmrPeersListener);
|
||||
connectionService.numPeersProperty().addListener(xmrPeersListener);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
|
|
@ -399,6 +399,7 @@ public class User implements PersistedDataHost {
|
|||
return userPayload.getPaymentAccounts();
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public ObservableSet<PaymentAccount> getPaymentAccountsAsObservable() {
|
||||
return paymentAccountsAsObservable;
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue