mirror of
https://github.com/haveno-dex/haveno.git
synced 2025-04-23 08:59:24 -04:00
Compare commits
21 Commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
de5250e89a | ||
![]() |
923b3ad73b | ||
![]() |
9a14d5552e | ||
![]() |
a3d3f51f02 | ||
![]() |
77429472f4 | ||
![]() |
38615edf86 | ||
![]() |
cf9a37f295 | ||
![]() |
c7a3a9740f | ||
![]() |
13e13d945d | ||
![]() |
bfef0f9492 | ||
![]() |
39909e7936 | ||
![]() |
695f2b8dd3 | ||
![]() |
8f778be4d9 | ||
![]() |
821ef16d8f | ||
![]() |
58590d60df | ||
![]() |
8eccbcce43 | ||
![]() |
bbfc5d5fed | ||
![]() |
22db354cb2 | ||
![]() |
60ceff6695 | ||
![]() |
c87b8a5b45 | ||
![]() |
bf055556f1 |
25
Makefile
25
Makefile
@ -485,6 +485,31 @@ arbitrator-desktop-mainnet:
|
|||||||
--xmrNode=http://127.0.0.1:18081 \
|
--xmrNode=http://127.0.0.1:18081 \
|
||||||
--useNativeXmrWallet=false \
|
--useNativeXmrWallet=false \
|
||||||
|
|
||||||
|
arbitrator2-daemon-mainnet:
|
||||||
|
./haveno-daemon$(APP_EXT) \
|
||||||
|
--baseCurrencyNetwork=XMR_MAINNET \
|
||||||
|
--useLocalhostForP2P=false \
|
||||||
|
--useDevPrivilegeKeys=false \
|
||||||
|
--nodePort=9999 \
|
||||||
|
--appName=haveno-XMR_MAINNET_arbitrator2 \
|
||||||
|
--apiPassword=apitest \
|
||||||
|
--apiPort=1205 \
|
||||||
|
--passwordRequired=false \
|
||||||
|
--xmrNode=http://127.0.0.1:18081 \
|
||||||
|
--useNativeXmrWallet=false \
|
||||||
|
|
||||||
|
arbitrator2-desktop-mainnet:
|
||||||
|
./haveno-desktop$(APP_EXT) \
|
||||||
|
--baseCurrencyNetwork=XMR_MAINNET \
|
||||||
|
--useLocalhostForP2P=false \
|
||||||
|
--useDevPrivilegeKeys=false \
|
||||||
|
--nodePort=9999 \
|
||||||
|
--appName=haveno-XMR_MAINNET_arbitrator2 \
|
||||||
|
--apiPassword=apitest \
|
||||||
|
--apiPort=1205 \
|
||||||
|
--xmrNode=http://127.0.0.1:18081 \
|
||||||
|
--useNativeXmrWallet=false \
|
||||||
|
|
||||||
haveno-daemon-mainnet:
|
haveno-daemon-mainnet:
|
||||||
./haveno-daemon$(APP_EXT) \
|
./haveno-daemon$(APP_EXT) \
|
||||||
--baseCurrencyNetwork=XMR_MAINNET \
|
--baseCurrencyNetwork=XMR_MAINNET \
|
||||||
|
@ -610,7 +610,7 @@ configure(project(':desktop')) {
|
|||||||
apply plugin: 'com.github.johnrengelman.shadow'
|
apply plugin: 'com.github.johnrengelman.shadow'
|
||||||
apply from: 'package/package.gradle'
|
apply from: 'package/package.gradle'
|
||||||
|
|
||||||
version = '1.0.19-SNAPSHOT'
|
version = '1.1.0-SNAPSHOT'
|
||||||
|
|
||||||
jar.manifest.attributes(
|
jar.manifest.attributes(
|
||||||
"Implementation-Title": project.name,
|
"Implementation-Title": project.name,
|
||||||
|
@ -28,7 +28,7 @@ import static com.google.common.base.Preconditions.checkArgument;
|
|||||||
public class Version {
|
public class Version {
|
||||||
// The application versions
|
// The application versions
|
||||||
// We use semantic versioning with major, minor and patch
|
// We use semantic versioning with major, minor and patch
|
||||||
public static final String VERSION = "1.0.19";
|
public static final String VERSION = "1.1.0";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Holds a list of the tagged resource files for optimizing the getData requests.
|
* Holds a list of the tagged resource files for optimizing the getData requests.
|
||||||
|
@ -335,12 +335,13 @@ public class SignedWitnessService {
|
|||||||
String message = Utilities.encodeToHex(signedWitness.getAccountAgeWitnessHash());
|
String message = Utilities.encodeToHex(signedWitness.getAccountAgeWitnessHash());
|
||||||
String signatureBase64 = new String(signedWitness.getSignature(), Charsets.UTF_8);
|
String signatureBase64 = new String(signedWitness.getSignature(), Charsets.UTF_8);
|
||||||
ECKey key = ECKey.fromPublicOnly(signedWitness.getSignerPubKey());
|
ECKey key = ECKey.fromPublicOnly(signedWitness.getSignerPubKey());
|
||||||
if (arbitratorManager.isPublicKeyInList(Utilities.encodeToHex(key.getPubKey()))) {
|
String pubKeyHex = Utilities.encodeToHex(key.getPubKey());
|
||||||
|
if (arbitratorManager.isPublicKeyInList(pubKeyHex)) {
|
||||||
key.verifyMessage(message, signatureBase64);
|
key.verifyMessage(message, signatureBase64);
|
||||||
verifySignatureWithECKeyResultCache.put(hash, true);
|
verifySignatureWithECKeyResultCache.put(hash, true);
|
||||||
return true;
|
return true;
|
||||||
} else {
|
} else {
|
||||||
log.warn("Provided EC key is not in list of valid arbitrators.");
|
log.warn("Provided EC key is not in list of valid arbitrators: " + pubKeyHex);
|
||||||
verifySignatureWithECKeyResultCache.put(hash, false);
|
verifySignatureWithECKeyResultCache.put(hash, false);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
@ -75,9 +75,10 @@ public final class XmrConnectionService {
|
|||||||
private static final long KEY_IMAGE_REFRESH_PERIOD_MS_LOCAL = 20000; // 20 seconds
|
private static final long KEY_IMAGE_REFRESH_PERIOD_MS_LOCAL = 20000; // 20 seconds
|
||||||
private static final long KEY_IMAGE_REFRESH_PERIOD_MS_REMOTE = 300000; // 5 minutes
|
private static final long KEY_IMAGE_REFRESH_PERIOD_MS_REMOTE = 300000; // 5 minutes
|
||||||
|
|
||||||
public enum XmrConnectionError {
|
public enum XmrConnectionFallbackType {
|
||||||
LOCAL,
|
LOCAL,
|
||||||
CUSTOM
|
CUSTOM,
|
||||||
|
PROVIDED
|
||||||
}
|
}
|
||||||
|
|
||||||
private final Object lock = new Object();
|
private final Object lock = new Object();
|
||||||
@ -97,7 +98,7 @@ public final class XmrConnectionService {
|
|||||||
private final LongProperty chainHeight = new SimpleLongProperty(0);
|
private final LongProperty chainHeight = new SimpleLongProperty(0);
|
||||||
private final DownloadListener downloadListener = new DownloadListener();
|
private final DownloadListener downloadListener = new DownloadListener();
|
||||||
@Getter
|
@Getter
|
||||||
private final ObjectProperty<XmrConnectionError> connectionServiceError = new SimpleObjectProperty<>();
|
private final ObjectProperty<XmrConnectionFallbackType> connectionServiceFallbackType = new SimpleObjectProperty<>();
|
||||||
@Getter
|
@Getter
|
||||||
private final StringProperty connectionServiceErrorMsg = new SimpleStringProperty();
|
private final StringProperty connectionServiceErrorMsg = new SimpleStringProperty();
|
||||||
private final LongProperty numUpdates = new SimpleLongProperty(0);
|
private final LongProperty numUpdates = new SimpleLongProperty(0);
|
||||||
@ -129,6 +130,7 @@ public final class XmrConnectionService {
|
|||||||
private Set<MoneroRpcConnection> excludedConnections = new HashSet<>();
|
private Set<MoneroRpcConnection> excludedConnections = new HashSet<>();
|
||||||
private static final long FALLBACK_INVOCATION_PERIOD_MS = 1000 * 30 * 1; // offer to fallback up to once every 30s
|
private static final long FALLBACK_INVOCATION_PERIOD_MS = 1000 * 30 * 1; // offer to fallback up to once every 30s
|
||||||
private boolean fallbackApplied;
|
private boolean fallbackApplied;
|
||||||
|
private boolean usedSyncingLocalNodeBeforeStartup;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
public XmrConnectionService(P2PService p2PService,
|
public XmrConnectionService(P2PService p2PService,
|
||||||
@ -156,7 +158,13 @@ public final class XmrConnectionService {
|
|||||||
p2PService.addP2PServiceListener(new P2PServiceListener() {
|
p2PService.addP2PServiceListener(new P2PServiceListener() {
|
||||||
@Override
|
@Override
|
||||||
public void onTorNodeReady() {
|
public void onTorNodeReady() {
|
||||||
ThreadUtils.submitToPool(() -> initialize());
|
ThreadUtils.submitToPool(() -> {
|
||||||
|
try {
|
||||||
|
initialize();
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.warn("Error initializing connection service, error={}\n", e.getMessage(), e);
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
@Override
|
@Override
|
||||||
public void onHiddenServicePublished() {}
|
public void onHiddenServicePublished() {}
|
||||||
@ -270,7 +278,7 @@ public final class XmrConnectionService {
|
|||||||
accountService.checkAccountOpen();
|
accountService.checkAccountOpen();
|
||||||
|
|
||||||
// user needs to authorize fallback on startup after using locally synced node
|
// user needs to authorize fallback on startup after using locally synced node
|
||||||
if (lastInfo == null && !fallbackApplied && lastUsedLocalSyncingNode() && !xmrLocalNode.isDetected()) {
|
if (fallbackRequiredBeforeConnectionSwitch()) {
|
||||||
log.warn("Cannot get best connection on startup because we last synced local node and user has not opted to fallback");
|
log.warn("Cannot get best connection on startup because we last synced local node and user has not opted to fallback");
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@ -283,6 +291,10 @@ public final class XmrConnectionService {
|
|||||||
return bestConnection;
|
return bestConnection;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private boolean fallbackRequiredBeforeConnectionSwitch() {
|
||||||
|
return lastInfo == null && !fallbackApplied && usedSyncingLocalNodeBeforeStartup && (!xmrLocalNode.isDetected() || xmrLocalNode.shouldBeIgnored());
|
||||||
|
}
|
||||||
|
|
||||||
private void addLocalNodeIfIgnored(Collection<MoneroRpcConnection> ignoredConnections) {
|
private void addLocalNodeIfIgnored(Collection<MoneroRpcConnection> ignoredConnections) {
|
||||||
if (xmrLocalNode.shouldBeIgnored() && connectionManager.hasConnection(xmrLocalNode.getUri())) ignoredConnections.add(connectionManager.getConnectionByUri(xmrLocalNode.getUri()));
|
if (xmrLocalNode.shouldBeIgnored() && connectionManager.hasConnection(xmrLocalNode.getUri())) ignoredConnections.add(connectionManager.getConnectionByUri(xmrLocalNode.getUri()));
|
||||||
}
|
}
|
||||||
@ -458,15 +470,20 @@ public final class XmrConnectionService {
|
|||||||
|
|
||||||
public void fallbackToBestConnection() {
|
public void fallbackToBestConnection() {
|
||||||
if (isShutDownStarted) return;
|
if (isShutDownStarted) return;
|
||||||
if (xmrNodes.getProvidedXmrNodes().isEmpty()) {
|
fallbackApplied = true;
|
||||||
|
if (isProvidedConnections() || xmrNodes.getProvidedXmrNodes().isEmpty()) {
|
||||||
log.warn("Falling back to public nodes");
|
log.warn("Falling back to public nodes");
|
||||||
preferences.setMoneroNodesOptionOrdinal(XmrNodes.MoneroNodesOption.PUBLIC.ordinal());
|
preferences.setMoneroNodesOptionOrdinal(XmrNodes.MoneroNodesOption.PUBLIC.ordinal());
|
||||||
|
initializeConnections();
|
||||||
} else {
|
} else {
|
||||||
log.warn("Falling back to provided nodes");
|
log.warn("Falling back to provided nodes");
|
||||||
preferences.setMoneroNodesOptionOrdinal(XmrNodes.MoneroNodesOption.PROVIDED.ordinal());
|
preferences.setMoneroNodesOptionOrdinal(XmrNodes.MoneroNodesOption.PROVIDED.ordinal());
|
||||||
}
|
|
||||||
fallbackApplied = true;
|
|
||||||
initializeConnections();
|
initializeConnections();
|
||||||
|
if (getConnection() == null) {
|
||||||
|
log.warn("No provided nodes available, falling back to public nodes");
|
||||||
|
fallbackToBestConnection();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ------------------------------- HELPERS --------------------------------
|
// ------------------------------- HELPERS --------------------------------
|
||||||
@ -578,8 +595,8 @@ public final class XmrConnectionService {
|
|||||||
setConnection(connection.getUri());
|
setConnection(connection.getUri());
|
||||||
|
|
||||||
// reset error connecting to local node
|
// reset error connecting to local node
|
||||||
if (connectionServiceError.get() == XmrConnectionError.LOCAL && isConnectionLocalHost()) {
|
if (connectionServiceFallbackType.get() == XmrConnectionFallbackType.LOCAL && isConnectionLocalHost()) {
|
||||||
connectionServiceError.set(null);
|
connectionServiceFallbackType.set(null);
|
||||||
}
|
}
|
||||||
} else if (getConnection() != null && getConnection().getUri().equals(connection.getUri())) {
|
} else if (getConnection() != null && getConnection().getUri().equals(connection.getUri())) {
|
||||||
MoneroRpcConnection bestConnection = getBestConnection();
|
MoneroRpcConnection bestConnection = getBestConnection();
|
||||||
@ -602,9 +619,11 @@ public final class XmrConnectionService {
|
|||||||
// add default connections
|
// add default connections
|
||||||
for (XmrNode node : xmrNodes.getAllXmrNodes()) {
|
for (XmrNode node : xmrNodes.getAllXmrNodes()) {
|
||||||
if (node.hasClearNetAddress()) {
|
if (node.hasClearNetAddress()) {
|
||||||
MoneroRpcConnection connection = new MoneroRpcConnection(node.getAddress() + ":" + node.getPort()).setPriority(node.getPriority());
|
if (!xmrLocalNode.shouldBeIgnored() || !xmrLocalNode.equalsUri(node.getClearNetUri())) {
|
||||||
|
MoneroRpcConnection connection = new MoneroRpcConnection(node.getHostNameOrAddress() + ":" + node.getPort()).setPriority(node.getPriority());
|
||||||
if (!connectionList.hasConnection(connection.getUri())) addConnection(connection);
|
if (!connectionList.hasConnection(connection.getUri())) addConnection(connection);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
if (node.hasOnionAddress()) {
|
if (node.hasOnionAddress()) {
|
||||||
MoneroRpcConnection connection = new MoneroRpcConnection(node.getOnionAddress() + ":" + node.getPort()).setPriority(node.getPriority());
|
MoneroRpcConnection connection = new MoneroRpcConnection(node.getOnionAddress() + ":" + node.getPort()).setPriority(node.getPriority());
|
||||||
if (!connectionList.hasConnection(connection.getUri())) addConnection(connection);
|
if (!connectionList.hasConnection(connection.getUri())) addConnection(connection);
|
||||||
@ -615,9 +634,11 @@ public final class XmrConnectionService {
|
|||||||
// add default connections
|
// add default connections
|
||||||
for (XmrNode node : xmrNodes.selectPreferredNodes(new XmrNodesSetupPreferences(preferences))) {
|
for (XmrNode node : xmrNodes.selectPreferredNodes(new XmrNodesSetupPreferences(preferences))) {
|
||||||
if (node.hasClearNetAddress()) {
|
if (node.hasClearNetAddress()) {
|
||||||
MoneroRpcConnection connection = new MoneroRpcConnection(node.getAddress() + ":" + node.getPort()).setPriority(node.getPriority());
|
if (!xmrLocalNode.shouldBeIgnored() || !xmrLocalNode.equalsUri(node.getClearNetUri())) {
|
||||||
|
MoneroRpcConnection connection = new MoneroRpcConnection(node.getHostNameOrAddress() + ":" + node.getPort()).setPriority(node.getPriority());
|
||||||
addConnection(connection);
|
addConnection(connection);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
if (node.hasOnionAddress()) {
|
if (node.hasOnionAddress()) {
|
||||||
MoneroRpcConnection connection = new MoneroRpcConnection(node.getOnionAddress() + ":" + node.getPort()).setPriority(node.getPriority());
|
MoneroRpcConnection connection = new MoneroRpcConnection(node.getOnionAddress() + ":" + node.getPort()).setPriority(node.getPriority());
|
||||||
addConnection(connection);
|
addConnection(connection);
|
||||||
@ -632,6 +653,11 @@ public final class XmrConnectionService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// set if last node was locally syncing
|
||||||
|
if (!isInitialized) {
|
||||||
|
usedSyncingLocalNodeBeforeStartup = connectionList.getCurrentConnectionUri().isPresent() && xmrLocalNode.equalsUri(connectionList.getCurrentConnectionUri().get()) && preferences.getXmrNodeSettings().getSyncBlockchain();
|
||||||
|
}
|
||||||
|
|
||||||
// set connection proxies
|
// set connection proxies
|
||||||
log.info("TOR proxy URI: " + getProxyUri());
|
log.info("TOR proxy URI: " + getProxyUri());
|
||||||
for (MoneroRpcConnection connection : connectionManager.getConnections()) {
|
for (MoneroRpcConnection connection : connectionManager.getConnections()) {
|
||||||
@ -666,29 +692,16 @@ public final class XmrConnectionService {
|
|||||||
onConnectionChanged(connectionManager.getConnection());
|
onConnectionChanged(connectionManager.getConnection());
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean lastUsedLocalSyncingNode() {
|
public void startLocalNode() throws Exception {
|
||||||
return connectionManager.getConnection() != null && xmrLocalNode.equalsUri(connectionManager.getConnection().getUri()) && !xmrLocalNode.isDetected() && !xmrLocalNode.shouldBeIgnored();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void startLocalNode() {
|
|
||||||
|
|
||||||
// cannot start local node as seed node
|
// cannot start local node as seed node
|
||||||
if (HavenoUtils.isSeedNode()) {
|
if (HavenoUtils.isSeedNode()) {
|
||||||
throw new RuntimeException("Cannot start local node on seed node");
|
throw new RuntimeException("Cannot start local node on seed node");
|
||||||
}
|
}
|
||||||
|
|
||||||
// start local node if offline and used as last connection
|
// start local node
|
||||||
if (connectionManager.getConnection() != null && xmrLocalNode.equalsUri(connectionManager.getConnection().getUri()) && !xmrLocalNode.isDetected() && !xmrLocalNode.shouldBeIgnored()) {
|
|
||||||
try {
|
|
||||||
log.info("Starting local node");
|
log.info("Starting local node");
|
||||||
xmrLocalNode.start();
|
xmrLocalNode.start();
|
||||||
} catch (Exception e) {
|
|
||||||
log.error("Unable to start local monero node, error={}\n", e.getMessage(), e);
|
|
||||||
throw new RuntimeException(e);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
throw new RuntimeException("Local node is not offline and used as last connection");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void onConnectionChanged(MoneroRpcConnection currentConnection) {
|
private void onConnectionChanged(MoneroRpcConnection currentConnection) {
|
||||||
@ -768,7 +781,7 @@ public final class XmrConnectionService {
|
|||||||
try {
|
try {
|
||||||
|
|
||||||
// poll daemon
|
// poll daemon
|
||||||
if (daemon == null) switchToBestConnection();
|
if (daemon == null && !fallbackRequiredBeforeConnectionSwitch()) switchToBestConnection();
|
||||||
try {
|
try {
|
||||||
if (daemon == null) throw new RuntimeException("No connection to Monero daemon");
|
if (daemon == null) throw new RuntimeException("No connection to Monero daemon");
|
||||||
lastInfo = daemon.getInfo();
|
lastInfo = daemon.getInfo();
|
||||||
@ -778,16 +791,19 @@ public final class XmrConnectionService {
|
|||||||
if (isShutDownStarted) return;
|
if (isShutDownStarted) return;
|
||||||
|
|
||||||
// invoke fallback handling on startup error
|
// invoke fallback handling on startup error
|
||||||
boolean canFallback = isFixedConnection() || isCustomConnections() || lastUsedLocalSyncingNode();
|
boolean canFallback = isFixedConnection() || isProvidedConnections() || isCustomConnections() || usedSyncingLocalNodeBeforeStartup;
|
||||||
if (lastInfo == null && canFallback) {
|
if (lastInfo == null && canFallback) {
|
||||||
if (connectionServiceError.get() == null && (lastFallbackInvocation == null || System.currentTimeMillis() - lastFallbackInvocation > FALLBACK_INVOCATION_PERIOD_MS)) {
|
if (connectionServiceFallbackType.get() == null && (lastFallbackInvocation == null || System.currentTimeMillis() - lastFallbackInvocation > FALLBACK_INVOCATION_PERIOD_MS)) {
|
||||||
lastFallbackInvocation = System.currentTimeMillis();
|
lastFallbackInvocation = System.currentTimeMillis();
|
||||||
if (lastUsedLocalSyncingNode()) {
|
if (usedSyncingLocalNodeBeforeStartup) {
|
||||||
log.warn("Failed to fetch daemon info from local connection on startup: " + e.getMessage());
|
log.warn("Failed to fetch daemon info from local connection on startup: " + e.getMessage());
|
||||||
connectionServiceError.set(XmrConnectionError.LOCAL);
|
connectionServiceFallbackType.set(XmrConnectionFallbackType.LOCAL);
|
||||||
|
} else if (isProvidedConnections()) {
|
||||||
|
log.warn("Failed to fetch daemon info from provided connections on startup: " + e.getMessage());
|
||||||
|
connectionServiceFallbackType.set(XmrConnectionFallbackType.PROVIDED);
|
||||||
} else {
|
} else {
|
||||||
log.warn("Failed to fetch daemon info from custom connection on startup: " + e.getMessage());
|
log.warn("Failed to fetch daemon info from custom connection on startup: " + e.getMessage());
|
||||||
connectionServiceError.set(XmrConnectionError.CUSTOM);
|
connectionServiceFallbackType.set(XmrConnectionFallbackType.CUSTOM);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
@ -808,7 +824,7 @@ public final class XmrConnectionService {
|
|||||||
|
|
||||||
// connected to daemon
|
// connected to daemon
|
||||||
isConnected = true;
|
isConnected = true;
|
||||||
connectionServiceError.set(null);
|
connectionServiceFallbackType.set(null);
|
||||||
|
|
||||||
// determine if blockchain is syncing locally
|
// determine if blockchain is syncing locally
|
||||||
boolean blockchainSyncing = lastInfo.getHeight().equals(lastInfo.getHeightWithoutBootstrap()) || (lastInfo.getTargetHeight().equals(0l) && lastInfo.getHeightWithoutBootstrap().equals(0l)); // blockchain is syncing if height equals height without bootstrap, or target height and height without bootstrap both equal 0
|
boolean blockchainSyncing = lastInfo.getHeight().equals(lastInfo.getHeightWithoutBootstrap()) || (lastInfo.getTargetHeight().equals(0l) && lastInfo.getHeightWithoutBootstrap().equals(0l)); // blockchain is syncing if height equals height without bootstrap, or target height and height without bootstrap both equal 0
|
||||||
@ -885,10 +901,14 @@ public final class XmrConnectionService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private boolean isFixedConnection() {
|
private boolean isFixedConnection() {
|
||||||
return !"".equals(config.xmrNode) && (!HavenoUtils.isLocalHost(config.xmrNode) || !xmrLocalNode.shouldBeIgnored()) && !fallbackApplied;
|
return !"".equals(config.xmrNode) && !(HavenoUtils.isLocalHost(config.xmrNode) && xmrLocalNode.shouldBeIgnored()) && !fallbackApplied;
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean isCustomConnections() {
|
private boolean isCustomConnections() {
|
||||||
return preferences.getMoneroNodesOption() == XmrNodes.MoneroNodesOption.CUSTOM;
|
return preferences.getMoneroNodesOption() == XmrNodes.MoneroNodesOption.CUSTOM;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private boolean isProvidedConnections() {
|
||||||
|
return preferences.getMoneroNodesOption() == XmrNodes.MoneroNodesOption.PROVIDED;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -109,17 +109,18 @@ public class XmrLocalNode {
|
|||||||
public boolean shouldBeIgnored() {
|
public boolean shouldBeIgnored() {
|
||||||
if (config.ignoreLocalXmrNode) return true;
|
if (config.ignoreLocalXmrNode) return true;
|
||||||
|
|
||||||
// determine if local node is configured
|
// ignore if fixed connection is not local
|
||||||
|
if (!"".equals(config.xmrNode)) return !HavenoUtils.isLocalHost(config.xmrNode);
|
||||||
|
|
||||||
|
// check if local node is within configuration
|
||||||
boolean hasConfiguredLocalNode = false;
|
boolean hasConfiguredLocalNode = false;
|
||||||
for (XmrNode node : xmrNodes.selectPreferredNodes(new XmrNodesSetupPreferences(preferences))) {
|
for (XmrNode node : xmrNodes.selectPreferredNodes(new XmrNodesSetupPreferences(preferences))) {
|
||||||
if (node.getAddress() != null && equalsUri("http://" + node.getAddress() + ":" + node.getPort())) {
|
if (node.hasClearNetAddress() && equalsUri(node.getClearNetUri())) {
|
||||||
hasConfiguredLocalNode = true;
|
hasConfiguredLocalNode = true;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (!hasConfiguredLocalNode) return true;
|
return !hasConfiguredLocalNode;
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void addListener(XmrLocalNodeListener listener) {
|
public void addListener(XmrLocalNodeListener listener) {
|
||||||
|
@ -75,7 +75,7 @@ public class HavenoHeadlessApp implements HeadlessApp {
|
|||||||
log.info("onDisplayTacHandler: We accept the tacs automatically in headless mode");
|
log.info("onDisplayTacHandler: We accept the tacs automatically in headless mode");
|
||||||
acceptedHandler.run();
|
acceptedHandler.run();
|
||||||
});
|
});
|
||||||
havenoSetup.setDisplayMoneroConnectionErrorHandler(show -> log.warn("onDisplayMoneroConnectionErrorHandler: show={}", show));
|
havenoSetup.setDisplayMoneroConnectionFallbackHandler(show -> log.warn("onDisplayMoneroConnectionFallbackHandler: show={}", show));
|
||||||
havenoSetup.setDisplayTorNetworkSettingsHandler(show -> log.info("onDisplayTorNetworkSettingsHandler: show={}", show));
|
havenoSetup.setDisplayTorNetworkSettingsHandler(show -> log.info("onDisplayTorNetworkSettingsHandler: show={}", show));
|
||||||
havenoSetup.setChainFileLockedExceptionHandler(msg -> log.error("onChainFileLockedExceptionHandler: msg={}", msg));
|
havenoSetup.setChainFileLockedExceptionHandler(msg -> log.error("onChainFileLockedExceptionHandler: msg={}", msg));
|
||||||
tradeManager.setLockedUpFundsHandler(msg -> log.info("onLockedUpFundsHandler: msg={}", msg));
|
tradeManager.setLockedUpFundsHandler(msg -> log.info("onLockedUpFundsHandler: msg={}", msg));
|
||||||
|
@ -55,7 +55,7 @@ import haveno.core.alert.PrivateNotificationManager;
|
|||||||
import haveno.core.alert.PrivateNotificationPayload;
|
import haveno.core.alert.PrivateNotificationPayload;
|
||||||
import haveno.core.api.CoreContext;
|
import haveno.core.api.CoreContext;
|
||||||
import haveno.core.api.XmrConnectionService;
|
import haveno.core.api.XmrConnectionService;
|
||||||
import haveno.core.api.XmrConnectionService.XmrConnectionError;
|
import haveno.core.api.XmrConnectionService.XmrConnectionFallbackType;
|
||||||
import haveno.core.api.XmrLocalNode;
|
import haveno.core.api.XmrLocalNode;
|
||||||
import haveno.core.locale.Res;
|
import haveno.core.locale.Res;
|
||||||
import haveno.core.offer.OpenOfferManager;
|
import haveno.core.offer.OpenOfferManager;
|
||||||
@ -159,7 +159,7 @@ public class HavenoSetup {
|
|||||||
rejectedTxErrorMessageHandler;
|
rejectedTxErrorMessageHandler;
|
||||||
@Setter
|
@Setter
|
||||||
@Nullable
|
@Nullable
|
||||||
private Consumer<XmrConnectionError> displayMoneroConnectionErrorHandler;
|
private Consumer<XmrConnectionFallbackType> displayMoneroConnectionFallbackHandler;
|
||||||
@Setter
|
@Setter
|
||||||
@Nullable
|
@Nullable
|
||||||
private Consumer<Boolean> displayTorNetworkSettingsHandler;
|
private Consumer<Boolean> displayTorNetworkSettingsHandler;
|
||||||
@ -431,9 +431,9 @@ public class HavenoSetup {
|
|||||||
getXmrWalletSyncProgress().addListener((observable, oldValue, newValue) -> resetStartupTimeout());
|
getXmrWalletSyncProgress().addListener((observable, oldValue, newValue) -> resetStartupTimeout());
|
||||||
|
|
||||||
// listen for fallback handling
|
// listen for fallback handling
|
||||||
getConnectionServiceError().addListener((observable, oldValue, newValue) -> {
|
getConnectionServiceFallbackType().addListener((observable, oldValue, newValue) -> {
|
||||||
if (displayMoneroConnectionErrorHandler == null) return;
|
if (displayMoneroConnectionFallbackHandler == null) return;
|
||||||
displayMoneroConnectionErrorHandler.accept(newValue);
|
displayMoneroConnectionFallbackHandler.accept(newValue);
|
||||||
});
|
});
|
||||||
|
|
||||||
log.info("Init P2P network");
|
log.info("Init P2P network");
|
||||||
@ -735,8 +735,8 @@ public class HavenoSetup {
|
|||||||
return xmrConnectionService.getConnectionServiceErrorMsg();
|
return xmrConnectionService.getConnectionServiceErrorMsg();
|
||||||
}
|
}
|
||||||
|
|
||||||
public ObjectProperty<XmrConnectionError> getConnectionServiceError() {
|
public ObjectProperty<XmrConnectionFallbackType> getConnectionServiceFallbackType() {
|
||||||
return xmrConnectionService.getConnectionServiceError();
|
return xmrConnectionService.getConnectionServiceFallbackType();
|
||||||
}
|
}
|
||||||
|
|
||||||
public StringProperty getTopErrorMsg() {
|
public StringProperty getTopErrorMsg() {
|
||||||
|
@ -149,6 +149,20 @@ public class OfferBookService {
|
|||||||
Offer offer = new Offer(offerPayload);
|
Offer offer = new Offer(offerPayload);
|
||||||
offer.setPriceFeedService(priceFeedService);
|
offer.setPriceFeedService(priceFeedService);
|
||||||
announceOfferRemoved(offer);
|
announceOfferRemoved(offer);
|
||||||
|
|
||||||
|
// check if invalid offers are now valid
|
||||||
|
synchronized (invalidOffers) {
|
||||||
|
for (Offer invalidOffer : new ArrayList<Offer>(invalidOffers)) {
|
||||||
|
try {
|
||||||
|
validateOfferPayload(invalidOffer.getOfferPayload());
|
||||||
|
removeInvalidOffer(invalidOffer.getId());
|
||||||
|
replaceValidOffer(invalidOffer);
|
||||||
|
announceOfferAdded(invalidOffer);
|
||||||
|
} catch (Exception e) {
|
||||||
|
// ignore
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}, OfferBookService.class.getSimpleName());
|
}, OfferBookService.class.getSimpleName());
|
||||||
@ -298,20 +312,6 @@ public class OfferBookService {
|
|||||||
synchronized (offerBookChangedListeners) {
|
synchronized (offerBookChangedListeners) {
|
||||||
offerBookChangedListeners.forEach(listener -> listener.onRemoved(offer));
|
offerBookChangedListeners.forEach(listener -> listener.onRemoved(offer));
|
||||||
}
|
}
|
||||||
|
|
||||||
// check if invalid offers are now valid
|
|
||||||
synchronized (invalidOffers) {
|
|
||||||
for (Offer invalidOffer : new ArrayList<Offer>(invalidOffers)) {
|
|
||||||
try {
|
|
||||||
validateOfferPayload(invalidOffer.getOfferPayload());
|
|
||||||
removeInvalidOffer(invalidOffer.getId());
|
|
||||||
replaceValidOffer(invalidOffer);
|
|
||||||
announceOfferAdded(invalidOffer);
|
|
||||||
} catch (Exception e) {
|
|
||||||
// ignore
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean hasValidOffer(String offerId) {
|
private boolean hasValidOffer(String offerId) {
|
||||||
|
@ -1101,6 +1101,8 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
|
|||||||
} else {
|
} else {
|
||||||
|
|
||||||
// validate non-pending state
|
// validate non-pending state
|
||||||
|
boolean skipValidation = openOffer.isDeactivated() && hasConflictingClone(openOffer) && openOffer.getOffer().getOfferPayload().getArbitratorSignature() == null; // clone with conflicting offer is deactivated and unsigned at first
|
||||||
|
if (!skipValidation) {
|
||||||
try {
|
try {
|
||||||
validateSignedState(openOffer);
|
validateSignedState(openOffer);
|
||||||
resultHandler.handleResult(null); // done processing if non-pending state is valid
|
resultHandler.handleResult(null); // done processing if non-pending state is valid
|
||||||
@ -1114,6 +1116,7 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
|
|||||||
if (openOffer.isAvailable()) openOffer.setState(OpenOffer.State.PENDING);
|
if (openOffer.isAvailable()) openOffer.setState(OpenOffer.State.PENDING);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// sign and post offer if already funded
|
// sign and post offer if already funded
|
||||||
if (openOffer.getReserveTxHash() != null) {
|
if (openOffer.getReserveTxHash() != null) {
|
||||||
@ -1574,14 +1577,6 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// verify the max version number
|
|
||||||
if (Version.compare(request.getOfferPayload().getVersionNr(), Version.VERSION) > 0) {
|
|
||||||
errorMessage = "Offer version number is too high: " + request.getOfferPayload().getVersionNr() + " > " + Version.VERSION;
|
|
||||||
log.warn(errorMessage);
|
|
||||||
sendAckMessage(request.getClass(), peer, request.getPubKeyRing(), request.getOfferId(), request.getUid(), false, errorMessage);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// verify maker and taker fees
|
// verify maker and taker fees
|
||||||
boolean hasBuyerAsTakerWithoutDeposit = offer.getDirection() == OfferDirection.SELL && offer.isPrivateOffer() && offer.getChallengeHash() != null && offer.getChallengeHash().length() > 0 && offer.getTakerFeePct() == 0;
|
boolean hasBuyerAsTakerWithoutDeposit = offer.getDirection() == OfferDirection.SELL && offer.isPrivateOffer() && offer.getChallengeHash() != null && offer.getChallengeHash().length() > 0 && offer.getTakerFeePct() == 0;
|
||||||
if (hasBuyerAsTakerWithoutDeposit) {
|
if (hasBuyerAsTakerWithoutDeposit) {
|
||||||
@ -2023,7 +2018,7 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
|
|||||||
originalOfferPayload.getAcceptedCountryCodes(),
|
originalOfferPayload.getAcceptedCountryCodes(),
|
||||||
originalOfferPayload.getBankId(),
|
originalOfferPayload.getBankId(),
|
||||||
originalOfferPayload.getAcceptedBankIds(),
|
originalOfferPayload.getAcceptedBankIds(),
|
||||||
originalOfferPayload.getVersionNr(),
|
Version.VERSION,
|
||||||
originalOfferPayload.getBlockHeightAtOfferCreation(),
|
originalOfferPayload.getBlockHeightAtOfferCreation(),
|
||||||
originalOfferPayload.getMaxTradeLimit(),
|
originalOfferPayload.getMaxTradeLimit(),
|
||||||
originalOfferPayload.getMaxTradePeriod(),
|
originalOfferPayload.getMaxTradePeriod(),
|
||||||
|
@ -145,6 +145,7 @@ public abstract class Trade extends XmrWalletBase implements Tradable, Model {
|
|||||||
private static final long DELETE_AFTER_MS = TradeProtocol.TRADE_STEP_TIMEOUT_SECONDS;
|
private static final long DELETE_AFTER_MS = TradeProtocol.TRADE_STEP_TIMEOUT_SECONDS;
|
||||||
private static final int NUM_CONFIRMATIONS_FOR_SCHEDULED_IMPORT = 5;
|
private static final int NUM_CONFIRMATIONS_FOR_SCHEDULED_IMPORT = 5;
|
||||||
protected final Object pollLock = new Object();
|
protected final Object pollLock = new Object();
|
||||||
|
private final Object removeTradeOnErrorLock = new Object();
|
||||||
protected static final Object importMultisigLock = new Object();
|
protected static final Object importMultisigLock = new Object();
|
||||||
private boolean pollInProgress;
|
private boolean pollInProgress;
|
||||||
private boolean restartInProgress;
|
private boolean restartInProgress;
|
||||||
@ -810,6 +811,10 @@ public abstract class Trade extends XmrWalletBase implements Tradable, Model {
|
|||||||
if (processModel.getTradeManager() != null) processModel.getTradeManager().requestPersistence();
|
if (processModel.getTradeManager() != null) processModel.getTradeManager().requestPersistence();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void persistNow(@Nullable Runnable completeHandler) {
|
||||||
|
processModel.getTradeManager().persistNow(completeHandler);
|
||||||
|
}
|
||||||
|
|
||||||
public TradeProtocol getProtocol() {
|
public TradeProtocol getProtocol() {
|
||||||
return processModel.getTradeManager().getTradeProtocol(this);
|
return processModel.getTradeManager().getTradeProtocol(this);
|
||||||
}
|
}
|
||||||
@ -1608,11 +1613,12 @@ public abstract class Trade extends XmrWalletBase implements Tradable, Model {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// shut down trade threads
|
// shut down trade threads
|
||||||
isInitialized = false;
|
|
||||||
isShutDown = true;
|
isShutDown = true;
|
||||||
List<Runnable> shutDownThreads = new ArrayList<>();
|
List<Runnable> shutDownThreads = new ArrayList<>();
|
||||||
shutDownThreads.add(() -> ThreadUtils.shutDown(getId()));
|
shutDownThreads.add(() -> ThreadUtils.shutDown(getId()));
|
||||||
ThreadUtils.awaitTasks(shutDownThreads);
|
ThreadUtils.awaitTasks(shutDownThreads);
|
||||||
|
stopProtocolTimeout();
|
||||||
|
isInitialized = false;
|
||||||
|
|
||||||
// save and close
|
// save and close
|
||||||
if (wallet != null) {
|
if (wallet != null) {
|
||||||
@ -1765,25 +1771,31 @@ public abstract class Trade extends XmrWalletBase implements Tradable, Model {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void removeTradeOnError() {
|
private void removeTradeOnError() {
|
||||||
|
synchronized (removeTradeOnErrorLock) {
|
||||||
|
|
||||||
|
// skip if already shut down or removed
|
||||||
|
if (isShutDown || !processModel.getTradeManager().hasTrade(getId())) return;
|
||||||
log.warn("removeTradeOnError() trade={}, tradeId={}, state={}", getClass().getSimpleName(), getShortId(), getState());
|
log.warn("removeTradeOnError() trade={}, tradeId={}, state={}", getClass().getSimpleName(), getShortId(), getState());
|
||||||
|
|
||||||
// force close and re-open wallet in case stuck
|
// force close and re-open wallet in case stuck
|
||||||
forceCloseWallet();
|
forceCloseWallet();
|
||||||
if (isDepositRequested()) getWallet();
|
if (isDepositRequested()) getWallet();
|
||||||
|
|
||||||
|
// clear and shut down trade
|
||||||
|
onShutDownStarted();
|
||||||
|
clearAndShutDown();
|
||||||
|
|
||||||
// shut down trade thread
|
// shut down trade thread
|
||||||
try {
|
try {
|
||||||
ThreadUtils.shutDown(getId(), 1000l);
|
ThreadUtils.shutDown(getId(), 5000l);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
log.warn("Error shutting down trade thread for {} {}: {}", getClass().getSimpleName(), getId(), e.getMessage());
|
log.warn("Error shutting down trade thread for {} {}: {}", getClass().getSimpleName(), getId(), e.getMessage());
|
||||||
}
|
}
|
||||||
|
|
||||||
// clear and shut down trade
|
|
||||||
clearAndShutDown();
|
|
||||||
|
|
||||||
// unregister trade
|
// unregister trade
|
||||||
processModel.getTradeManager().unregisterTrade(this);
|
processModel.getTradeManager().unregisterTrade(this);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
// Model implementation
|
// Model implementation
|
||||||
@ -1824,6 +1836,13 @@ public abstract class Trade extends XmrWalletBase implements Tradable, Model {
|
|||||||
getProtocol().startTimeout(TradeProtocol.TRADE_STEP_TIMEOUT_SECONDS);
|
getProtocol().startTimeout(TradeProtocol.TRADE_STEP_TIMEOUT_SECONDS);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void stopProtocolTimeout() {
|
||||||
|
if (!isInitialized) return;
|
||||||
|
TradeProtocol protocol = getProtocol();
|
||||||
|
if (protocol == null) return;
|
||||||
|
protocol.stopTimeout();
|
||||||
|
}
|
||||||
|
|
||||||
public void setState(State state) {
|
public void setState(State state) {
|
||||||
if (isInitialized) {
|
if (isInitialized) {
|
||||||
// We don't want to log at startup the setState calls from all persisted trades
|
// We don't want to log at startup the setState calls from all persisted trades
|
||||||
@ -1837,7 +1856,7 @@ public abstract class Trade extends XmrWalletBase implements Tradable, Model {
|
|||||||
|
|
||||||
this.state = state;
|
this.state = state;
|
||||||
requestPersistence();
|
requestPersistence();
|
||||||
UserThread.await(() -> {
|
UserThread.execute(() -> {
|
||||||
stateProperty.set(state);
|
stateProperty.set(state);
|
||||||
phaseProperty.set(state.getPhase());
|
phaseProperty.set(state.getPhase());
|
||||||
});
|
});
|
||||||
@ -1869,7 +1888,7 @@ public abstract class Trade extends XmrWalletBase implements Tradable, Model {
|
|||||||
|
|
||||||
this.payoutState = payoutState;
|
this.payoutState = payoutState;
|
||||||
requestPersistence();
|
requestPersistence();
|
||||||
UserThread.await(() -> payoutStateProperty.set(payoutState));
|
UserThread.execute(() -> payoutStateProperty.set(payoutState));
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setDisputeState(DisputeState disputeState) {
|
public void setDisputeState(DisputeState disputeState) {
|
||||||
@ -2648,7 +2667,7 @@ public abstract class Trade extends XmrWalletBase implements Tradable, Model {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
setDepositTxs(txs);
|
setDepositTxs(txs);
|
||||||
if (getMaker().getDepositTx() == null || (getTaker().getDepositTx() == null && !hasBuyerAsTakerWithoutDeposit())) return; // skip if either deposit tx not seen
|
if (!isPublished(getMaker().getDepositTx()) || (!hasBuyerAsTakerWithoutDeposit() && !isPublished(getTaker().getDepositTx()))) return; // skip if deposit txs not published successfully
|
||||||
setStateDepositsSeen();
|
setStateDepositsSeen();
|
||||||
|
|
||||||
// set actual security deposits
|
// set actual security deposits
|
||||||
@ -2750,6 +2769,13 @@ public abstract class Trade extends XmrWalletBase implements Tradable, Model {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static boolean isPublished(MoneroTx tx) {
|
||||||
|
if (tx == null) return false;
|
||||||
|
if (Boolean.TRUE.equals(tx.isFailed())) return false;
|
||||||
|
if (!Boolean.TRUE.equals(tx.inTxPool()) && !Boolean.TRUE.equals(tx.isConfirmed())) return false;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
private void syncWalletIfBehind() {
|
private void syncWalletIfBehind() {
|
||||||
synchronized (walletLock) {
|
synchronized (walletLock) {
|
||||||
if (isWalletBehind()) {
|
if (isWalletBehind()) {
|
||||||
|
@ -546,6 +546,10 @@ public class TradeManager implements PersistedDataHost, DecryptedDirectMessageLi
|
|||||||
persistenceManager.requestPersistence();
|
persistenceManager.requestPersistence();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void persistNow(@Nullable Runnable completeHandler) {
|
||||||
|
persistenceManager.persistNow(completeHandler);
|
||||||
|
}
|
||||||
|
|
||||||
private void handleInitTradeRequest(InitTradeRequest request, NodeAddress sender) {
|
private void handleInitTradeRequest(InitTradeRequest request, NodeAddress sender) {
|
||||||
log.info("TradeManager handling InitTradeRequest for tradeId={}, sender={}, uid={}", request.getOfferId(), sender, request.getUid());
|
log.info("TradeManager handling InitTradeRequest for tradeId={}, sender={}, uid={}", request.getOfferId(), sender, request.getUid());
|
||||||
|
|
||||||
@ -563,9 +567,14 @@ public class TradeManager implements PersistedDataHost, DecryptedDirectMessageLi
|
|||||||
Optional<OpenOffer> openOfferOptional = openOfferManager.getOpenOffer(request.getOfferId());
|
Optional<OpenOffer> openOfferOptional = openOfferManager.getOpenOffer(request.getOfferId());
|
||||||
if (!openOfferOptional.isPresent()) return;
|
if (!openOfferOptional.isPresent()) return;
|
||||||
OpenOffer openOffer = openOfferOptional.get();
|
OpenOffer openOffer = openOfferOptional.get();
|
||||||
if (openOffer.getState() != OpenOffer.State.AVAILABLE) return;
|
|
||||||
Offer offer = openOffer.getOffer();
|
Offer offer = openOffer.getOffer();
|
||||||
|
|
||||||
|
// check availability
|
||||||
|
if (openOffer.getState() != OpenOffer.State.AVAILABLE) {
|
||||||
|
log.warn("Ignoring InitTradeRequest to maker because offer is not available, offerId={}, sender={}", request.getOfferId(), sender);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// validate challenge
|
// validate challenge
|
||||||
if (openOffer.getChallenge() != null && !HavenoUtils.getChallengeHash(openOffer.getChallenge()).equals(HavenoUtils.getChallengeHash(request.getChallenge()))) {
|
if (openOffer.getChallenge() != null && !HavenoUtils.getChallengeHash(openOffer.getChallenge()).equals(HavenoUtils.getChallengeHash(request.getChallenge()))) {
|
||||||
log.warn("Ignoring InitTradeRequest to maker because challenge is incorrect, tradeId={}, sender={}", request.getOfferId(), sender);
|
log.warn("Ignoring InitTradeRequest to maker because challenge is incorrect, tradeId={}, sender={}", request.getOfferId(), sender);
|
||||||
@ -980,9 +989,7 @@ public class TradeManager implements PersistedDataHost, DecryptedDirectMessageLi
|
|||||||
closedTradableManager.add(trade);
|
closedTradableManager.add(trade);
|
||||||
trade.setCompleted(true);
|
trade.setCompleted(true);
|
||||||
removeTrade(trade, true);
|
removeTrade(trade, true);
|
||||||
|
xmrWalletService.swapPayoutAddressEntryToAvailable(trade.getId()); // TODO The address entry should have been removed already. Check and if its the case remove that.
|
||||||
// TODO The address entry should have been removed already. Check and if its the case remove that.
|
|
||||||
xmrWalletService.swapPayoutAddressEntryToAvailable(trade.getId());
|
|
||||||
requestPersistence();
|
requestPersistence();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -990,6 +997,7 @@ public class TradeManager implements PersistedDataHost, DecryptedDirectMessageLi
|
|||||||
log.warn("Unregistering {} {}", trade.getClass().getSimpleName(), trade.getId());
|
log.warn("Unregistering {} {}", trade.getClass().getSimpleName(), trade.getId());
|
||||||
removeTrade(trade, true);
|
removeTrade(trade, true);
|
||||||
removeFailedTrade(trade);
|
removeFailedTrade(trade);
|
||||||
|
xmrWalletService.swapPayoutAddressEntryToAvailable(trade.getId()); // TODO The address entry should have been removed already. Check and if its the case remove that.
|
||||||
requestPersistence();
|
requestPersistence();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1274,11 +1282,15 @@ public class TradeManager implements PersistedDataHost, DecryptedDirectMessageLi
|
|||||||
return offer.getDirection() == OfferDirection.SELL;
|
return offer.getDirection() == OfferDirection.SELL;
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO (woodser): make Optional<Trade> versus Trade return types consistent
|
// TODO: make Optional<Trade> versus Trade return types consistent
|
||||||
public Trade getTrade(String tradeId) {
|
public Trade getTrade(String tradeId) {
|
||||||
return getOpenTrade(tradeId).orElseGet(() -> getClosedTrade(tradeId).orElseGet(() -> getFailedTrade(tradeId).orElseGet(() -> null)));
|
return getOpenTrade(tradeId).orElseGet(() -> getClosedTrade(tradeId).orElseGet(() -> getFailedTrade(tradeId).orElseGet(() -> null)));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean hasTrade(String tradeId) {
|
||||||
|
return getTrade(tradeId) != null;
|
||||||
|
}
|
||||||
|
|
||||||
public Optional<Trade> getOpenTrade(String tradeId) {
|
public Optional<Trade> getOpenTrade(String tradeId) {
|
||||||
synchronized (tradableList.getList()) {
|
synchronized (tradableList.getList()) {
|
||||||
return tradableList.stream().filter(e -> e.getId().equals(tradeId)).findFirst();
|
return tradableList.stream().filter(e -> e.getId().equals(tradeId)).findFirst();
|
||||||
|
@ -460,9 +460,19 @@ public abstract class TradeProtocol implements DecryptedDirectMessageListener, D
|
|||||||
.using(new TradeTaskRunner(trade,
|
.using(new TradeTaskRunner(trade,
|
||||||
() -> {
|
() -> {
|
||||||
stopTimeout();
|
stopTimeout();
|
||||||
|
|
||||||
|
// tasks may complete successfully but process an error
|
||||||
|
if (trade.getInitError() == null) {
|
||||||
this.errorMessageHandler = null; // TODO: set this when trade state is >= DEPOSIT_PUBLISHED
|
this.errorMessageHandler = null; // TODO: set this when trade state is >= DEPOSIT_PUBLISHED
|
||||||
handleTaskRunnerSuccess(sender, response);
|
handleTaskRunnerSuccess(sender, response);
|
||||||
if (tradeResultHandler != null) tradeResultHandler.handleResult(trade); // trade is initialized
|
if (tradeResultHandler != null) tradeResultHandler.handleResult(trade); // trade is initialized
|
||||||
|
} else {
|
||||||
|
handleTaskRunnerSuccess(sender, response);
|
||||||
|
if (errorMessageHandler != null) errorMessageHandler.handleErrorMessage(trade.getInitError().getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
this.tradeResultHandler = null;
|
||||||
|
this.errorMessageHandler = null;
|
||||||
},
|
},
|
||||||
errorMessage -> {
|
errorMessage -> {
|
||||||
handleTaskRunnerFault(sender, response, errorMessage);
|
handleTaskRunnerFault(sender, response, errorMessage);
|
||||||
@ -527,7 +537,7 @@ public abstract class TradeProtocol implements DecryptedDirectMessageListener, D
|
|||||||
|
|
||||||
// save message for reprocessing
|
// save message for reprocessing
|
||||||
trade.getBuyer().setPaymentSentMessage(message);
|
trade.getBuyer().setPaymentSentMessage(message);
|
||||||
trade.requestPersistence();
|
trade.persistNow(() -> {
|
||||||
|
|
||||||
// process message on trade thread
|
// process message on trade thread
|
||||||
if (!trade.isInitialized() || trade.isShutDownStarted()) return;
|
if (!trade.isInitialized() || trade.isShutDownStarted()) return;
|
||||||
@ -583,6 +593,7 @@ public abstract class TradeProtocol implements DecryptedDirectMessageListener, D
|
|||||||
awaitTradeLatch();
|
awaitTradeLatch();
|
||||||
}
|
}
|
||||||
}, trade.getId());
|
}, trade.getId());
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// received by buyer and arbitrator
|
// received by buyer and arbitrator
|
||||||
@ -609,7 +620,7 @@ public abstract class TradeProtocol implements DecryptedDirectMessageListener, D
|
|||||||
|
|
||||||
// save message for reprocessing
|
// save message for reprocessing
|
||||||
trade.getSeller().setPaymentReceivedMessage(message);
|
trade.getSeller().setPaymentReceivedMessage(message);
|
||||||
trade.requestPersistence();
|
trade.persistNow(() -> {
|
||||||
|
|
||||||
// process message on trade thread
|
// process message on trade thread
|
||||||
if (!trade.isInitialized() || trade.isShutDownStarted()) return;
|
if (!trade.isInitialized() || trade.isShutDownStarted()) return;
|
||||||
@ -662,6 +673,7 @@ public abstract class TradeProtocol implements DecryptedDirectMessageListener, D
|
|||||||
awaitTradeLatch();
|
awaitTradeLatch();
|
||||||
}
|
}
|
||||||
}, trade.getId());
|
}, trade.getId());
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public void onWithdrawCompleted() {
|
public void onWithdrawCompleted() {
|
||||||
@ -832,7 +844,7 @@ public abstract class TradeProtocol implements DecryptedDirectMessageListener, D
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected synchronized void stopTimeout() {
|
public synchronized void stopTimeout() {
|
||||||
synchronized (timeoutTimerLock) {
|
synchronized (timeoutTimerLock) {
|
||||||
if (timeoutTimer != null) {
|
if (timeoutTimer != null) {
|
||||||
timeoutTimer.stop();
|
timeoutTimer.stop();
|
||||||
|
@ -95,6 +95,18 @@ public class ArbitratorProcessDepositRequest extends TradeTask {
|
|||||||
// set peer's signature
|
// set peer's signature
|
||||||
sender.setContractSignature(signature);
|
sender.setContractSignature(signature);
|
||||||
|
|
||||||
|
// subscribe to trade state once to send responses with ack or nack
|
||||||
|
if (!hasBothContractSignatures()) {
|
||||||
|
trade.stateProperty().addListener((obs, oldState, newState) -> {
|
||||||
|
if (oldState == newState) return;
|
||||||
|
if (newState == Trade.State.PUBLISH_DEPOSIT_TX_REQUEST_FAILED) {
|
||||||
|
sendDepositResponsesOnce(trade.getProcessModel().error == null ? "Arbitrator failed to publish deposit txs within timeout for trade " + trade.getId() : trade.getProcessModel().error.getMessage());
|
||||||
|
} else if (newState.ordinal() >= Trade.State.ARBITRATOR_PUBLISHED_DEPOSIT_TXS.ordinal()) {
|
||||||
|
sendDepositResponsesOnce(null);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// collect expected values
|
// collect expected values
|
||||||
Offer offer = trade.getOffer();
|
Offer offer = trade.getOffer();
|
||||||
boolean isFromTaker = sender == trade.getTaker();
|
boolean isFromTaker = sender == trade.getTaker();
|
||||||
@ -138,7 +150,7 @@ public class ArbitratorProcessDepositRequest extends TradeTask {
|
|||||||
|
|
||||||
// relay deposit txs when both requests received
|
// relay deposit txs when both requests received
|
||||||
MoneroDaemon daemon = trade.getXmrWalletService().getDaemon();
|
MoneroDaemon daemon = trade.getXmrWalletService().getDaemon();
|
||||||
if (processModel.getMaker().getContractSignature() != null && processModel.getTaker().getContractSignature() != null) {
|
if (hasBothContractSignatures()) {
|
||||||
|
|
||||||
// check timeout and extend just before relaying
|
// check timeout and extend just before relaying
|
||||||
if (isTimedOut()) throw new RuntimeException("Trade protocol has timed out before relaying deposit txs for {} {}" + trade.getClass().getSimpleName() + " " + trade.getShortId());
|
if (isTimedOut()) throw new RuntimeException("Trade protocol has timed out before relaying deposit txs for {} {}" + trade.getClass().getSimpleName() + " " + trade.getShortId());
|
||||||
@ -182,22 +194,15 @@ public class ArbitratorProcessDepositRequest extends TradeTask {
|
|||||||
throw e;
|
throw e;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
|
||||||
// subscribe to trade state once to send responses with ack or nack
|
|
||||||
trade.stateProperty().addListener((obs, oldState, newState) -> {
|
|
||||||
if (oldState == newState) return;
|
|
||||||
if (newState == Trade.State.PUBLISH_DEPOSIT_TX_REQUEST_FAILED) {
|
|
||||||
sendDepositResponsesOnce(trade.getProcessModel().error == null ? "Arbitrator failed to publish deposit txs within timeout for trade " + trade.getId() : trade.getProcessModel().error.getMessage());
|
|
||||||
} else if (newState.ordinal() >= Trade.State.ARBITRATOR_PUBLISHED_DEPOSIT_TXS.ordinal()) {
|
|
||||||
sendDepositResponsesOnce(null);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
if (processModel.getMaker().getDepositTxHex() == null) log.info("Arbitrator waiting for deposit request from maker for trade " + trade.getId());
|
if (processModel.getMaker().getDepositTxHex() == null) log.info("Arbitrator waiting for deposit request from maker for trade " + trade.getId());
|
||||||
if (processModel.getTaker().getDepositTxHex() == null && !trade.hasBuyerAsTakerWithoutDeposit()) log.info("Arbitrator waiting for deposit request from taker for trade " + trade.getId());
|
if (processModel.getTaker().getDepositTxHex() == null && !trade.hasBuyerAsTakerWithoutDeposit()) log.info("Arbitrator waiting for deposit request from taker for trade " + trade.getId());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private boolean hasBothContractSignatures() {
|
||||||
|
return processModel.getMaker().getContractSignature() != null && processModel.getTaker().getContractSignature() != null;
|
||||||
|
}
|
||||||
|
|
||||||
private boolean isTimedOut() {
|
private boolean isTimedOut() {
|
||||||
return !processModel.getTradeManager().hasOpenTrade(trade);
|
return !processModel.getTradeManager().hasOpenTrade(trade);
|
||||||
}
|
}
|
||||||
@ -210,7 +215,7 @@ public class ArbitratorProcessDepositRequest extends TradeTask {
|
|||||||
|
|
||||||
// log error
|
// log error
|
||||||
if (errorMessage != null) {
|
if (errorMessage != null) {
|
||||||
log.warn("Sending deposit responses with error={}", errorMessage, new Throwable("Stack trace"));
|
log.warn("Sending deposit responses for tradeId={}, error={}", trade.getId(), errorMessage);
|
||||||
}
|
}
|
||||||
|
|
||||||
// create deposit response
|
// create deposit response
|
||||||
@ -229,7 +234,7 @@ public class ArbitratorProcessDepositRequest extends TradeTask {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void sendDepositResponse(NodeAddress nodeAddress, PubKeyRing pubKeyRing, DepositResponse response) {
|
private void sendDepositResponse(NodeAddress nodeAddress, PubKeyRing pubKeyRing, DepositResponse response) {
|
||||||
log.info("Sending deposit response to trader={}; offerId={}, error={}", nodeAddress, trade.getId(), trade.getProcessModel().error);
|
log.info("Sending deposit response to trader={}; offerId={}, error={}", nodeAddress, trade.getId(), response.getErrorMessage());
|
||||||
processModel.getP2PService().sendEncryptedDirectMessage(nodeAddress, pubKeyRing, response, new SendDirectMessageListener() {
|
processModel.getP2PService().sendEncryptedDirectMessage(nodeAddress, pubKeyRing, response, new SendDirectMessageListener() {
|
||||||
@Override
|
@Override
|
||||||
public void onArrived() {
|
public void onArrived() {
|
||||||
|
@ -38,13 +38,14 @@ public class ProcessDepositResponse extends TradeTask {
|
|||||||
try {
|
try {
|
||||||
runInterceptHook();
|
runInterceptHook();
|
||||||
|
|
||||||
// throw if error
|
// handle error
|
||||||
DepositResponse message = (DepositResponse) processModel.getTradeMessage();
|
DepositResponse message = (DepositResponse) processModel.getTradeMessage();
|
||||||
if (message.getErrorMessage() != null) {
|
if (message.getErrorMessage() != null) {
|
||||||
log.warn("Unregistering trade {} {} because deposit response has error message={}", trade.getClass().getSimpleName(), trade.getShortId(), message.getErrorMessage());
|
log.warn("Deposit response for {} {} has error message={}", trade.getClass().getSimpleName(), trade.getShortId(), message.getErrorMessage());
|
||||||
trade.setStateIfValidTransitionTo(Trade.State.PUBLISH_DEPOSIT_TX_REQUEST_FAILED);
|
trade.setStateIfValidTransitionTo(Trade.State.PUBLISH_DEPOSIT_TX_REQUEST_FAILED);
|
||||||
processModel.getTradeManager().unregisterTrade(trade);
|
trade.setInitError(new RuntimeException(message.getErrorMessage()));
|
||||||
throw new RuntimeException(message.getErrorMessage());
|
complete();
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// record security deposits
|
// record security deposits
|
||||||
|
@ -37,7 +37,6 @@ package haveno.core.xmr;
|
|||||||
import com.google.inject.Inject;
|
import com.google.inject.Inject;
|
||||||
|
|
||||||
import haveno.common.ThreadUtils;
|
import haveno.common.ThreadUtils;
|
||||||
import haveno.common.UserThread;
|
|
||||||
import haveno.core.api.model.XmrBalanceInfo;
|
import haveno.core.api.model.XmrBalanceInfo;
|
||||||
import haveno.core.offer.OpenOffer;
|
import haveno.core.offer.OpenOffer;
|
||||||
import haveno.core.offer.OpenOfferManager;
|
import haveno.core.offer.OpenOfferManager;
|
||||||
@ -163,18 +162,12 @@ public class Balances {
|
|||||||
// calculate reserved balance
|
// calculate reserved balance
|
||||||
reservedBalance = reservedOfferBalance.add(reservedTradeBalance);
|
reservedBalance = reservedOfferBalance.add(reservedTradeBalance);
|
||||||
|
|
||||||
// notify balance update
|
// play sound if funds received
|
||||||
UserThread.execute(() -> {
|
|
||||||
|
|
||||||
// check if funds received
|
|
||||||
boolean fundsReceived = balanceSumBefore != null && getNonTradeBalanceSum().compareTo(balanceSumBefore) > 0;
|
boolean fundsReceived = balanceSumBefore != null && getNonTradeBalanceSum().compareTo(balanceSumBefore) > 0;
|
||||||
if (fundsReceived) {
|
if (fundsReceived) HavenoUtils.playCashRegisterSound();
|
||||||
HavenoUtils.playCashRegisterSound();
|
|
||||||
}
|
|
||||||
|
|
||||||
// increase counter to notify listeners
|
// notify balance update
|
||||||
updateCounter.set(updateCounter.get() + 1);
|
updateCounter.set(updateCounter.get() + 1);
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -184,10 +184,6 @@ public class XmrNodes {
|
|||||||
this.operator = operator;
|
this.operator = operator;
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean hasOnionAddress() {
|
|
||||||
return onionAddress != null;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getHostNameOrAddress() {
|
public String getHostNameOrAddress() {
|
||||||
if (hostName != null)
|
if (hostName != null)
|
||||||
return hostName;
|
return hostName;
|
||||||
@ -195,10 +191,19 @@ public class XmrNodes {
|
|||||||
return address;
|
return address;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean hasOnionAddress() {
|
||||||
|
return onionAddress != null;
|
||||||
|
}
|
||||||
|
|
||||||
public boolean hasClearNetAddress() {
|
public boolean hasClearNetAddress() {
|
||||||
return hostName != null || address != null;
|
return hostName != null || address != null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String getClearNetUri() {
|
||||||
|
if (!hasClearNetAddress()) throw new IllegalStateException("XmrNode does not have clearnet address");
|
||||||
|
return "http://" + getHostNameOrAddress() + ":" + port;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
return "onionAddress='" + onionAddress + '\'' +
|
return "onionAddress='" + onionAddress + '\'' +
|
||||||
|
@ -159,8 +159,13 @@ public class XmrKeyImagePoller {
|
|||||||
if (keyImagesGroup == null) return;
|
if (keyImagesGroup == null) return;
|
||||||
keyImagesGroup.removeAll(keyImages);
|
keyImagesGroup.removeAll(keyImages);
|
||||||
if (keyImagesGroup.isEmpty()) keyImageGroups.remove(groupId);
|
if (keyImagesGroup.isEmpty()) keyImageGroups.remove(groupId);
|
||||||
|
Set<String> allKeyImages = getKeyImages();
|
||||||
synchronized (lastStatuses) {
|
synchronized (lastStatuses) {
|
||||||
for (String lastKeyImage : new HashSet<>(lastStatuses.keySet())) lastStatuses.remove(lastKeyImage);
|
for (String keyImage : keyImages) {
|
||||||
|
if (lastStatuses.containsKey(keyImage) && !allKeyImages.contains(keyImage)) {
|
||||||
|
lastStatuses.remove(keyImage);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
refreshPolling();
|
refreshPolling();
|
||||||
}
|
}
|
||||||
@ -171,10 +176,10 @@ public class XmrKeyImagePoller {
|
|||||||
Set<String> keyImagesGroup = keyImageGroups.get(groupId);
|
Set<String> keyImagesGroup = keyImageGroups.get(groupId);
|
||||||
if (keyImagesGroup == null) return;
|
if (keyImagesGroup == null) return;
|
||||||
keyImageGroups.remove(groupId);
|
keyImageGroups.remove(groupId);
|
||||||
Set<String> keyImages = getKeyImages();
|
Set<String> allKeyImages = getKeyImages();
|
||||||
synchronized (lastStatuses) {
|
synchronized (lastStatuses) {
|
||||||
for (String keyImage : keyImagesGroup) {
|
for (String keyImage : keyImagesGroup) {
|
||||||
if (lastStatuses.containsKey(keyImage) && !keyImages.contains(keyImage)) {
|
if (lastStatuses.containsKey(keyImage) && !allKeyImages.contains(keyImage)) {
|
||||||
lastStatuses.remove(keyImage);
|
lastStatuses.remove(keyImage);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -105,7 +105,6 @@ public abstract class XmrWalletBase {
|
|||||||
// start polling wallet for progress
|
// start polling wallet for progress
|
||||||
syncProgressLatch = new CountDownLatch(1);
|
syncProgressLatch = new CountDownLatch(1);
|
||||||
syncProgressLooper = new TaskLooper(() -> {
|
syncProgressLooper = new TaskLooper(() -> {
|
||||||
if (wallet == null) return;
|
|
||||||
long height;
|
long height;
|
||||||
try {
|
try {
|
||||||
height = wallet.getHeight(); // can get read timeout while syncing
|
height = wallet.getHeight(); // can get read timeout while syncing
|
||||||
|
@ -237,6 +237,7 @@ shared.pending=Pending
|
|||||||
shared.me=Me
|
shared.me=Me
|
||||||
shared.maker=Maker
|
shared.maker=Maker
|
||||||
shared.taker=Taker
|
shared.taker=Taker
|
||||||
|
shared.none=None
|
||||||
|
|
||||||
|
|
||||||
####################################################################
|
####################################################################
|
||||||
@ -2090,7 +2091,8 @@ closedTradesSummaryWindow.totalTradeFeeInXmr.value={0} ({1} of total trade amoun
|
|||||||
walletPasswordWindow.headline=Enter password to unlock
|
walletPasswordWindow.headline=Enter password to unlock
|
||||||
|
|
||||||
xmrConnectionError.headline=Monero connection error
|
xmrConnectionError.headline=Monero connection error
|
||||||
xmrConnectionError.customNode=Error connecting to your custom Monero node(s).\n\nDo you want to use the next best available Monero node?
|
xmrConnectionError.providedNodes=Error connecting to provided Monero node(s).\n\nDo you want to use the next best available Monero node?
|
||||||
|
xmrConnectionError.customNodes=Error connecting to your custom Monero node(s).\n\nDo you want to use the next best available Monero node?
|
||||||
xmrConnectionError.localNode=We previously synced using a local Monero node, but it appears to be unreachable.\n\nPlease check that it's running and synced.
|
xmrConnectionError.localNode=We previously synced using a local Monero node, but it appears to be unreachable.\n\nPlease check that it's running and synced.
|
||||||
xmrConnectionError.localNode.start=Start local node
|
xmrConnectionError.localNode.start=Start local node
|
||||||
xmrConnectionError.localNode.start.error=Error starting local node
|
xmrConnectionError.localNode.start.error=Error starting local node
|
||||||
|
@ -60,6 +60,6 @@
|
|||||||
</content_rating>
|
</content_rating>
|
||||||
|
|
||||||
<releases>
|
<releases>
|
||||||
<release version="1.0.19" date="2025-03-10"/>
|
<release version="1.1.0" date="2025-04-17"/>
|
||||||
</releases>
|
</releases>
|
||||||
</component>
|
</component>
|
||||||
|
@ -5,10 +5,10 @@
|
|||||||
<!-- See: https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html -->
|
<!-- See: https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html -->
|
||||||
|
|
||||||
<key>CFBundleVersion</key>
|
<key>CFBundleVersion</key>
|
||||||
<string>1.0.19</string>
|
<string>1.1.0</string>
|
||||||
|
|
||||||
<key>CFBundleShortVersionString</key>
|
<key>CFBundleShortVersionString</key>
|
||||||
<string>1.0.19</string>
|
<string>1.1.0</string>
|
||||||
|
|
||||||
<key>CFBundleExecutable</key>
|
<key>CFBundleExecutable</key>
|
||||||
<string>Haveno</string>
|
<string>Haveno</string>
|
||||||
|
@ -353,7 +353,7 @@ public class MainView extends InitializableView<StackPane, MainViewModel> {
|
|||||||
settingsButtonWithBadge.getStyleClass().add("new");
|
settingsButtonWithBadge.getStyleClass().add("new");
|
||||||
|
|
||||||
navigation.addListener((viewPath, data) -> {
|
navigation.addListener((viewPath, data) -> {
|
||||||
UserThread.await(() -> {
|
UserThread.await(() -> { // TODO: this uses `await` to fix nagivation link from market view to offer book, but await can cause hanging, so execute should be used
|
||||||
if (viewPath.size() != 2 || viewPath.indexOf(MainView.class) != 0) return;
|
if (viewPath.size() != 2 || viewPath.indexOf(MainView.class) != 0) return;
|
||||||
|
|
||||||
Class<? extends View> viewClass = viewPath.tip();
|
Class<? extends View> viewClass = viewPath.tip();
|
||||||
|
@ -337,7 +337,7 @@ public class MainViewModel implements ViewModel, HavenoSetup.HavenoSetupListener
|
|||||||
tacWindow.onAction(acceptedHandler::run).show();
|
tacWindow.onAction(acceptedHandler::run).show();
|
||||||
}, 1));
|
}, 1));
|
||||||
|
|
||||||
havenoSetup.setDisplayMoneroConnectionErrorHandler(connectionError -> {
|
havenoSetup.setDisplayMoneroConnectionFallbackHandler(connectionError -> {
|
||||||
if (connectionError == null) {
|
if (connectionError == null) {
|
||||||
if (moneroConnectionErrorPopup != null) moneroConnectionErrorPopup.hide();
|
if (moneroConnectionErrorPopup != null) moneroConnectionErrorPopup.hide();
|
||||||
} else {
|
} else {
|
||||||
@ -349,7 +349,6 @@ public class MainViewModel implements ViewModel, HavenoSetup.HavenoSetupListener
|
|||||||
.actionButtonText(Res.get("xmrConnectionError.localNode.start"))
|
.actionButtonText(Res.get("xmrConnectionError.localNode.start"))
|
||||||
.onAction(() -> {
|
.onAction(() -> {
|
||||||
log.warn("User has chosen to start local node.");
|
log.warn("User has chosen to start local node.");
|
||||||
havenoSetup.getConnectionServiceError().set(null);
|
|
||||||
new Thread(() -> {
|
new Thread(() -> {
|
||||||
try {
|
try {
|
||||||
HavenoUtils.xmrConnectionService.startLocalNode();
|
HavenoUtils.xmrConnectionService.startLocalNode();
|
||||||
@ -359,16 +358,20 @@ public class MainViewModel implements ViewModel, HavenoSetup.HavenoSetupListener
|
|||||||
.headLine(Res.get("xmrConnectionError.localNode.start.error"))
|
.headLine(Res.get("xmrConnectionError.localNode.start.error"))
|
||||||
.warning(e.getMessage())
|
.warning(e.getMessage())
|
||||||
.closeButtonText(Res.get("shared.close"))
|
.closeButtonText(Res.get("shared.close"))
|
||||||
.onClose(() -> havenoSetup.getConnectionServiceError().set(null))
|
.onClose(() -> havenoSetup.getConnectionServiceFallbackType().set(null))
|
||||||
.show();
|
.show();
|
||||||
|
} finally {
|
||||||
|
havenoSetup.getConnectionServiceFallbackType().set(null);
|
||||||
}
|
}
|
||||||
}).start();
|
}).start();
|
||||||
})
|
})
|
||||||
.secondaryActionButtonText(Res.get("xmrConnectionError.localNode.fallback"))
|
.secondaryActionButtonText(Res.get("xmrConnectionError.localNode.fallback"))
|
||||||
.onSecondaryAction(() -> {
|
.onSecondaryAction(() -> {
|
||||||
log.warn("User has chosen to fallback to the next best available Monero node.");
|
log.warn("User has chosen to fallback to the next best available Monero node.");
|
||||||
havenoSetup.getConnectionServiceError().set(null);
|
new Thread(() -> {
|
||||||
new Thread(() -> HavenoUtils.xmrConnectionService.fallbackToBestConnection()).start();
|
HavenoUtils.xmrConnectionService.fallbackToBestConnection();
|
||||||
|
havenoSetup.getConnectionServiceFallbackType().set(null);
|
||||||
|
}).start();
|
||||||
})
|
})
|
||||||
.closeButtonText(Res.get("shared.shutDown"))
|
.closeButtonText(Res.get("shared.shutDown"))
|
||||||
.onClose(HavenoApp.getShutDownHandler());
|
.onClose(HavenoApp.getShutDownHandler());
|
||||||
@ -376,16 +379,35 @@ public class MainViewModel implements ViewModel, HavenoSetup.HavenoSetupListener
|
|||||||
case CUSTOM:
|
case CUSTOM:
|
||||||
moneroConnectionErrorPopup = new Popup()
|
moneroConnectionErrorPopup = new Popup()
|
||||||
.headLine(Res.get("xmrConnectionError.headline"))
|
.headLine(Res.get("xmrConnectionError.headline"))
|
||||||
.warning(Res.get("xmrConnectionError.customNode"))
|
.warning(Res.get("xmrConnectionError.customNodes"))
|
||||||
.actionButtonText(Res.get("shared.yes"))
|
.actionButtonText(Res.get("shared.yes"))
|
||||||
.onAction(() -> {
|
.onAction(() -> {
|
||||||
havenoSetup.getConnectionServiceError().set(null);
|
new Thread(() -> {
|
||||||
new Thread(() -> HavenoUtils.xmrConnectionService.fallbackToBestConnection()).start();
|
HavenoUtils.xmrConnectionService.fallbackToBestConnection();
|
||||||
|
havenoSetup.getConnectionServiceFallbackType().set(null);
|
||||||
|
}).start();
|
||||||
})
|
})
|
||||||
.closeButtonText(Res.get("shared.no"))
|
.closeButtonText(Res.get("shared.no"))
|
||||||
.onClose(() -> {
|
.onClose(() -> {
|
||||||
log.warn("User has declined to fallback to the next best available Monero node.");
|
log.warn("User has declined to fallback to the next best available Monero node.");
|
||||||
havenoSetup.getConnectionServiceError().set(null);
|
havenoSetup.getConnectionServiceFallbackType().set(null);
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
case PROVIDED:
|
||||||
|
moneroConnectionErrorPopup = new Popup()
|
||||||
|
.headLine(Res.get("xmrConnectionError.headline"))
|
||||||
|
.warning(Res.get("xmrConnectionError.providedNodes"))
|
||||||
|
.actionButtonText(Res.get("shared.yes"))
|
||||||
|
.onAction(() -> {
|
||||||
|
new Thread(() -> {
|
||||||
|
HavenoUtils.xmrConnectionService.fallbackToBestConnection();
|
||||||
|
havenoSetup.getConnectionServiceFallbackType().set(null);
|
||||||
|
}).start();
|
||||||
|
})
|
||||||
|
.closeButtonText(Res.get("shared.no"))
|
||||||
|
.onClose(() -> {
|
||||||
|
log.warn("User has declined to fallback to the next best available Monero node.");
|
||||||
|
havenoSetup.getConnectionServiceFallbackType().set(null);
|
||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -380,8 +380,6 @@ public abstract class MutableOfferView<M extends MutableOfferViewModel<?>> exten
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void onShowPayFundsScreen() {
|
private void onShowPayFundsScreen() {
|
||||||
scrollPane.setVbarPolicy(ScrollPane.ScrollBarPolicy.AS_NEEDED);
|
|
||||||
|
|
||||||
nextButton.setVisible(false);
|
nextButton.setVisible(false);
|
||||||
nextButton.setManaged(false);
|
nextButton.setManaged(false);
|
||||||
nextButton.setOnAction(null);
|
nextButton.setOnAction(null);
|
||||||
@ -445,13 +443,7 @@ public abstract class MutableOfferView<M extends MutableOfferViewModel<?>> exten
|
|||||||
// temporarily disabled due to high CPU usage (per issue #4649)
|
// temporarily disabled due to high CPU usage (per issue #4649)
|
||||||
// waitingForFundsSpinner.play();
|
// waitingForFundsSpinner.play();
|
||||||
|
|
||||||
payFundsTitledGroupBg.setVisible(true);
|
showFundingGroup();
|
||||||
totalToPayTextField.setVisible(true);
|
|
||||||
addressTextField.setVisible(true);
|
|
||||||
qrCodeImageView.setVisible(true);
|
|
||||||
balanceTextField.setVisible(true);
|
|
||||||
cancelButton2.setVisible(true);
|
|
||||||
reserveExactAmountSlider.setVisible(true);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void updateOfferElementsStyle() {
|
private void updateOfferElementsStyle() {
|
||||||
@ -986,6 +978,7 @@ public abstract class MutableOfferView<M extends MutableOfferViewModel<?>> exten
|
|||||||
gridPane.setVgap(5);
|
gridPane.setVgap(5);
|
||||||
GUIUtil.setDefaultTwoColumnConstraintsForGridPane(gridPane);
|
GUIUtil.setDefaultTwoColumnConstraintsForGridPane(gridPane);
|
||||||
scrollPane.setContent(gridPane);
|
scrollPane.setContent(gridPane);
|
||||||
|
scrollPane.setVbarPolicy(ScrollPane.ScrollBarPolicy.AS_NEEDED);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void addPaymentGroup() {
|
private void addPaymentGroup() {
|
||||||
@ -1179,6 +1172,40 @@ public abstract class MutableOfferView<M extends MutableOfferViewModel<?>> exten
|
|||||||
cancelButton1.setManaged(false);
|
cancelButton1.setManaged(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected void hideFundingGroup() {
|
||||||
|
payFundsTitledGroupBg.setVisible(false);
|
||||||
|
payFundsTitledGroupBg.setManaged(false);
|
||||||
|
totalToPayTextField.setVisible(false);
|
||||||
|
totalToPayTextField.setManaged(false);
|
||||||
|
addressTextField.setVisible(false);
|
||||||
|
addressTextField.setManaged(false);
|
||||||
|
qrCodeImageView.setVisible(false);
|
||||||
|
qrCodeImageView.setManaged(false);
|
||||||
|
balanceTextField.setVisible(false);
|
||||||
|
balanceTextField.setManaged(false);
|
||||||
|
cancelButton2.setVisible(false);
|
||||||
|
cancelButton2.setManaged(false);
|
||||||
|
reserveExactAmountSlider.setVisible(false);
|
||||||
|
reserveExactAmountSlider.setManaged(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void showFundingGroup() {
|
||||||
|
payFundsTitledGroupBg.setVisible(true);
|
||||||
|
payFundsTitledGroupBg.setManaged(true);
|
||||||
|
totalToPayTextField.setVisible(true);
|
||||||
|
totalToPayTextField.setManaged(true);
|
||||||
|
addressTextField.setVisible(true);
|
||||||
|
addressTextField.setManaged(true);
|
||||||
|
qrCodeImageView.setVisible(true);
|
||||||
|
qrCodeImageView.setManaged(true);
|
||||||
|
balanceTextField.setVisible(true);
|
||||||
|
balanceTextField.setManaged(true);
|
||||||
|
cancelButton2.setVisible(true);
|
||||||
|
cancelButton2.setManaged(true);
|
||||||
|
reserveExactAmountSlider.setVisible(true);
|
||||||
|
reserveExactAmountSlider.setManaged(true);
|
||||||
|
}
|
||||||
|
|
||||||
private VBox getSecurityDepositBox() {
|
private VBox getSecurityDepositBox() {
|
||||||
Tuple3<HBox, InfoInputTextField, Label> tuple = getEditableValueBoxWithInfo(
|
Tuple3<HBox, InfoInputTextField, Label> tuple = getEditableValueBoxWithInfo(
|
||||||
Res.get("createOffer.securityDeposit.prompt"));
|
Res.get("createOffer.securityDeposit.prompt"));
|
||||||
@ -1326,6 +1353,8 @@ public abstract class MutableOfferView<M extends MutableOfferViewModel<?>> exten
|
|||||||
});
|
});
|
||||||
cancelButton2.setDefaultButton(false);
|
cancelButton2.setDefaultButton(false);
|
||||||
cancelButton2.setVisible(false);
|
cancelButton2.setVisible(false);
|
||||||
|
|
||||||
|
hideFundingGroup();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void openWallet() {
|
private void openWallet() {
|
||||||
|
@ -19,6 +19,8 @@ package haveno.desktop.main.offer;
|
|||||||
|
|
||||||
import com.google.inject.Inject;
|
import com.google.inject.Inject;
|
||||||
import com.google.inject.name.Named;
|
import com.google.inject.name.Named;
|
||||||
|
|
||||||
|
import haveno.common.ThreadUtils;
|
||||||
import haveno.common.UserThread;
|
import haveno.common.UserThread;
|
||||||
import haveno.common.app.DevEnv;
|
import haveno.common.app.DevEnv;
|
||||||
import haveno.common.handlers.ErrorMessageHandler;
|
import haveno.common.handlers.ErrorMessageHandler;
|
||||||
@ -108,7 +110,7 @@ public abstract class MutableOfferViewModel<M extends MutableOfferDataModel> ext
|
|||||||
private String amountDescription;
|
private String amountDescription;
|
||||||
private String addressAsString;
|
private String addressAsString;
|
||||||
private final String paymentLabel;
|
private final String paymentLabel;
|
||||||
private boolean createOfferRequested;
|
private boolean createOfferInProgress;
|
||||||
public boolean createOfferCanceled;
|
public boolean createOfferCanceled;
|
||||||
|
|
||||||
public final StringProperty amount = new SimpleStringProperty();
|
public final StringProperty amount = new SimpleStringProperty();
|
||||||
@ -638,16 +640,18 @@ public abstract class MutableOfferViewModel<M extends MutableOfferDataModel> ext
|
|||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
void onPlaceOffer(Offer offer, Runnable resultHandler) {
|
void onPlaceOffer(Offer offer, Runnable resultHandler) {
|
||||||
|
ThreadUtils.execute(() -> {
|
||||||
errorMessage.set(null);
|
errorMessage.set(null);
|
||||||
createOfferRequested = true;
|
createOfferInProgress = true;
|
||||||
createOfferCanceled = false;
|
createOfferCanceled = false;
|
||||||
|
|
||||||
dataModel.onPlaceOffer(offer, transaction -> {
|
dataModel.onPlaceOffer(offer, transaction -> {
|
||||||
|
createOfferInProgress = false;
|
||||||
resultHandler.run();
|
resultHandler.run();
|
||||||
if (!createOfferCanceled) placeOfferCompleted.set(true);
|
if (!createOfferCanceled) placeOfferCompleted.set(true);
|
||||||
errorMessage.set(null);
|
errorMessage.set(null);
|
||||||
}, errMessage -> {
|
}, errMessage -> {
|
||||||
createOfferRequested = false;
|
createOfferInProgress = false;
|
||||||
if (offer.getState() == Offer.State.OFFER_FEE_RESERVED) errorMessage.set(errMessage + Res.get("createOffer.errorInfo"));
|
if (offer.getState() == Offer.State.OFFER_FEE_RESERVED) errorMessage.set(errMessage + Res.get("createOffer.errorInfo"));
|
||||||
else errorMessage.set(errMessage);
|
else errorMessage.set(errMessage);
|
||||||
|
|
||||||
@ -658,12 +662,15 @@ public abstract class MutableOfferViewModel<M extends MutableOfferDataModel> ext
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
UserThread.execute(() -> {
|
||||||
updateButtonDisableState();
|
updateButtonDisableState();
|
||||||
updateSpinnerInfo();
|
updateSpinnerInfo();
|
||||||
|
});
|
||||||
|
}, getClass().getSimpleName());
|
||||||
}
|
}
|
||||||
|
|
||||||
public void onCancelOffer(ResultHandler resultHandler, ErrorMessageHandler errorMessageHandler) {
|
public void onCancelOffer(ResultHandler resultHandler, ErrorMessageHandler errorMessageHandler) {
|
||||||
createOfferRequested = false;
|
log.info("Canceling posting offer {}", offer.getId());
|
||||||
createOfferCanceled = true;
|
createOfferCanceled = true;
|
||||||
OpenOfferManager openOfferManager = HavenoUtils.openOfferManager;
|
OpenOfferManager openOfferManager = HavenoUtils.openOfferManager;
|
||||||
Optional<OpenOffer> openOffer = openOfferManager.getOpenOffer(offer.getId());
|
Optional<OpenOffer> openOffer = openOfferManager.getOpenOffer(offer.getId());
|
||||||
@ -1355,7 +1362,7 @@ public abstract class MutableOfferViewModel<M extends MutableOfferDataModel> ext
|
|||||||
inputDataValid = inputDataValid && getExtraInfoValidationResult().isValid;
|
inputDataValid = inputDataValid && getExtraInfoValidationResult().isValid;
|
||||||
|
|
||||||
isNextButtonDisabled.set(!inputDataValid);
|
isNextButtonDisabled.set(!inputDataValid);
|
||||||
isPlaceOfferButtonDisabled.set(createOfferRequested || !inputDataValid || !dataModel.getIsXmrWalletFunded().get());
|
isPlaceOfferButtonDisabled.set(createOfferInProgress || !inputDataValid || !dataModel.getIsXmrWalletFunded().get());
|
||||||
}
|
}
|
||||||
|
|
||||||
private ValidationResult getExtraInfoValidationResult() {
|
private ValidationResult getExtraInfoValidationResult() {
|
||||||
|
@ -174,9 +174,11 @@ abstract class OfferBookViewModel extends ActivatableViewModel {
|
|||||||
tradeCurrencyListChangeListener = c -> fillCurrencies();
|
tradeCurrencyListChangeListener = c -> fillCurrencies();
|
||||||
|
|
||||||
// refresh filter on changes
|
// refresh filter on changes
|
||||||
offerBook.getOfferBookListItems().addListener((ListChangeListener<OfferBookListItem>) c -> {
|
// TODO: This is removed because it's expensive to re-filter offers for every change (high cpu for many offers).
|
||||||
filterOffers();
|
// This was used to ensure offer list is fully refreshed, but is unnecessary after refactoring OfferBookService to clone offers?
|
||||||
});
|
// offerBook.getOfferBookListItems().addListener((ListChangeListener<OfferBookListItem>) c -> {
|
||||||
|
// filterOffers();
|
||||||
|
// });
|
||||||
|
|
||||||
filterItemsListener = c -> {
|
filterItemsListener = c -> {
|
||||||
final Optional<OfferBookListItem> highestAmountOffer = filteredItems.stream()
|
final Optional<OfferBookListItem> highestAmountOffer = filteredItems.stream()
|
||||||
|
@ -649,7 +649,7 @@ public class TakeOfferView extends ActivatableViewAndModel<AnchorPane, TakeOffer
|
|||||||
|
|
||||||
errorMessageSubscription = EasyBind.subscribe(model.errorMessage, newValue -> {
|
errorMessageSubscription = EasyBind.subscribe(model.errorMessage, newValue -> {
|
||||||
if (newValue != null) {
|
if (newValue != null) {
|
||||||
new Popup().warning(Res.get("takeOffer.error.message", model.errorMessage.get()))
|
new Popup().error(Res.get("takeOffer.error.message", model.errorMessage.get()))
|
||||||
.onClose(() -> {
|
.onClose(() -> {
|
||||||
errorPopupDisplayed.set(true);
|
errorPopupDisplayed.set(true);
|
||||||
model.resetErrorMessage();
|
model.resetErrorMessage();
|
||||||
|
@ -91,7 +91,7 @@
|
|||||||
<AutoTooltipButton fx:id="rescanOutputsButton"/>
|
<AutoTooltipButton fx:id="rescanOutputsButton"/>
|
||||||
</VBox>
|
</VBox>
|
||||||
|
|
||||||
<TitledGroupBg fx:id="p2pHeader" GridPane.rowIndex="5" GridPane.rowSpan="5">
|
<TitledGroupBg fx:id="p2pHeader" GridPane.rowIndex="5" GridPane.rowSpan="6">
|
||||||
<padding>
|
<padding>
|
||||||
<Insets top="50.0"/>
|
<Insets top="50.0"/>
|
||||||
</padding>
|
</padding>
|
||||||
@ -159,7 +159,10 @@
|
|||||||
<HavenoTextField fx:id="chainHeightTextField" GridPane.rowIndex="9" editable="false"
|
<HavenoTextField fx:id="chainHeightTextField" GridPane.rowIndex="9" editable="false"
|
||||||
focusTraversable="false" labelFloat="true"/>
|
focusTraversable="false" labelFloat="true"/>
|
||||||
|
|
||||||
<AutoTooltipButton fx:id="openTorSettingsButton" GridPane.rowIndex="10" GridPane.columnIndex="0"/>
|
<HavenoTextField fx:id="minVersionForTrading" GridPane.rowIndex="10" editable="false"
|
||||||
|
focusTraversable="false" labelFloat="true"/>
|
||||||
|
|
||||||
|
<AutoTooltipButton fx:id="openTorSettingsButton" GridPane.rowIndex="11" GridPane.columnIndex="0"/>
|
||||||
|
|
||||||
<columnConstraints>
|
<columnConstraints>
|
||||||
<ColumnConstraints hgrow="ALWAYS" minWidth="500"/>
|
<ColumnConstraints hgrow="ALWAYS" minWidth="500"/>
|
||||||
|
@ -75,7 +75,7 @@ public class NetworkSettingsView extends ActivatableView<GridPane, Void> {
|
|||||||
@FXML
|
@FXML
|
||||||
InputTextField xmrNodesInputTextField;
|
InputTextField xmrNodesInputTextField;
|
||||||
@FXML
|
@FXML
|
||||||
TextField onionAddress, sentDataTextField, receivedDataTextField, chainHeightTextField;
|
TextField onionAddress, sentDataTextField, receivedDataTextField, chainHeightTextField, minVersionForTrading;
|
||||||
@FXML
|
@FXML
|
||||||
Label p2PPeersLabel, moneroConnectionsLabel;
|
Label p2PPeersLabel, moneroConnectionsLabel;
|
||||||
@FXML
|
@FXML
|
||||||
@ -176,6 +176,7 @@ public class NetworkSettingsView extends ActivatableView<GridPane, Void> {
|
|||||||
sentDataTextField.setPromptText(Res.get("settings.net.sentDataLabel"));
|
sentDataTextField.setPromptText(Res.get("settings.net.sentDataLabel"));
|
||||||
receivedDataTextField.setPromptText(Res.get("settings.net.receivedDataLabel"));
|
receivedDataTextField.setPromptText(Res.get("settings.net.receivedDataLabel"));
|
||||||
chainHeightTextField.setPromptText(Res.get("settings.net.chainHeightLabel"));
|
chainHeightTextField.setPromptText(Res.get("settings.net.chainHeightLabel"));
|
||||||
|
minVersionForTrading.setPromptText(Res.get("filterWindow.disableTradeBelowVersion"));
|
||||||
roundTripTimeColumn.setGraphic(new AutoTooltipLabel(Res.get("settings.net.roundTripTimeColumn")));
|
roundTripTimeColumn.setGraphic(new AutoTooltipLabel(Res.get("settings.net.roundTripTimeColumn")));
|
||||||
sentBytesColumn.setGraphic(new AutoTooltipLabel(Res.get("settings.net.sentBytesColumn")));
|
sentBytesColumn.setGraphic(new AutoTooltipLabel(Res.get("settings.net.sentBytesColumn")));
|
||||||
receivedBytesColumn.setGraphic(new AutoTooltipLabel(Res.get("settings.net.receivedBytesColumn")));
|
receivedBytesColumn.setGraphic(new AutoTooltipLabel(Res.get("settings.net.receivedBytesColumn")));
|
||||||
@ -275,7 +276,7 @@ public class NetworkSettingsView extends ActivatableView<GridPane, Void> {
|
|||||||
showShutDownPopup();
|
showShutDownPopup();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
filterPropertyListener = (observable, oldValue, newValue) -> applyPreventPublicXmrNetwork();
|
filterPropertyListener = (observable, oldValue, newValue) -> applyFilter();
|
||||||
|
|
||||||
// disable radio buttons if no nodes available
|
// disable radio buttons if no nodes available
|
||||||
if (xmrNodes.getProvidedXmrNodes().isEmpty()) {
|
if (xmrNodes.getProvidedXmrNodes().isEmpty()) {
|
||||||
@ -298,7 +299,7 @@ public class NetworkSettingsView extends ActivatableView<GridPane, Void> {
|
|||||||
moneroPeersToggleGroup.selectedToggleProperty().addListener(moneroPeersToggleGroupListener);
|
moneroPeersToggleGroup.selectedToggleProperty().addListener(moneroPeersToggleGroupListener);
|
||||||
|
|
||||||
if (filterManager.getFilter() != null)
|
if (filterManager.getFilter() != null)
|
||||||
applyPreventPublicXmrNetwork();
|
applyFilter();
|
||||||
|
|
||||||
filterManager.filterProperty().addListener(filterPropertyListener);
|
filterManager.filterProperty().addListener(filterPropertyListener);
|
||||||
|
|
||||||
@ -492,7 +493,9 @@ public class NetworkSettingsView extends ActivatableView<GridPane, Void> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private void applyPreventPublicXmrNetwork() {
|
private void applyFilter() {
|
||||||
|
|
||||||
|
// prevent public xmr network
|
||||||
final boolean preventPublicXmrNetwork = isPreventPublicXmrNetwork();
|
final boolean preventPublicXmrNetwork = isPreventPublicXmrNetwork();
|
||||||
usePublicNodesRadio.setDisable(isPublicNodesDisabled());
|
usePublicNodesRadio.setDisable(isPublicNodesDisabled());
|
||||||
if (preventPublicXmrNetwork && selectedMoneroNodesOption == XmrNodes.MoneroNodesOption.PUBLIC) {
|
if (preventPublicXmrNetwork && selectedMoneroNodesOption == XmrNodes.MoneroNodesOption.PUBLIC) {
|
||||||
@ -501,6 +504,10 @@ public class NetworkSettingsView extends ActivatableView<GridPane, Void> {
|
|||||||
selectMoneroPeersToggle();
|
selectMoneroPeersToggle();
|
||||||
onMoneroPeersToggleSelected(false);
|
onMoneroPeersToggleSelected(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// set min version for trading
|
||||||
|
String minVersion = filterManager.getDisableTradeBelowVersion();
|
||||||
|
minVersionForTrading.textProperty().setValue(minVersion == null ? Res.get("shared.none") : minVersion);
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean isPublicNodesDisabled() {
|
private boolean isPublicNodesDisabled() {
|
||||||
|
@ -5,10 +5,11 @@ This guide describes how to deploy a Haveno network:
|
|||||||
- Manage services on a VPS
|
- Manage services on a VPS
|
||||||
- Fork and build Haveno
|
- Fork and build Haveno
|
||||||
- Start a Monero node
|
- Start a Monero node
|
||||||
- Build and start price nodes
|
|
||||||
- Add seed nodes
|
- Add seed nodes
|
||||||
- Add arbitrators
|
- Add arbitrators
|
||||||
- Configure trade fees and other configuration
|
- Configure trade fees and other configuration
|
||||||
|
- Build and start price nodes
|
||||||
|
- Set a network filter
|
||||||
- Build Haveno installers for distribution
|
- Build Haveno installers for distribution
|
||||||
- Send alerts to update the application and other maintenance
|
- Send alerts to update the application and other maintenance
|
||||||
|
|
||||||
@ -69,14 +70,6 @@ Optionally customize and deploy monero-stagenet.service and monero-stagenet.conf
|
|||||||
|
|
||||||
You can also start the Monero node in your current terminal session by running `make monerod` for mainnet or `make monerod-stagenet` for stagenet.
|
You can also start the Monero node in your current terminal session by running `make monerod` for mainnet or `make monerod-stagenet` for stagenet.
|
||||||
|
|
||||||
## Build and start price nodes
|
|
||||||
|
|
||||||
The price node is separated from Haveno and is run as a standalone service. To deploy a pricenode on both TOR and clearnet, see the instructions on the repository: https://github.com/haveno-dex/haveno-pricenode.
|
|
||||||
|
|
||||||
After the price node is built and deployed, add the price node to `DEFAULT_NODES` in [ProvidersRepository.java](https://github.com/haveno-dex/haveno/blob/3cdd88b56915c7f8afd4f1a39e6c1197c2665d63/core/src/main/java/haveno/core/provider/ProvidersRepository.java#L50).
|
|
||||||
|
|
||||||
Customize and deploy haveno-pricenode.env and haveno-pricenode.service to run as a system service.
|
|
||||||
|
|
||||||
## Add seed nodes
|
## Add seed nodes
|
||||||
|
|
||||||
### Seed nodes without Proof of Work (PoW)
|
### Seed nodes without Proof of Work (PoW)
|
||||||
@ -139,7 +132,7 @@ Each seed node requires a locally running Monero node. You can use the default p
|
|||||||
|
|
||||||
Rebuild all seed nodes any time the list of registered seed nodes changes.
|
Rebuild all seed nodes any time the list of registered seed nodes changes.
|
||||||
|
|
||||||
> **Notes**
|
> [!note]
|
||||||
> * Avoid all seed nodes going offline at the same time. If all seed nodes go offline at the same time, the network will be reset, including registered arbitrators, the network filter object, and trade history. In that case, arbitrators need to restart or re-register, and the network filter object needs to be re-applied. This should be done immediately or clients will cancel their offers due to the signing arbitrators being unregistered and no replacements being available to re-sign.
|
> * Avoid all seed nodes going offline at the same time. If all seed nodes go offline at the same time, the network will be reset, including registered arbitrators, the network filter object, and trade history. In that case, arbitrators need to restart or re-register, and the network filter object needs to be re-applied. This should be done immediately or clients will cancel their offers due to the signing arbitrators being unregistered and no replacements being available to re-sign.
|
||||||
> * At least 2 seed nodes should be run because the seed nodes restart once per day.
|
> * At least 2 seed nodes should be run because the seed nodes restart once per day.
|
||||||
|
|
||||||
@ -180,32 +173,21 @@ For each arbitrator:
|
|||||||
|
|
||||||
The arbitrator is now registered and ready to accept requests for dispute resolution.
|
The arbitrator is now registered and ready to accept requests for dispute resolution.
|
||||||
|
|
||||||
**Notes**
|
> [!note]
|
||||||
- Arbitrators must use a local Monero node with unrestricted RPC in order to submit and flush transactions from the pool.
|
> * Arbitrators must use a local Monero node with unrestricted RPC in order to submit and flush transactions from the pool.
|
||||||
- Arbitrators should remain online as much as possible in order to balance trades and avoid clients spending time trying to contact offline arbitrators. A VPS or dedicated machine running 24/7 is highly recommended.
|
> * Arbitrators should remain online as much as possible in order to balance trades and avoid clients spending time trying to contact offline arbitrators. A VPS or dedicated machine running 24/7 is highly recommended.
|
||||||
- Remember that for the network to run correctly and people to be able to open and accept trades, at least one arbitrator must be registered on the network.
|
> * Remember that for the network to run correctly and people to be able to open and accept trades, at least one arbitrator must be registered on the network.
|
||||||
- IMPORTANT: Do not reuse keypairs on multiple arbitrator instances.
|
> * IMPORTANT: Do not reuse keypairs on multiple arbitrator instances.
|
||||||
|
|
||||||
## Remove an arbitrator
|
## Remove an arbitrator
|
||||||
|
|
||||||
> **Note**
|
> [!warning]
|
||||||
> Ensure the arbitrator's trades are completed before retiring the instance.
|
> * Ensure the arbitrator's trades are completed before retiring the instance.
|
||||||
|
> * To preserve signed accounts, the arbitrator public key must remain in the repository, even after revoking.
|
||||||
|
|
||||||
1. Start the arbitrator's desktop application using the application launcher or e.g. `make arbitrator-desktop-mainnet` from the root of the repository.
|
1. Start the arbitrator's desktop application using the application launcher or e.g. `make arbitrator-desktop-mainnet` from the root of the repository.
|
||||||
2. Go to the `Account` tab and click the button to unregister the arbitrator.
|
2. Go to the `Account` tab and click the button to unregister the arbitrator.
|
||||||
|
|
||||||
## Set a network filter on mainnet
|
|
||||||
|
|
||||||
On mainnet, the p2p network is expected to have a filter object for offers, onions, currencies, payment methods, etc.
|
|
||||||
|
|
||||||
To set the network's filter object:
|
|
||||||
|
|
||||||
1. Enter `ctrl + f` in the arbitrator or other Haveno instance to open the Filter window.
|
|
||||||
2. Enter a developer private key from the previous steps and click "Add Filter" to register.
|
|
||||||
|
|
||||||
> **Note**
|
|
||||||
> If all seed nodes are restarted at the same time, arbitrators and the filter object will become unregistered and will need to be re-registered.
|
|
||||||
|
|
||||||
## Change the default folder name for Haveno application data
|
## Change the default folder name for Haveno application data
|
||||||
|
|
||||||
To avoid user data corruption when using multiple Haveno networks, change the default folder name for Haveno's application data on your network:
|
To avoid user data corruption when using multiple Haveno networks, change the default folder name for Haveno's application data on your network:
|
||||||
@ -243,10 +225,30 @@ Set `ARBITRATOR_ASSIGNS_TRADE_FEE_ADDRESS` to `true` for the arbitrator to assig
|
|||||||
|
|
||||||
Otherwise set `ARBITRATOR_ASSIGNS_TRADE_FEE_ADDRESS` to `false` and set the XMR address in `getGlobalTradeFeeAddress()` to collect all trade fees to a single address (e.g. a multisig wallet shared among network administrators).
|
Otherwise set `ARBITRATOR_ASSIGNS_TRADE_FEE_ADDRESS` to `false` and set the XMR address in `getGlobalTradeFeeAddress()` to collect all trade fees to a single address (e.g. a multisig wallet shared among network administrators).
|
||||||
|
|
||||||
|
## Build and start price nodes
|
||||||
|
|
||||||
|
The price node is separated from Haveno and is run as a standalone service. To deploy a pricenode on both TOR and clearnet, see the instructions on the repository: https://github.com/haveno-dex/haveno-pricenode.
|
||||||
|
|
||||||
|
After the price node is built and deployed, add the price node to `DEFAULT_NODES` in [ProvidersRepository.java](https://github.com/haveno-dex/haveno/blob/3cdd88b56915c7f8afd4f1a39e6c1197c2665d63/core/src/main/java/haveno/core/provider/ProvidersRepository.java#L50).
|
||||||
|
|
||||||
|
Customize and deploy haveno-pricenode.env and haveno-pricenode.service to run as a system service.
|
||||||
|
|
||||||
## Update the download URL
|
## Update the download URL
|
||||||
|
|
||||||
Change every instance of `https://haveno.exchange/downloads` to your download URL. For example, `https://havenoexample.com/downloads`.
|
Change every instance of `https://haveno.exchange/downloads` to your download URL. For example, `https://havenoexample.com/downloads`.
|
||||||
|
|
||||||
|
## Set a network filter on mainnet
|
||||||
|
|
||||||
|
On mainnet, the p2p network is expected to have a filter object for offers, onions, currencies, payment methods, etc.
|
||||||
|
|
||||||
|
To set the network's filter object:
|
||||||
|
|
||||||
|
1. Enter `ctrl + f` in the arbitrator or other Haveno instance to open the Filter window.
|
||||||
|
2. Enter a developer private key from the previous steps and click "Add Filter" to register.
|
||||||
|
|
||||||
|
> [!note]
|
||||||
|
> If all seed nodes are restarted at the same time, arbitrators and the filter object will become unregistered and will need to be re-registered.
|
||||||
|
|
||||||
## Start users for testing
|
## Start users for testing
|
||||||
|
|
||||||
Start user1 on Monero's mainnet using `make user1-desktop-mainnet` or Monero's stagenet using `make user1-desktop-stagenet`.
|
Start user1 on Monero's mainnet using `make user1-desktop-mainnet` or Monero's stagenet using `make user1-desktop-stagenet`.
|
||||||
|
@ -124,7 +124,7 @@ OUTPUT=$(gpg --digest-algo SHA256 --verify "${signature_filename}" "${binary_fil
|
|||||||
if ! echo "$OUTPUT" | grep -q "Good signature from"; then
|
if ! echo "$OUTPUT" | grep -q "Good signature from"; then
|
||||||
echo_red "Verification failed: $OUTPUT"
|
echo_red "Verification failed: $OUTPUT"
|
||||||
exit 1;
|
exit 1;
|
||||||
else 7z x "${binary_filename}" && mv haveno*.deb "${package_filename}"
|
else mv -f "${binary_filename}" "${package_filename}"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
echo_blue "Haveno binaries have been successfully verified."
|
echo_blue "Haveno binaries have been successfully verified."
|
||||||
@ -136,7 +136,7 @@ mkdir -p "${install_dir}"
|
|||||||
|
|
||||||
# Delete old Haveno binaries
|
# Delete old Haveno binaries
|
||||||
#rm -f "${install_dir}/"*.deb*
|
#rm -f "${install_dir}/"*.deb*
|
||||||
mv "${binary_filename}" "${package_filename}" "${key_filename}" "${signature_filename}" "${install_dir}"
|
mv "${package_filename}" "${key_filename}" "${signature_filename}" "${install_dir}"
|
||||||
echo_blue "Files moved to persistent directory ${install_dir}"
|
echo_blue "Files moved to persistent directory ${install_dir}"
|
||||||
|
|
||||||
|
|
||||||
|
@ -41,7 +41,7 @@ import lombok.extern.slf4j.Slf4j;
|
|||||||
@Slf4j
|
@Slf4j
|
||||||
public class SeedNodeMain extends ExecutableForAppWithP2p {
|
public class SeedNodeMain extends ExecutableForAppWithP2p {
|
||||||
private static final long CHECK_CONNECTION_LOSS_SEC = 30;
|
private static final long CHECK_CONNECTION_LOSS_SEC = 30;
|
||||||
private static final String VERSION = "1.0.19";
|
private static final String VERSION = "1.1.0";
|
||||||
private SeedNode seedNode;
|
private SeedNode seedNode;
|
||||||
private Timer checkConnectionLossTime;
|
private Timer checkConnectionLossTime;
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user