mirror of
https://github.com/haveno-dex/haveno.git
synced 2025-04-20 15:55:54 -04:00
Compare commits
15 Commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
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());
|
||||||
|
initializeConnections();
|
||||||
|
if (getConnection() == null) {
|
||||||
|
log.warn("No provided nodes available, falling back to public nodes");
|
||||||
|
fallbackToBestConnection();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
fallbackApplied = true;
|
|
||||||
initializeConnections();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ------------------------------- 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,8 +619,10 @@ 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())) {
|
||||||
if (!connectionList.hasConnection(connection.getUri())) addConnection(connection);
|
MoneroRpcConnection connection = new MoneroRpcConnection(node.getHostNameOrAddress() + ":" + node.getPort()).setPriority(node.getPriority());
|
||||||
|
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());
|
||||||
@ -615,8 +634,10 @@ 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())) {
|
||||||
addConnection(connection);
|
MoneroRpcConnection connection = new MoneroRpcConnection(node.getHostNameOrAddress() + ":" + node.getPort()).setPriority(node.getPriority());
|
||||||
|
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());
|
||||||
@ -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()) {
|
log.info("Starting local node");
|
||||||
try {
|
xmrLocalNode.start();
|
||||||
log.info("Starting local node");
|
|
||||||
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,17 +1101,20 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
|
|||||||
} else {
|
} else {
|
||||||
|
|
||||||
// validate non-pending state
|
// validate non-pending state
|
||||||
try {
|
boolean skipValidation = openOffer.isDeactivated() && hasConflictingClone(openOffer) && openOffer.getOffer().getOfferPayload().getArbitratorSignature() == null; // clone with conflicting offer is deactivated and unsigned at first
|
||||||
validateSignedState(openOffer);
|
if (!skipValidation) {
|
||||||
resultHandler.handleResult(null); // done processing if non-pending state is valid
|
try {
|
||||||
return;
|
validateSignedState(openOffer);
|
||||||
} catch (Exception e) {
|
resultHandler.handleResult(null); // done processing if non-pending state is valid
|
||||||
log.warn(e.getMessage());
|
return;
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.warn(e.getMessage());
|
||||||
|
|
||||||
// reset arbitrator signature
|
// reset arbitrator signature
|
||||||
openOffer.getOffer().getOfferPayload().setArbitratorSignature(null);
|
openOffer.getOffer().getOfferPayload().setArbitratorSignature(null);
|
||||||
openOffer.getOffer().getOfferPayload().setArbitratorSigner(null);
|
openOffer.getOffer().getOfferPayload().setArbitratorSigner(null);
|
||||||
if (openOffer.isAvailable()) openOffer.setState(OpenOffer.State.PENDING);
|
if (openOffer.isAvailable()) openOffer.setState(OpenOffer.State.PENDING);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -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;
|
||||||
@ -1608,11 +1609,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,24 +1767,30 @@ public abstract class Trade extends XmrWalletBase implements Tradable, Model {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void removeTradeOnError() {
|
private void removeTradeOnError() {
|
||||||
log.warn("removeTradeOnError() trade={}, tradeId={}, state={}", getClass().getSimpleName(), getShortId(), getState());
|
synchronized (removeTradeOnErrorLock) {
|
||||||
|
|
||||||
// force close and re-open wallet in case stuck
|
// skip if already shut down or removed
|
||||||
forceCloseWallet();
|
if (isShutDown || !processModel.getTradeManager().hasTrade(getId())) return;
|
||||||
if (isDepositRequested()) getWallet();
|
log.warn("removeTradeOnError() trade={}, tradeId={}, state={}", getClass().getSimpleName(), getShortId(), getState());
|
||||||
|
|
||||||
// shut down trade thread
|
// force close and re-open wallet in case stuck
|
||||||
try {
|
forceCloseWallet();
|
||||||
ThreadUtils.shutDown(getId(), 1000l);
|
if (isDepositRequested()) getWallet();
|
||||||
} catch (Exception e) {
|
|
||||||
log.warn("Error shutting down trade thread for {} {}: {}", getClass().getSimpleName(), getId(), e.getMessage());
|
// clear and shut down trade
|
||||||
|
onShutDownStarted();
|
||||||
|
clearAndShutDown();
|
||||||
|
|
||||||
|
// shut down trade thread
|
||||||
|
try {
|
||||||
|
ThreadUtils.shutDown(getId(), 5000l);
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.warn("Error shutting down trade thread for {} {}: {}", getClass().getSimpleName(), getId(), e.getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
// unregister trade
|
||||||
|
processModel.getTradeManager().unregisterTrade(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
// clear and shut down trade
|
|
||||||
clearAndShutDown();
|
|
||||||
|
|
||||||
// unregister trade
|
|
||||||
processModel.getTradeManager().unregisterTrade(this);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
@ -1824,6 +1832,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
|
||||||
@ -2648,7 +2663,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 +2765,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()) {
|
||||||
|
@ -563,9 +563,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 +985,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 +993,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 +1278,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();
|
||||||
this.errorMessageHandler = null; // TODO: set this when trade state is >= DEPOSIT_PUBLISHED
|
|
||||||
handleTaskRunnerSuccess(sender, response);
|
// tasks may complete successfully but process an error
|
||||||
if (tradeResultHandler != null) tradeResultHandler.handleResult(trade); // trade is initialized
|
if (trade.getInitError() == null) {
|
||||||
|
this.errorMessageHandler = null; // TODO: set this when trade state is >= DEPOSIT_PUBLISHED
|
||||||
|
handleTaskRunnerSuccess(sender, response);
|
||||||
|
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);
|
||||||
@ -832,7 +842,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);
|
||||||
|
|
||||||
|
// play sound if funds received
|
||||||
|
boolean fundsReceived = balanceSumBefore != null && getNonTradeBalanceSum().compareTo(balanceSumBefore) > 0;
|
||||||
|
if (fundsReceived) HavenoUtils.playCashRegisterSound();
|
||||||
|
|
||||||
// notify balance update
|
// notify balance update
|
||||||
UserThread.execute(() -> {
|
updateCounter.set(updateCounter.get() + 1);
|
||||||
|
|
||||||
// check if funds received
|
|
||||||
boolean fundsReceived = balanceSumBefore != null && getNonTradeBalanceSum().compareTo(balanceSumBefore) > 0;
|
|
||||||
if (fundsReceived) {
|
|
||||||
HavenoUtils.playCashRegisterSound();
|
|
||||||
}
|
|
||||||
|
|
||||||
// increase counter to notify listeners
|
|
||||||
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,32 +640,37 @@ public abstract class MutableOfferViewModel<M extends MutableOfferDataModel> ext
|
|||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
void onPlaceOffer(Offer offer, Runnable resultHandler) {
|
void onPlaceOffer(Offer offer, Runnable resultHandler) {
|
||||||
errorMessage.set(null);
|
ThreadUtils.execute(() -> {
|
||||||
createOfferRequested = true;
|
|
||||||
createOfferCanceled = false;
|
|
||||||
|
|
||||||
dataModel.onPlaceOffer(offer, transaction -> {
|
|
||||||
resultHandler.run();
|
|
||||||
if (!createOfferCanceled) placeOfferCompleted.set(true);
|
|
||||||
errorMessage.set(null);
|
errorMessage.set(null);
|
||||||
}, errMessage -> {
|
createOfferInProgress = true;
|
||||||
createOfferRequested = false;
|
createOfferCanceled = false;
|
||||||
if (offer.getState() == Offer.State.OFFER_FEE_RESERVED) errorMessage.set(errMessage + Res.get("createOffer.errorInfo"));
|
|
||||||
else errorMessage.set(errMessage);
|
dataModel.onPlaceOffer(offer, transaction -> {
|
||||||
|
createOfferInProgress = false;
|
||||||
|
resultHandler.run();
|
||||||
|
if (!createOfferCanceled) placeOfferCompleted.set(true);
|
||||||
|
errorMessage.set(null);
|
||||||
|
}, errMessage -> {
|
||||||
|
createOfferInProgress = false;
|
||||||
|
if (offer.getState() == Offer.State.OFFER_FEE_RESERVED) errorMessage.set(errMessage + Res.get("createOffer.errorInfo"));
|
||||||
|
else errorMessage.set(errorMessage.get());
|
||||||
|
|
||||||
|
UserThread.execute(() -> {
|
||||||
|
updateButtonDisableState();
|
||||||
|
updateSpinnerInfo();
|
||||||
|
resultHandler.run();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
UserThread.execute(() -> {
|
UserThread.execute(() -> {
|
||||||
updateButtonDisableState();
|
updateButtonDisableState();
|
||||||
updateSpinnerInfo();
|
updateSpinnerInfo();
|
||||||
resultHandler.run();
|
|
||||||
});
|
});
|
||||||
});
|
}, getClass().getSimpleName());
|
||||||
|
|
||||||
updateButtonDisableState();
|
|
||||||
updateSpinnerInfo();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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()
|
||||||
|
@ -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() {
|
||||||
|
@ -194,6 +194,9 @@ The arbitrator is now registered and ready to accept requests for dispute resolu
|
|||||||
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.
|
||||||
|
|
||||||
|
> **Note**
|
||||||
|
> To preserve signed accounts, the arbitrator public key must remain in the repository, even after revoking.
|
||||||
|
|
||||||
## Set a network filter on mainnet
|
## 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.
|
On mainnet, the p2p network is expected to have a filter object for offers, onions, currencies, payment methods, etc.
|
||||||
|
@ -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