mirror of
https://github.com/haveno-dex/haveno.git
synced 2024-12-18 04:04:31 -05:00
Add API functions to start and stop local Monero node
This commit is contained in:
parent
00765d7b32
commit
9dfbb0d5a6
@ -172,7 +172,7 @@ public class KeyStorage {
|
|||||||
// Most of the time (probably of slightly less than 255/256, around 99.61%) a bad password
|
// Most of the time (probably of slightly less than 255/256, around 99.61%) a bad password
|
||||||
// will result in BadPaddingException before HMAC check.
|
// will result in BadPaddingException before HMAC check.
|
||||||
// See https://stackoverflow.com/questions/8049872/given-final-block-not-properly-padded
|
// See https://stackoverflow.com/questions/8049872/given-final-block-not-properly-padded
|
||||||
if (ce.getCause() instanceof BadPaddingException || ce.getMessage() == Encryption.HMAC_ERROR_MSG)
|
if (ce.getCause() instanceof BadPaddingException || Encryption.HMAC_ERROR_MSG.equals(ce.getMessage()))
|
||||||
throw new IncorrectPasswordException("Incorrect password");
|
throw new IncorrectPasswordException("Incorrect password");
|
||||||
else
|
else
|
||||||
throw ce;
|
throw ce;
|
||||||
|
@ -36,6 +36,8 @@ import bisq.core.support.messages.ChatMessage;
|
|||||||
import bisq.core.trade.Trade;
|
import bisq.core.trade.Trade;
|
||||||
import bisq.core.trade.statistics.TradeStatistics3;
|
import bisq.core.trade.statistics.TradeStatistics3;
|
||||||
import bisq.core.trade.statistics.TradeStatisticsManager;
|
import bisq.core.trade.statistics.TradeStatisticsManager;
|
||||||
|
import bisq.core.xmr.MoneroNodeSettings;
|
||||||
|
|
||||||
import bisq.common.app.Version;
|
import bisq.common.app.Version;
|
||||||
import bisq.common.config.Config;
|
import bisq.common.config.Config;
|
||||||
import bisq.common.crypto.IncorrectPasswordException;
|
import bisq.common.crypto.IncorrectPasswordException;
|
||||||
@ -53,6 +55,7 @@ import javax.inject.Singleton;
|
|||||||
|
|
||||||
import com.google.common.util.concurrent.FutureCallback;
|
import com.google.common.util.concurrent.FutureCallback;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
@ -94,6 +97,7 @@ public class CoreApi {
|
|||||||
private final TradeStatisticsManager tradeStatisticsManager;
|
private final TradeStatisticsManager tradeStatisticsManager;
|
||||||
private final CoreNotificationService notificationService;
|
private final CoreNotificationService notificationService;
|
||||||
private final CoreMoneroConnectionsService coreMoneroConnectionsService;
|
private final CoreMoneroConnectionsService coreMoneroConnectionsService;
|
||||||
|
private final CoreMoneroNodeService coreMoneroNodeService;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
public CoreApi(Config config,
|
public CoreApi(Config config,
|
||||||
@ -109,7 +113,8 @@ public class CoreApi {
|
|||||||
CoreWalletsService walletsService,
|
CoreWalletsService walletsService,
|
||||||
TradeStatisticsManager tradeStatisticsManager,
|
TradeStatisticsManager tradeStatisticsManager,
|
||||||
CoreNotificationService notificationService,
|
CoreNotificationService notificationService,
|
||||||
CoreMoneroConnectionsService coreMoneroConnectionsService) {
|
CoreMoneroConnectionsService coreMoneroConnectionsService,
|
||||||
|
CoreMoneroNodeService coreMoneroNodeService) {
|
||||||
this.config = config;
|
this.config = config;
|
||||||
this.appStartupState = appStartupState;
|
this.appStartupState = appStartupState;
|
||||||
this.coreAccountService = coreAccountService;
|
this.coreAccountService = coreAccountService;
|
||||||
@ -124,6 +129,7 @@ public class CoreApi {
|
|||||||
this.tradeStatisticsManager = tradeStatisticsManager;
|
this.tradeStatisticsManager = tradeStatisticsManager;
|
||||||
this.notificationService = notificationService;
|
this.notificationService = notificationService;
|
||||||
this.coreMoneroConnectionsService = coreMoneroConnectionsService;
|
this.coreMoneroConnectionsService = coreMoneroConnectionsService;
|
||||||
|
this.coreMoneroNodeService = coreMoneroNodeService;
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings("SameReturnValue")
|
@SuppressWarnings("SameReturnValue")
|
||||||
@ -235,6 +241,26 @@ public class CoreApi {
|
|||||||
coreMoneroConnectionsService.setAutoSwitch(autoSwitch);
|
coreMoneroConnectionsService.setAutoSwitch(autoSwitch);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
// Monero node
|
||||||
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
public boolean isMoneroNodeRunning() {
|
||||||
|
return coreMoneroNodeService.isMoneroNodeRunning();
|
||||||
|
}
|
||||||
|
|
||||||
|
public MoneroNodeSettings getMoneroNodeSettings() {
|
||||||
|
return coreMoneroNodeService.getMoneroNodeSettings();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void startMoneroNode(MoneroNodeSettings settings) throws IOException {
|
||||||
|
coreMoneroNodeService.startMoneroNode(settings);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void stopMoneroNode() {
|
||||||
|
coreMoneroNodeService.stopMoneroNode();
|
||||||
|
}
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
// Wallets
|
// Wallets
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
@ -18,6 +18,7 @@ import javafx.beans.property.SimpleLongProperty;
|
|||||||
import javafx.beans.property.SimpleObjectProperty;
|
import javafx.beans.property.SimpleObjectProperty;
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
import javax.inject.Singleton;
|
import javax.inject.Singleton;
|
||||||
|
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import monero.common.MoneroConnectionManager;
|
import monero.common.MoneroConnectionManager;
|
||||||
import monero.common.MoneroConnectionManagerListener;
|
import monero.common.MoneroConnectionManagerListener;
|
||||||
@ -36,12 +37,13 @@ public final class CoreMoneroConnectionsService {
|
|||||||
|
|
||||||
// TODO (woodser): support each network type, move to config, remove localhost authentication
|
// TODO (woodser): support each network type, move to config, remove localhost authentication
|
||||||
private static final List<MoneroRpcConnection> DEFAULT_CONNECTIONS = Arrays.asList(
|
private static final List<MoneroRpcConnection> DEFAULT_CONNECTIONS = Arrays.asList(
|
||||||
new MoneroRpcConnection("http://localhost:38081", "superuser", "abctesting123").setPriority(1), // localhost is first priority
|
new MoneroRpcConnection("http://127.0.0.1:38081", "superuser", "abctesting123").setPriority(1), // localhost is first priority, use loopback address to match url generated by local node service
|
||||||
new MoneroRpcConnection("http://haveno.exchange:38081", "", "").setPriority(2)
|
new MoneroRpcConnection("http://haveno.exchange:38081", "", "").setPriority(2)
|
||||||
);
|
);
|
||||||
|
|
||||||
private final Object lock = new Object();
|
private final Object lock = new Object();
|
||||||
private final CoreAccountService accountService;
|
private final CoreAccountService accountService;
|
||||||
|
private final CoreMoneroNodeService nodeService;
|
||||||
private final MoneroConnectionManager connectionManager;
|
private final MoneroConnectionManager connectionManager;
|
||||||
private final EncryptedConnectionList connectionList;
|
private final EncryptedConnectionList connectionList;
|
||||||
private final ObjectProperty<List<MoneroPeer>> peers = new SimpleObjectProperty<>();
|
private final ObjectProperty<List<MoneroPeer>> peers = new SimpleObjectProperty<>();
|
||||||
@ -55,9 +57,11 @@ public final class CoreMoneroConnectionsService {
|
|||||||
@Inject
|
@Inject
|
||||||
public CoreMoneroConnectionsService(WalletsSetup walletsSetup,
|
public CoreMoneroConnectionsService(WalletsSetup walletsSetup,
|
||||||
CoreAccountService accountService,
|
CoreAccountService accountService,
|
||||||
|
CoreMoneroNodeService nodeService,
|
||||||
MoneroConnectionManager connectionManager,
|
MoneroConnectionManager connectionManager,
|
||||||
EncryptedConnectionList connectionList) {
|
EncryptedConnectionList connectionList) {
|
||||||
this.accountService = accountService;
|
this.accountService = accountService;
|
||||||
|
this.nodeService = nodeService;
|
||||||
this.connectionManager = connectionManager;
|
this.connectionManager = connectionManager;
|
||||||
this.connectionList = connectionList;
|
this.connectionList = connectionList;
|
||||||
|
|
||||||
@ -265,7 +269,8 @@ public final class CoreMoneroConnectionsService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// restore last used connection
|
// restore last used connection
|
||||||
connectionList.getCurrentConnectionUri().ifPresentOrElse(connectionManager::setConnection, () -> {
|
var currentConnection = connectionList.getCurrentConnectionUri();
|
||||||
|
currentConnection.ifPresentOrElse(connectionManager::setConnection, () -> {
|
||||||
connectionManager.setConnection(DEFAULT_CONNECTIONS.get(0).getUri()); // default to localhost
|
connectionManager.setConnection(DEFAULT_CONNECTIONS.get(0).getUri()); // default to localhost
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -283,6 +288,32 @@ public final class CoreMoneroConnectionsService {
|
|||||||
// run once
|
// run once
|
||||||
if (!isInitialized) {
|
if (!isInitialized) {
|
||||||
|
|
||||||
|
// initialize local monero node
|
||||||
|
nodeService.addListener(new MoneroNodeServiceListener() {
|
||||||
|
@Override
|
||||||
|
public void onNodeStarted(MoneroDaemonRpc daemon) {
|
||||||
|
log.info(getClass() + ".onNodeStarted() called");
|
||||||
|
setConnection(daemon.getRpcConnection());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onNodeStopped() {
|
||||||
|
log.info(getClass() + ".onNodeStopped() called");
|
||||||
|
checkConnection();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// start local node if the last connection is local and not running
|
||||||
|
currentConnection.ifPresent(connection -> {
|
||||||
|
try {
|
||||||
|
if (nodeService.isMoneroNodeConnection(connection) && !nodeService.isMoneroNodeRunning()) {
|
||||||
|
nodeService.startMoneroNode();
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.warn("Unable to start local monero node: " + e.getMessage());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
// register connection change listener
|
// register connection change listener
|
||||||
connectionManager.addListener(this::onConnectionChanged);
|
connectionManager.addListener(this::onConnectionChanged);
|
||||||
|
|
||||||
|
168
core/src/main/java/bisq/core/api/CoreMoneroNodeService.java
Normal file
168
core/src/main/java/bisq/core/api/CoreMoneroNodeService.java
Normal file
@ -0,0 +1,168 @@
|
|||||||
|
/*
|
||||||
|
* 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 bisq.core.user.Preferences;
|
||||||
|
import bisq.core.xmr.MoneroNodeSettings;
|
||||||
|
|
||||||
|
import bisq.common.config.Config;
|
||||||
|
|
||||||
|
import javax.inject.Inject;
|
||||||
|
import javax.inject.Singleton;
|
||||||
|
|
||||||
|
import java.net.URI;
|
||||||
|
import java.net.URISyntaxException;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
|
import monero.common.MoneroRpcConnection;
|
||||||
|
import monero.daemon.MoneroDaemonRpc;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Manages a Monero node instance or connection to an instance.
|
||||||
|
*/
|
||||||
|
@Slf4j
|
||||||
|
@Singleton
|
||||||
|
public class CoreMoneroNodeService {
|
||||||
|
|
||||||
|
public static final String LOCAL_NODE_ADDRESS = "127.0.0.1"; // expected connection from local MoneroDaemonRpc
|
||||||
|
private static final String MONERO_NETWORK_TYPE = Config.baseCurrencyNetwork().getNetwork().toLowerCase();
|
||||||
|
private static final String MONEROD_PATH = System.getProperty("user.dir") + File.separator + ".localnet" + File.separator + "monerod";
|
||||||
|
private static final String MONEROD_DATADIR = System.getProperty("user.dir") + File.separator + ".localnet" + File.separator + MONERO_NETWORK_TYPE;
|
||||||
|
|
||||||
|
private final Preferences preferences;
|
||||||
|
private final List<MoneroNodeServiceListener> listeners = new ArrayList<>();
|
||||||
|
|
||||||
|
// required arguments
|
||||||
|
private static final List<String> MONEROD_ARGS = Arrays.asList(
|
||||||
|
MONEROD_PATH,
|
||||||
|
"--" + MONERO_NETWORK_TYPE,
|
||||||
|
"--no-igd",
|
||||||
|
"--hide-my-port",
|
||||||
|
"--rpc-login", "superuser:abctesting123" // TODO: remove authentication
|
||||||
|
);
|
||||||
|
|
||||||
|
// local monero node owned by this process
|
||||||
|
private MoneroDaemonRpc daemon;
|
||||||
|
|
||||||
|
// local monero node for detecting running node not owned by this process
|
||||||
|
private MoneroDaemonRpc defaultMoneroDaemon;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
public CoreMoneroNodeService(Preferences preferences) {
|
||||||
|
this.daemon = null;
|
||||||
|
this.preferences = preferences;
|
||||||
|
int rpcPort = 18081; // mainnet
|
||||||
|
if (Config.baseCurrencyNetwork().isTestnet()) {
|
||||||
|
rpcPort = 28081;
|
||||||
|
} else if (Config.baseCurrencyNetwork().isStagenet()) {
|
||||||
|
rpcPort = 38081;
|
||||||
|
}
|
||||||
|
// TODO: remove authentication
|
||||||
|
var defaultMoneroConnection = new MoneroRpcConnection("http://" + LOCAL_NODE_ADDRESS + ":" + rpcPort, "superuser", "abctesting123").setPriority(1); // localhost is first priority
|
||||||
|
defaultMoneroDaemon = new MoneroDaemonRpc(defaultMoneroConnection);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void addListener(MoneroNodeServiceListener listener) {
|
||||||
|
listeners.add(listener);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean removeListener(MoneroNodeServiceListener listener) {
|
||||||
|
return listeners.remove(listener);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns whether a connection string URI is a local monero node.
|
||||||
|
*/
|
||||||
|
public boolean isMoneroNodeConnection(String connection) throws URISyntaxException {
|
||||||
|
var uri = new URI(connection);
|
||||||
|
return CoreMoneroNodeService.LOCAL_NODE_ADDRESS.equals(uri.getHost());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns whether the local monero node is running or local daemon connection is running
|
||||||
|
*/
|
||||||
|
public boolean isMoneroNodeRunning() {
|
||||||
|
return daemon != null || defaultMoneroDaemon.isConnected();
|
||||||
|
}
|
||||||
|
|
||||||
|
public MoneroNodeSettings getMoneroNodeSettings() {
|
||||||
|
return preferences.getMoneroNodeSettings();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Starts a local monero node from settings.
|
||||||
|
*/
|
||||||
|
public void startMoneroNode() throws IOException {
|
||||||
|
var settings = preferences.getMoneroNodeSettings();
|
||||||
|
this.startMoneroNode(settings);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Starts a local monero node. Throws MoneroError if the node cannot be started.
|
||||||
|
* Persists the settings to preferences if the node started successfully.
|
||||||
|
*/
|
||||||
|
public void startMoneroNode(MoneroNodeSettings settings) throws IOException {
|
||||||
|
if (isMoneroNodeRunning()) throw new IllegalStateException("Monero node already running");
|
||||||
|
|
||||||
|
log.info("Starting local Monero node: " + settings);
|
||||||
|
|
||||||
|
var args = new ArrayList<>(MONEROD_ARGS);
|
||||||
|
|
||||||
|
var dataDir = settings.getBlockchainPath();
|
||||||
|
if (dataDir == null || dataDir.isEmpty()) {
|
||||||
|
dataDir = MONEROD_DATADIR;
|
||||||
|
}
|
||||||
|
args.add("--data-dir=" + dataDir);
|
||||||
|
|
||||||
|
var bootstrapUrl = settings.getBootstrapUrl();
|
||||||
|
if (bootstrapUrl != null && !bootstrapUrl.isEmpty()) {
|
||||||
|
args.add("--bootstrap-daemon-address=" + bootstrapUrl);
|
||||||
|
}
|
||||||
|
|
||||||
|
var flags = settings.getStartupFlags();
|
||||||
|
if (flags != null) {
|
||||||
|
args.addAll(flags);
|
||||||
|
}
|
||||||
|
|
||||||
|
daemon = new MoneroDaemonRpc(args);
|
||||||
|
preferences.setMoneroNodeSettings(settings);
|
||||||
|
for (var listener : listeners) listener.onNodeStarted(daemon);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stops the current local monero node if owned by this process.
|
||||||
|
* Does not remove the last MoneroNodeSettings.
|
||||||
|
*/
|
||||||
|
public void stopMoneroNode() {
|
||||||
|
if (!isMoneroNodeRunning()) throw new IllegalStateException("Monero node is not running");
|
||||||
|
if (daemon != null) {
|
||||||
|
daemon.stopProcess();
|
||||||
|
daemon = null;
|
||||||
|
for (var listener : listeners) listener.onNodeStopped();
|
||||||
|
} else {
|
||||||
|
defaultMoneroDaemon.stopProcess(); // throws MoneroError
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,24 @@
|
|||||||
|
/*
|
||||||
|
* 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 monero.daemon.MoneroDaemonRpc;
|
||||||
|
|
||||||
|
public class MoneroNodeServiceListener {
|
||||||
|
public void onNodeStarted(MoneroDaemonRpc daemon) {}
|
||||||
|
public void onNodeStopped() {}
|
||||||
|
}
|
@ -30,6 +30,7 @@ import bisq.core.locale.TradeCurrency;
|
|||||||
import bisq.core.payment.PaymentAccount;
|
import bisq.core.payment.PaymentAccount;
|
||||||
import bisq.core.payment.PaymentAccountUtil;
|
import bisq.core.payment.PaymentAccountUtil;
|
||||||
import bisq.core.provider.fee.FeeService;
|
import bisq.core.provider.fee.FeeService;
|
||||||
|
import bisq.core.xmr.MoneroNodeSettings;
|
||||||
|
|
||||||
import bisq.network.p2p.network.BridgeAddressProvider;
|
import bisq.network.p2p.network.BridgeAddressProvider;
|
||||||
|
|
||||||
@ -166,7 +167,6 @@ public final class Preferences implements PersistedDataHost, BridgeAddressProvid
|
|||||||
@Getter
|
@Getter
|
||||||
private final BooleanProperty useStandbyModeProperty = new SimpleBooleanProperty(prefPayload.isUseStandbyMode());
|
private final BooleanProperty useStandbyModeProperty = new SimpleBooleanProperty(prefPayload.isUseStandbyMode());
|
||||||
|
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
// Constructor
|
// Constructor
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
@ -708,6 +708,10 @@ public final class Preferences implements PersistedDataHost, BridgeAddressProvid
|
|||||||
requestPersistence();
|
requestPersistence();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setMoneroNodeSettings(MoneroNodeSettings settings) {
|
||||||
|
prefPayload.setMoneroNodeSettings(settings);
|
||||||
|
requestPersistence();
|
||||||
|
}
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
// Getter
|
// Getter
|
||||||
@ -957,5 +961,7 @@ public final class Preferences implements PersistedDataHost, BridgeAddressProvid
|
|||||||
void setDenyApiTaker(boolean value);
|
void setDenyApiTaker(boolean value);
|
||||||
|
|
||||||
void setNotifyOnPreRelease(boolean value);
|
void setNotifyOnPreRelease(boolean value);
|
||||||
|
|
||||||
|
void setMoneroNodeSettings(MoneroNodeSettings settings);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -23,6 +23,7 @@ import bisq.core.locale.FiatCurrency;
|
|||||||
import bisq.core.locale.TradeCurrency;
|
import bisq.core.locale.TradeCurrency;
|
||||||
import bisq.core.payment.PaymentAccount;
|
import bisq.core.payment.PaymentAccount;
|
||||||
import bisq.core.proto.CoreProtoResolver;
|
import bisq.core.proto.CoreProtoResolver;
|
||||||
|
import bisq.core.xmr.MoneroNodeSettings;
|
||||||
|
|
||||||
import bisq.common.proto.ProtoUtil;
|
import bisq.common.proto.ProtoUtil;
|
||||||
import bisq.common.proto.persistable.PersistableEnvelope;
|
import bisq.common.proto.persistable.PersistableEnvelope;
|
||||||
@ -130,6 +131,8 @@ public final class PreferencesPayload implements PersistableEnvelope {
|
|||||||
private boolean denyApiTaker;
|
private boolean denyApiTaker;
|
||||||
private boolean notifyOnPreRelease;
|
private boolean notifyOnPreRelease;
|
||||||
|
|
||||||
|
private MoneroNodeSettings moneroNodeSettings;
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
// Constructor
|
// Constructor
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
@ -201,8 +204,7 @@ public final class PreferencesPayload implements PersistableEnvelope {
|
|||||||
Optional.ofNullable(tradeChartsScreenCurrencyCode).ifPresent(builder::setTradeChartsScreenCurrencyCode);
|
Optional.ofNullable(tradeChartsScreenCurrencyCode).ifPresent(builder::setTradeChartsScreenCurrencyCode);
|
||||||
Optional.ofNullable(buyScreenCurrencyCode).ifPresent(builder::setBuyScreenCurrencyCode);
|
Optional.ofNullable(buyScreenCurrencyCode).ifPresent(builder::setBuyScreenCurrencyCode);
|
||||||
Optional.ofNullable(sellScreenCurrencyCode).ifPresent(builder::setSellScreenCurrencyCode);
|
Optional.ofNullable(sellScreenCurrencyCode).ifPresent(builder::setSellScreenCurrencyCode);
|
||||||
Optional.ofNullable(selectedPaymentAccountForCreateOffer).ifPresent(
|
Optional.ofNullable(selectedPaymentAccountForCreateOffer).ifPresent(account -> builder.setSelectedPaymentAccountForCreateOffer(account.toProtoMessage()));
|
||||||
account -> builder.setSelectedPaymentAccountForCreateOffer(selectedPaymentAccountForCreateOffer.toProtoMessage()));
|
|
||||||
Optional.ofNullable(bridgeAddresses).ifPresent(builder::addAllBridgeAddresses);
|
Optional.ofNullable(bridgeAddresses).ifPresent(builder::addAllBridgeAddresses);
|
||||||
Optional.ofNullable(customBridges).ifPresent(builder::setCustomBridges);
|
Optional.ofNullable(customBridges).ifPresent(builder::setCustomBridges);
|
||||||
Optional.ofNullable(referralId).ifPresent(builder::setReferralId);
|
Optional.ofNullable(referralId).ifPresent(builder::setReferralId);
|
||||||
@ -210,7 +212,7 @@ public final class PreferencesPayload implements PersistableEnvelope {
|
|||||||
Optional.ofNullable(rpcUser).ifPresent(builder::setRpcUser);
|
Optional.ofNullable(rpcUser).ifPresent(builder::setRpcUser);
|
||||||
Optional.ofNullable(rpcPw).ifPresent(builder::setRpcPw);
|
Optional.ofNullable(rpcPw).ifPresent(builder::setRpcPw);
|
||||||
Optional.ofNullable(takeOfferSelectedPaymentAccountId).ifPresent(builder::setTakeOfferSelectedPaymentAccountId);
|
Optional.ofNullable(takeOfferSelectedPaymentAccountId).ifPresent(builder::setTakeOfferSelectedPaymentAccountId);
|
||||||
|
Optional.ofNullable(moneroNodeSettings).ifPresent(settings -> builder.setMoneroNodeSettings(settings.toProtoMessage()));
|
||||||
return protobuf.PersistableEnvelope.newBuilder().setPreferencesPayload(builder).build();
|
return protobuf.PersistableEnvelope.newBuilder().setPreferencesPayload(builder).build();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -286,7 +288,8 @@ public final class PreferencesPayload implements PersistableEnvelope {
|
|||||||
proto.getHideNonAccountPaymentMethods(),
|
proto.getHideNonAccountPaymentMethods(),
|
||||||
proto.getShowOffersMatchingMyAccounts(),
|
proto.getShowOffersMatchingMyAccounts(),
|
||||||
proto.getDenyApiTaker(),
|
proto.getDenyApiTaker(),
|
||||||
proto.getNotifyOnPreRelease()
|
proto.getNotifyOnPreRelease(),
|
||||||
|
MoneroNodeSettings.fromProto(proto.getMoneroNodeSettings())
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
50
core/src/main/java/bisq/core/xmr/MoneroNodeSettings.java
Normal file
50
core/src/main/java/bisq/core/xmr/MoneroNodeSettings.java
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
/*
|
||||||
|
* 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.xmr;
|
||||||
|
|
||||||
|
import bisq.common.proto.persistable.PersistableEnvelope;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
|
@Slf4j
|
||||||
|
@Data
|
||||||
|
@AllArgsConstructor
|
||||||
|
public class MoneroNodeSettings implements PersistableEnvelope {
|
||||||
|
|
||||||
|
String blockchainPath;
|
||||||
|
String bootstrapUrl;
|
||||||
|
List<String> startupFlags;
|
||||||
|
|
||||||
|
public static MoneroNodeSettings fromProto(protobuf.MoneroNodeSettings proto) {
|
||||||
|
return new MoneroNodeSettings(
|
||||||
|
proto.getBlockchainPath(),
|
||||||
|
proto.getBootstrapUrl(),
|
||||||
|
proto.getStartupFlagsList());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public protobuf.MoneroNodeSettings toProtoMessage() {
|
||||||
|
return protobuf.MoneroNodeSettings.newBuilder()
|
||||||
|
.setBlockchainPath(blockchainPath)
|
||||||
|
.setBootstrapUrl(bootstrapUrl)
|
||||||
|
.addAllStartupFlags(startupFlags).build();
|
||||||
|
}
|
||||||
|
}
|
156
daemon/src/main/java/bisq/daemon/grpc/GrpcMoneroNodeService.java
Normal file
156
daemon/src/main/java/bisq/daemon/grpc/GrpcMoneroNodeService.java
Normal file
@ -0,0 +1,156 @@
|
|||||||
|
/*
|
||||||
|
* 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.daemon.grpc;
|
||||||
|
|
||||||
|
import bisq.core.api.CoreApi;
|
||||||
|
import bisq.core.xmr.MoneroNodeSettings;
|
||||||
|
|
||||||
|
import bisq.proto.grpc.GetMoneroNodeSettingsReply;
|
||||||
|
import bisq.proto.grpc.GetMoneroNodeSettingsRequest;
|
||||||
|
import bisq.proto.grpc.IsMoneroNodeRunningReply;
|
||||||
|
import bisq.proto.grpc.IsMoneroNodeRunningRequest;
|
||||||
|
import bisq.proto.grpc.MoneroNodeGrpc.MoneroNodeImplBase;
|
||||||
|
import bisq.proto.grpc.StartMoneroNodeReply;
|
||||||
|
import bisq.proto.grpc.StartMoneroNodeRequest;
|
||||||
|
import bisq.proto.grpc.StopMoneroNodeReply;
|
||||||
|
import bisq.proto.grpc.StopMoneroNodeRequest;
|
||||||
|
|
||||||
|
import io.grpc.ServerInterceptor;
|
||||||
|
import io.grpc.stub.StreamObserver;
|
||||||
|
|
||||||
|
import javax.inject.Inject;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
|
import static bisq.daemon.grpc.interceptor.GrpcServiceRateMeteringConfig.getCustomRateMeteringInterceptor;
|
||||||
|
import static bisq.proto.grpc.MoneroNodeGrpc.getStartMoneroNodeMethod;
|
||||||
|
import static bisq.proto.grpc.MoneroNodeGrpc.getStopMoneroNodeMethod;
|
||||||
|
import static bisq.proto.grpc.MoneroNodeGrpc.getIsMoneroNodeRunningMethod;
|
||||||
|
import static bisq.proto.grpc.MoneroNodeGrpc.getGetMoneroNodeSettingsMethod;
|
||||||
|
import static java.util.concurrent.TimeUnit.SECONDS;
|
||||||
|
|
||||||
|
import bisq.daemon.grpc.interceptor.CallRateMeteringInterceptor;
|
||||||
|
import bisq.daemon.grpc.interceptor.GrpcCallRateMeter;
|
||||||
|
import monero.common.MoneroError;
|
||||||
|
|
||||||
|
@Slf4j
|
||||||
|
public class GrpcMoneroNodeService extends MoneroNodeImplBase {
|
||||||
|
|
||||||
|
private final CoreApi coreApi;
|
||||||
|
private final GrpcExceptionHandler exceptionHandler;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
public GrpcMoneroNodeService(CoreApi coreApi, GrpcExceptionHandler exceptionHandler) {
|
||||||
|
this.coreApi = coreApi;
|
||||||
|
this.exceptionHandler = exceptionHandler;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void isMoneroNodeRunning(IsMoneroNodeRunningRequest request,
|
||||||
|
StreamObserver<IsMoneroNodeRunningReply> responseObserver) {
|
||||||
|
try {
|
||||||
|
var reply = IsMoneroNodeRunningReply.newBuilder()
|
||||||
|
.setIsRunning(coreApi.isMoneroNodeRunning())
|
||||||
|
.build();
|
||||||
|
responseObserver.onNext(reply);
|
||||||
|
responseObserver.onCompleted();
|
||||||
|
} catch (Throwable cause) {
|
||||||
|
exceptionHandler.handleException(log, cause, responseObserver);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void getMoneroNodeSettings(GetMoneroNodeSettingsRequest request,
|
||||||
|
StreamObserver<GetMoneroNodeSettingsReply> responseObserver) {
|
||||||
|
try {
|
||||||
|
var settings = coreApi.getMoneroNodeSettings();
|
||||||
|
var builder = GetMoneroNodeSettingsReply.newBuilder();
|
||||||
|
if (settings != null) {
|
||||||
|
builder.setSettings(settings.toProtoMessage());
|
||||||
|
}
|
||||||
|
var reply = builder.build();
|
||||||
|
responseObserver.onNext(reply);
|
||||||
|
responseObserver.onCompleted();
|
||||||
|
} catch (Throwable cause) {
|
||||||
|
exceptionHandler.handleException(log, cause, responseObserver);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void startMoneroNode(StartMoneroNodeRequest request,
|
||||||
|
StreamObserver<StartMoneroNodeReply> responseObserver) {
|
||||||
|
try {
|
||||||
|
var settings = request.getSettings();
|
||||||
|
coreApi.startMoneroNode(MoneroNodeSettings.fromProto(settings));
|
||||||
|
var reply = StartMoneroNodeReply.newBuilder().build();
|
||||||
|
responseObserver.onNext(reply);
|
||||||
|
responseObserver.onCompleted();
|
||||||
|
} catch (MoneroError me) {
|
||||||
|
handleMoneroError(me, responseObserver);
|
||||||
|
} catch (Throwable cause) {
|
||||||
|
exceptionHandler.handleException(log, cause, responseObserver);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void stopMoneroNode(StopMoneroNodeRequest request,
|
||||||
|
StreamObserver<StopMoneroNodeReply> responseObserver) {
|
||||||
|
try {
|
||||||
|
coreApi.stopMoneroNode();
|
||||||
|
var reply = StopMoneroNodeReply.newBuilder().build();
|
||||||
|
responseObserver.onNext(reply);
|
||||||
|
responseObserver.onCompleted();
|
||||||
|
} catch (MoneroError me) {
|
||||||
|
handleMoneroError(me, responseObserver);
|
||||||
|
} catch (Throwable cause) {
|
||||||
|
exceptionHandler.handleException(log, cause, responseObserver);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void handleMoneroError(MoneroError me, StreamObserver<?> responseObserver) {
|
||||||
|
// MoneroError is caused by the node startup failing, don't treat as unknown server error
|
||||||
|
// by wrapping with a handled exception type.
|
||||||
|
var headerLengthLimit = 8192; // MoneroErrors may print the entire monerod help text which causes a header overflow in grpc
|
||||||
|
if (me.getMessage().length() > headerLengthLimit) {
|
||||||
|
exceptionHandler.handleException(log, new IllegalStateException(me.getMessage().substring(0, headerLengthLimit - 1)), responseObserver);
|
||||||
|
} else {
|
||||||
|
exceptionHandler.handleException(log, new IllegalStateException(me), responseObserver);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
final ServerInterceptor[] interceptors() {
|
||||||
|
Optional<ServerInterceptor> rateMeteringInterceptor = rateMeteringInterceptor();
|
||||||
|
return rateMeteringInterceptor.map(serverInterceptor ->
|
||||||
|
new ServerInterceptor[]{serverInterceptor}).orElseGet(() -> new ServerInterceptor[0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Optional<ServerInterceptor> rateMeteringInterceptor() {
|
||||||
|
return getCustomRateMeteringInterceptor(coreApi.getConfig().appDataDir, this.getClass())
|
||||||
|
.or(() -> Optional.of(CallRateMeteringInterceptor.valueOf(
|
||||||
|
new HashMap<>() {{
|
||||||
|
int allowedCallsPerTimeWindow = 10;
|
||||||
|
put(getIsMoneroNodeRunningMethod().getFullMethodName(), new GrpcCallRateMeter(allowedCallsPerTimeWindow, SECONDS));
|
||||||
|
put(getGetMoneroNodeSettingsMethod().getFullMethodName(), new GrpcCallRateMeter(allowedCallsPerTimeWindow, SECONDS));
|
||||||
|
put(getStartMoneroNodeMethod().getFullMethodName(), new GrpcCallRateMeter(allowedCallsPerTimeWindow, SECONDS));
|
||||||
|
put(getStopMoneroNodeMethod().getFullMethodName(), new GrpcCallRateMeter(allowedCallsPerTimeWindow, SECONDS));
|
||||||
|
}}
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
}
|
@ -62,7 +62,8 @@ public class GrpcServer {
|
|||||||
GrpcTradesService tradesService,
|
GrpcTradesService tradesService,
|
||||||
GrpcWalletsService walletsService,
|
GrpcWalletsService walletsService,
|
||||||
GrpcNotificationsService notificationsService,
|
GrpcNotificationsService notificationsService,
|
||||||
GrpcMoneroConnectionsService moneroConnectionsService) {
|
GrpcMoneroConnectionsService moneroConnectionsService,
|
||||||
|
GrpcMoneroNodeService moneroNodeService) {
|
||||||
this.server = ServerBuilder.forPort(config.apiPort)
|
this.server = ServerBuilder.forPort(config.apiPort)
|
||||||
.executor(UserThread.getExecutor())
|
.executor(UserThread.getExecutor())
|
||||||
.addService(interceptForward(accountService, accountService.interceptors()))
|
.addService(interceptForward(accountService, accountService.interceptors()))
|
||||||
@ -79,6 +80,7 @@ public class GrpcServer {
|
|||||||
.addService(interceptForward(walletsService, walletsService.interceptors()))
|
.addService(interceptForward(walletsService, walletsService.interceptors()))
|
||||||
.addService(interceptForward(notificationsService, notificationsService.interceptors()))
|
.addService(interceptForward(notificationsService, notificationsService.interceptors()))
|
||||||
.addService(interceptForward(moneroConnectionsService, moneroConnectionsService.interceptors()))
|
.addService(interceptForward(moneroConnectionsService, moneroConnectionsService.interceptors()))
|
||||||
|
.addService(interceptForward(moneroNodeService, moneroNodeService.interceptors()))
|
||||||
.intercept(passwordAuthInterceptor)
|
.intercept(passwordAuthInterceptor)
|
||||||
.build();
|
.build();
|
||||||
coreContext.setApiUser(true);
|
coreContext.setApiUser(true);
|
||||||
|
@ -382,6 +382,48 @@ message SetAutoSwitchRequest {
|
|||||||
|
|
||||||
message SetAutoSwitchReply {}
|
message SetAutoSwitchReply {}
|
||||||
|
|
||||||
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
// MoneroNode
|
||||||
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
service MoneroNode {
|
||||||
|
rpc IsMoneroNodeRunning (IsMoneroNodeRunningRequest) returns (IsMoneroNodeRunningReply) {
|
||||||
|
}
|
||||||
|
rpc GetMoneroNodeSettings (GetMoneroNodeSettingsRequest) returns (GetMoneroNodeSettingsReply) {
|
||||||
|
}
|
||||||
|
rpc StartMoneroNode (StartMoneroNodeRequest) returns (StartMoneroNodeReply) {
|
||||||
|
}
|
||||||
|
rpc StopMoneroNode (StopMoneroNodeRequest) returns (StopMoneroNodeReply) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
message IsMoneroNodeRunningRequest {
|
||||||
|
}
|
||||||
|
|
||||||
|
message IsMoneroNodeRunningReply {
|
||||||
|
bool is_running = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
message GetMoneroNodeSettingsRequest {
|
||||||
|
}
|
||||||
|
|
||||||
|
message GetMoneroNodeSettingsReply {
|
||||||
|
MoneroNodeSettings settings = 1; // pb.proto
|
||||||
|
}
|
||||||
|
|
||||||
|
message StartMoneroNodeRequest {
|
||||||
|
MoneroNodeSettings settings = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
message StartMoneroNodeReply {
|
||||||
|
}
|
||||||
|
|
||||||
|
message StopMoneroNodeRequest {
|
||||||
|
}
|
||||||
|
|
||||||
|
message StopMoneroNodeReply {
|
||||||
|
}
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
// Offers
|
// Offers
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
@ -1814,6 +1814,7 @@ message PreferencesPayload {
|
|||||||
bool show_offers_matching_my_accounts = 55;
|
bool show_offers_matching_my_accounts = 55;
|
||||||
bool deny_api_taker = 56;
|
bool deny_api_taker = 56;
|
||||||
bool notify_on_pre_release = 57;
|
bool notify_on_pre_release = 57;
|
||||||
|
MoneroNodeSettings monero_node_settings = 58;
|
||||||
}
|
}
|
||||||
|
|
||||||
message AutoConfirmSettings {
|
message AutoConfirmSettings {
|
||||||
@ -1824,6 +1825,12 @@ message AutoConfirmSettings {
|
|||||||
string currency_code = 5;
|
string currency_code = 5;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
message MoneroNodeSettings {
|
||||||
|
string blockchain_path = 1;
|
||||||
|
string bootstrap_url = 2;
|
||||||
|
repeated string startup_flags = 3;
|
||||||
|
}
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
// UserPayload
|
// UserPayload
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
Loading…
Reference in New Issue
Block a user