Add API functions to initialize Haveno account (#216)

Co-authored-by: woodser@protonmail.com
This commit is contained in:
duriancrepe 2022-02-09 01:39:57 -08:00 committed by GitHub
parent dc4692d97a
commit e3b9a9962b
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
81 changed files with 2755 additions and 1660 deletions

View 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) {}
}

View file

@ -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);
}
}
}

View file

@ -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);
}
}

View file

@ -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());
}
}

View file

@ -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)

View file

@ -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 {

View file

@ -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(),

View file

@ -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");
}
});

View file

@ -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);
}
}

View file

@ -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)

View file

@ -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);
}
}

View file

@ -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
///////////////////////////////////////////////////////////////////////////////////////////

View file

@ -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 {

View file

@ -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() &&

View file

@ -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;

View file

@ -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) {

View file

@ -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));
}
}

View file

@ -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());
}
}

View file

@ -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{" +

View file

@ -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));
}

View file

@ -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;

View file

@ -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) {

View file

@ -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.
*

View file

@ -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

View file

@ -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;
}
});
}
}

View file

@ -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:

View file

@ -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,

View file

@ -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);
}

View file

@ -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);

View file

@ -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();
}

View file

@ -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();
});

View file

@ -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

View file

@ -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);
}

View file

@ -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);
}

View file

@ -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 {}",

View file

@ -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) {

View file

@ -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();
}
}

View file

@ -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);
}

View file

@ -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);
}
}
}

View file

@ -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;
}

View file

@ -399,6 +399,7 @@ public class User implements PersistedDataHost {
return userPayload.getPaymentAccounts();
}
@Nullable
public ObservableSet<PaymentAccount> getPaymentAccountsAsObservable() {
return paymentAccountsAsObservable;
}