diff --git a/src/main/java/common/utils/GenUtils.java b/src/main/java/common/utils/GenUtils.java
index 9e38285e..48464150 100644
--- a/src/main/java/common/utils/GenUtils.java
+++ b/src/main/java/common/utils/GenUtils.java
@@ -6,6 +6,8 @@
import java.util.Collection;
import java.util.List;
import java.util.UUID;
+import java.util.concurrent.Callable;
+import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
@@ -393,7 +395,7 @@ public static String getUUID() {
/**
* Wait for the duration.
*
- * @param durationMs is the duration to wait for in milliseconds
+ * @param durationMs the duration to wait for in milliseconds
*/
public static void waitFor(long durationMs) {
try { TimeUnit.MILLISECONDS.sleep(durationMs); }
@@ -403,7 +405,7 @@ public static void waitFor(long durationMs) {
/**
* Execute tasks in parallel.
*
- * @param tasks are the tasks to execute in parallel
+ * @param tasks are tasks to execute in parallel
*/
public static void executeTasks(Collection tasks) {
executeTasks(tasks, tasks.size());
@@ -412,8 +414,8 @@ public static void executeTasks(Collection tasks) {
/**
* Execute tasks in parallel.
*
- * @param tasks are the tasks to execute in parallel
- * @param maxConcurrency is the maximum number of tasks to run in parallel
+ * @param tasks are tasks to execute in parallel
+ * @param maxConcurrency the maximum number of tasks to run in parallel
*/
public static void executeTasks(Collection tasks, int maxConcurrency) {
if (tasks.isEmpty()) return;
@@ -435,4 +437,40 @@ public static void executeTasks(Collection tasks, int maxConcurrency)
throw new RuntimeException(e);
}
}
+
+ /**
+ * Execute tasks in parallel.
+ *
+ * @param parameterized type
+ * @param tasks the tasks to execute in parallel
+ * @return the results of each task
+ * @throws InterruptedException
+ * @throws ExecutionException
+ */
+ public List executeTasks(List> tasks) throws InterruptedException, ExecutionException {
+ return executeTasks(tasks, tasks.size());
+ }
+
+ /**
+ * Execute tasks in parallel.
+ *
+ * @param parameterized type
+ * @param tasks the tasks to execute in parallel
+ * @param maxConcurrency the maximum number of tasks to run in parallel
+ * @return the results of each task
+ * @throws InterruptedException
+ * @throws ExecutionException
+ */
+ public List executeTasks(List> tasks, int maxConcurrency) throws InterruptedException, ExecutionException {
+ ExecutorService executor = Executors.newFixedThreadPool(maxConcurrency);
+ List> futures = new ArrayList<>();
+ List results = new ArrayList<>();
+ try {
+ for (Callable task : tasks) futures.add(executor.submit(task));
+ for (Future future : futures) results.add(future.get());
+ } finally {
+ executor.shutdown();
+ }
+ return results;
+ }
}
diff --git a/src/main/java/monero/common/MoneroConnectionManager.java b/src/main/java/monero/common/MoneroConnectionManager.java
index 6e0825e2..1f38a82a 100644
--- a/src/main/java/monero/common/MoneroConnectionManager.java
+++ b/src/main/java/monero/common/MoneroConnectionManager.java
@@ -2,16 +2,20 @@
import java.util.ArrayList;
import java.util.Arrays;
+import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
+import java.util.HashMap;
import java.util.List;
import java.util.Map;
+import java.util.Map.Entry;
import java.util.TreeMap;
import java.util.concurrent.CompletionService;
import java.util.concurrent.ExecutorCompletionService;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
-import java.util.concurrent.TimeUnit;
+
+import common.utils.GenUtils;
/**
* Manages a collection of prioritized connections to daemon or wallet RPC endpoints.
@@ -43,11 +47,8 @@
* }
* });
*
- * // check connection status every 10 seconds
- * connectionManager.startCheckingConnection(10000l);
- *
- * // automatically switch to best available connection if disconnected
- * connectionManager.setAutoSwitch(true);
+ * // start polling for best connection every 10 seconds and automatically switch
+ * connectionManager.startPolling(10000l);
*
* // get best available connection in order of priority then response time
* MoneroRpcConnection bestConnection = connectionManager.getBestAvailableConnection();
@@ -63,19 +64,33 @@
*
*/
public class MoneroConnectionManager {
-
+
// static variables
private static final long DEFAULT_TIMEOUT = 5000l;
- private static final long DEFAULT_CHECK_CONNECTION_PERIOD = 15000l;
-
+ private static final long DEFAULT_POLL_PERIOD = 20000l;
+ private static final boolean DEFAULT_AUTO_SWITCH = true;
+ private static int MIN_BETTER_RESPONSES = 3;
+
// instance variables
private MoneroRpcConnection currentConnection;
private List connections = new ArrayList();
private List listeners = new ArrayList();
private ConnectionComparator connectionComparator = new ConnectionComparator();
+ private boolean autoSwitch = DEFAULT_AUTO_SWITCH;
private long timeoutMs = DEFAULT_TIMEOUT;
- private boolean autoSwitch;
- private TaskLooper checkConnectionLooper;
+ private TaskLooper poller;
+ private Map> responseTimes = new HashMap>();
+
+ /**
+ * Specify behavior when polling.
+ *
+ * One of PRIORITIZED (poll connections in order of priority until connected; default), CURRENT (poll current connection), or ALL (poll all connections).
+ */
+ public enum PollType {
+ PRIORITIZED,
+ CURRENT,
+ ALL
+ }
/**
* Add a listener to receive notifications when the connection changes.
@@ -117,6 +132,16 @@ public MoneroConnectionManager removeListeners() {
public List getListeners() {
return listeners;
}
+
+ /**
+ * Add a connection URI.
+ *
+ * @param uri - uri of the connection to add
+ * @return this connection manager for chaining
+ */
+ public MoneroConnectionManager addConnection(String uri) {
+ return addConnection(new MoneroRpcConnection(uri));
+ }
/**
* Add a connection. The connection may have an elevated priority for this manager to use.
@@ -142,23 +167,60 @@ public MoneroConnectionManager removeConnection(String uri) {
MoneroRpcConnection connection = getConnectionByUri(uri);
if (connection == null) throw new MoneroError("No connection exists with URI: " + uri);
connections.remove(connection);
+ responseTimes.remove(connection);
if (connection == currentConnection) {
currentConnection = null;
onConnectionChanged(currentConnection);
}
return this;
}
-
+
/**
- * Indicates if the connection manager is connected to a node.
+ * Set the current connection without changing the credentials.
+ * Add new connection if URI not previously added.
+ * Notify if current connection changes.
+ * Does not check the connection.
*
- * @return true if the current connection is set, online, and not unauthenticated, null if unknown, false otherwise
+ * @param uri identifies the connection to make current
+ * @return this connection manager for chaining
*/
- public Boolean isConnected() {
- if (currentConnection == null) return false;
- return currentConnection.isConnected();
+ public MoneroConnectionManager setConnection(String uri) {
+ if (uri == null || "".equals(uri)) return setConnection((MoneroRpcConnection) null);
+ MoneroRpcConnection connection = getConnectionByUri(uri);
+ return setConnection(connection == null ? new MoneroRpcConnection(uri) : connection);
}
+ /**
+ * Set the current connection.
+ * Replace connection if its URI was previously added. Otherwise add new connection.
+ * Notify if current connection changes.
+ * Does not check the connection.
+ *
+ * @param connection is the connection to make current
+ * @return this connection manager for chaining
+ */
+ public MoneroConnectionManager setConnection(MoneroRpcConnection connection) {
+ if (currentConnection == connection) return this;
+
+ // check if setting null connection
+ if (connection == null) {
+ currentConnection = null;
+ onConnectionChanged(null);
+ return this;
+ }
+
+ // must provide uri
+ if (connection.getUri() == null || "".equals(connection.getUri())) throw new MoneroError("Connection is missing URI");
+
+ // add or replace connection
+ MoneroRpcConnection prevConnection = getConnectionByUri(connection.getUri());
+ if (prevConnection != null) connections.remove(prevConnection);
+ addConnection(connection);
+ currentConnection = connection;
+ onConnectionChanged(currentConnection);
+ return this;
+ }
+
/**
* Get the current connection.
*
@@ -167,18 +229,28 @@ public Boolean isConnected() {
public MoneroRpcConnection getConnection() {
return currentConnection;
}
+
+ /**
+ * Indicates if this manager has a connection with the given URI.
+ *
+ * @param uri - URI of the connection to check
+ * @return true if this manager has a connection with the given URI, false otherwise
+ */
+ public boolean hasConnection(String uri) {
+ return getConnectionByUri(uri) != null;
+ }
/**
* Get a connection by URI.
*
- * @param uri - uri of the connection to get
+ * @param uri - URI of the connection to get
* @return the connection with the URI or null if no connection with the URI exists
*/
public MoneroRpcConnection getConnectionByUri(String uri) {
for (MoneroRpcConnection connection : connections) if (connection.getUri().equals(uri)) return connection;
return null;
}
-
+
/**
* Get all connections in order of current connection (if applicable), online status, priority, and name.
*
@@ -189,93 +261,80 @@ public List getConnections() {
Collections.sort(sortedConnections, connectionComparator);
return sortedConnections;
}
-
+
/**
- * Get the best available connection in order of priority then response time.
+ * Indicates if the connection manager is connected to a node.
*
- * @param excludedConnections - connections to be excluded from consideration (optional)
- * @return the best available connection in order of priority then response time, null if no connections available
+ * @return true if the current connection is set, online, and not unauthenticated, null if unknown, false otherwise
*/
- public MoneroRpcConnection getBestAvailableConnection(MoneroRpcConnection... excludedConnections ) {
-
- // try connections within each ascending priority
- for (List prioritizedConnections : getConnectionsInAscendingPriority()) {
- try {
-
- // check connections in parallel
- int numTasks = 0;
- ExecutorService pool = Executors.newFixedThreadPool(prioritizedConnections.size());
- CompletionService completionService = new ExecutorCompletionService(pool);
- for (MoneroRpcConnection connection : prioritizedConnections) {
- if (Arrays.asList(excludedConnections).contains(connection)) continue;
- numTasks++;
- completionService.submit(new Runnable() {
- @Override
- public void run() {
- connection.checkConnection(timeoutMs);
- }
- }, connection);
- }
-
- // use first available connection
- pool.shutdown();
- for (int i = 0; i < numTasks; i++) {
- MoneroRpcConnection connection = completionService.take().get();
- if (connection.isConnected()) return connection;
- }
- } catch (Exception e) {
- throw new MoneroError(e);
- }
- }
- return null;
+ public Boolean isConnected() {
+ if (currentConnection == null) return false;
+ return currentConnection.isConnected();
}
-
+
/**
- * Set the current connection without changing the credentials.
- * Add new connection if URI not previously added.
- * Notify if current connection changes.
- * Does not check the connection.
+ * Start polling connections. Automatically connects to the best available connection based on priority, response time, and consistency.
+ */
+ public void startPolling() {
+ startPolling(null, null, null, null, null);
+ }
+
+ /**
+ * Start polling connections. Automatically connects to the best available connection based on priority, response time, and consistency.
*
- * @param uri identifies the connection to make current
- * @return this connection manager for chaining
+ * @param periodMs poll period in milliseconds (default 20s)
*/
- public MoneroConnectionManager setConnection(String uri) {
- if (uri == null || "".equals(uri)) return setConnection((MoneroRpcConnection) null);
- MoneroRpcConnection connection = getConnectionByUri(uri);
- return setConnection(connection == null ? new MoneroRpcConnection(uri) : connection);
+ public void startPolling(Long periodMs) {
+ startPolling(periodMs, null, null, null, null);
}
-
+
/**
- * Set the current connection.
- * Replace connection if its URI was previously added. Otherwise add new connection.
- * Notify if current connection changes.
- * Does not check the connection.
+ * Start polling connections.
*
- * @param connection is the connection to make current
+ * @param periodMs poll period in milliseconds (default 20s)
+ * @param autoSwitch specifies to automatically switch to the best connection (default true unless changed)
+ * @param timeoutMs specifies the timeout to poll a single connection (default 5s unless changed)
+ * @param pollType one of PRIORITIZED (poll connections in order of priority until connected; default), CURRENT (poll current connection), or ALL (poll all connections)
+ * @param excludedConnections connections excluded from being polled
* @return this connection manager for chaining
*/
- public MoneroConnectionManager setConnection(MoneroRpcConnection connection) {
- if (currentConnection == connection) return this;
-
- // check if setting null connection
- if (connection == null) {
- currentConnection = null;
- onConnectionChanged(null);
- return this;
+ public MoneroConnectionManager startPolling(Long periodMs, Boolean autoSwitch, Long timeoutMs, PollType pollType, Collection excludedConnections) {
+
+ // apply defaults
+ if (periodMs == null) periodMs = DEFAULT_POLL_PERIOD;
+ if (autoSwitch != null) setAutoSwitch(autoSwitch);
+ if (timeoutMs != null) setTimeout(timeoutMs);
+ if (pollType == null) pollType = PollType.PRIORITIZED;
+
+ // stop polling
+ stopPolling();
+
+ // start polling
+ switch (pollType) {
+ case CURRENT:
+ startPollingConnection(periodMs);
+ break;
+ case ALL:
+ startPollingConnections(periodMs);
+ break;
+ case PRIORITIZED:
+ default:
+ startPollingPrioritizedConnections(periodMs, excludedConnections);
}
-
- // must provide uri
- if (connection.getUri() == null || "".equals(connection.getUri())) throw new MoneroError("Connection is missing URI");
+ return this;
+ }
- // add or replace connection
- MoneroRpcConnection prevConnection = getConnectionByUri(connection.getUri());
- if (prevConnection != null) connections.remove(prevConnection);
- addConnection(connection);
- currentConnection = connection;
- onConnectionChanged(currentConnection);
+ /**
+ * Stop polling connections.
+ *
+ * @return this connection manager for chaining
+ */
+ public MoneroConnectionManager stopPolling() {
+ if (poller != null) poller.stop();
+ poller = null;
return this;
}
-
+
/**
* Check the current connection. If disconnected and auto switch enabled, switches to best available connection.
*
@@ -284,7 +343,10 @@ public MoneroConnectionManager setConnection(MoneroRpcConnection connection) {
public MoneroConnectionManager checkConnection() {
boolean connectionChanged = false;
MoneroRpcConnection connection = getConnection();
- if (connection != null && connection.checkConnection(timeoutMs)) connectionChanged = true;
+ if (connection != null) {
+ if (connection.checkConnection(timeoutMs)) connectionChanged = true;
+ processResponses(Arrays.asList(connection));
+ }
if (autoSwitch && !isConnected()) {
MoneroRpcConnection bestConnection = getBestAvailableConnection(connection);
if (bestConnection != null) {
@@ -295,107 +357,59 @@ public MoneroConnectionManager checkConnection() {
if (connectionChanged) onConnectionChanged(connection);
return this;
}
-
+
/**
* Check all managed connections.
*
* @return this connection manager for chaining
*/
public MoneroConnectionManager checkConnections() {
-
- // collect tasks to check connections
- MoneroRpcConnection currentConnection = getConnection();
- ExecutorService pool = Executors.newFixedThreadPool(connections.size());
- for (MoneroRpcConnection connection : connections) {
- pool.submit(new Runnable() {
- @Override
- public void run() {
- try {
- if (connection.checkConnection(timeoutMs) && connection == currentConnection) onConnectionChanged(connection);
- } catch (MoneroError err) {
- // ignore error
- }
- }
- });
- }
- try {
-
- // check connections in parallel
- pool.shutdown();
- pool.awaitTermination(timeoutMs, TimeUnit.MILLISECONDS);
-
- // auto switch to best connection
- if (autoSwitch && !isConnected()) {
- for (List prioritizedConnections : getConnectionsInAscendingPriority()) {
- MoneroRpcConnection bestConnection = null;
- for (MoneroRpcConnection prioritizedConnection : prioritizedConnections) {
- if (prioritizedConnection.isConnected() && (bestConnection == null || prioritizedConnection.getResponseTime() < bestConnection.getResponseTime())) {
- bestConnection = prioritizedConnection;
- }
- }
- if (bestConnection != null) {
- setConnection(bestConnection);
- break;
- }
- }
- }
- } catch (InterruptedException e) {
- throw new MoneroError(e);
- }
- return this;
- }
-
- /**
- * Check the connection and start checking the connection periodically.
- *
- * @return this connection manager for chaining (after first checking the connection)
- */
- public MoneroConnectionManager startCheckingConnection() {
- startCheckingConnection(null);
+ checkConnections(getConnections(), null);
return this;
}
-
+
/**
- * Check the connection and start checking the connection periodically.
+ * Get the best available connection in order of priority then response time.
*
- * @param periodMs is the time between checks in milliseconds (default 10000 ms or 10 seconds)
- * @return this connection manager for chaining (after first checking the connection)
+ * @param excludedConnections - connections to be excluded from consideration (optional)
+ * @return the best available connection in order of priority then response time, null if no connections available
*/
- public synchronized MoneroConnectionManager startCheckingConnection(Long periodMs) {
- checkConnection();
- if (periodMs == null) periodMs = DEFAULT_CHECK_CONNECTION_PERIOD;
- if (checkConnectionLooper != null) return this;
- checkConnectionLooper = new TaskLooper(new Runnable() {
- boolean isFirstCheck = true;
- @Override
- public void run() {
- if (isFirstCheck) {
- isFirstCheck = false; // skip first check
- return;
+ public MoneroRpcConnection getBestAvailableConnection(MoneroRpcConnection... excludedConnections ) {
+
+ // try connections within each ascending priority
+ for (List prioritizedConnections : getConnectionsInAscendingPriority()) {
+ try {
+
+ // check connections in parallel
+ int numTasks = 0;
+ ExecutorService pool = Executors.newFixedThreadPool(prioritizedConnections.size());
+ CompletionService completionService = new ExecutorCompletionService(pool);
+ for (MoneroRpcConnection connection : prioritizedConnections) {
+ if (Arrays.asList(excludedConnections).contains(connection)) continue;
+ numTasks++;
+ completionService.submit(() -> {
+ connection.checkConnection(timeoutMs);
+ return connection;
+ });
}
- try { checkConnection(); }
- catch (Exception e) { e.printStackTrace(); }
+
+ // use first available connection
+ pool.shutdown();
+ for (int i = 0; i < numTasks; i++) {
+ MoneroRpcConnection connection = completionService.take().get();
+ if (connection.isConnected()) return connection;
+ }
+ } catch (Exception e) {
+ throw new MoneroError(e);
}
- });
- checkConnectionLooper.start(periodMs);
- return this;
- }
-
- /**
- * Stop checking the connection status periodically.
- *
- * @return this connection manager for chaining
- */
- public synchronized MoneroConnectionManager stopCheckingConnection() {
- if (checkConnectionLooper != null) checkConnectionLooper.stop();
- checkConnectionLooper = null;
- return this;
+ }
+ return null;
}
/**
- * Automatically switch to best available connection if current connection is disconnected after being checked.
+ * Automatically switch to the best available connection as connections are polled, based on priority, response time, and consistency.
*
- * @param autoSwitch specifies if the connection should switch on disconnect
+ * @param autoSwitch specifies if the connection should auto switch to a better connection
* @return this connection manager for chaining
*/
public MoneroConnectionManager setAutoSwitch(boolean autoSwitch) {
@@ -415,11 +429,11 @@ public boolean getAutoSwitch() {
/**
* Set the maximum request time before a connection is considered offline.
*
- * @param timeoutInMs is the timeout before a connection is considered offline
+ * @param timeoutMs is the timeout before a connection is considered offline
* @return this connection manager for chaining
*/
- public MoneroConnectionManager setTimeout(long timeoutInMs) {
- this.timeoutMs = timeoutInMs;
+ public MoneroConnectionManager setTimeout(long timeoutMs) {
+ this.timeoutMs = timeoutMs;
return this;
}
@@ -472,14 +486,14 @@ public MoneroConnectionManager clear() {
*/
public MoneroConnectionManager reset() {
removeListeners();
- stopCheckingConnection();
+ stopPolling();
clear();
timeoutMs = DEFAULT_TIMEOUT;
- autoSwitch = false;
+ autoSwitch = DEFAULT_AUTO_SWITCH;
return this;
}
- // ------------------------------ PRIVATE HELPERS ---------------------------
+ // ----------------------------- PRIVATE HELPERS ----------------------------
private void onConnectionChanged(MoneroRpcConnection connection) {
for (MoneroConnectionManagerListener listener : listeners) listener.onConnectionChanged(connection);
@@ -518,4 +532,124 @@ public int compare(MoneroRpcConnection c1, MoneroRpcConnection c2) {
}
}
}
+
+ private void startPollingConnection(long periodMs) {
+ poller = new TaskLooper(() -> {
+ try { checkConnection(); }
+ catch (Exception e) { e.printStackTrace(); }
+ });
+ poller.start(periodMs);
+ }
+
+ private void startPollingConnections(long periodMs) {
+ poller = new TaskLooper(() -> {
+ try { checkConnections(); }
+ catch (Exception e) { e.printStackTrace(); }
+ });
+ poller.start(periodMs);
+ }
+
+ private void startPollingPrioritizedConnections(long periodMs, Collection excludedConnections) {
+ poller = new TaskLooper(() -> {
+ try { checkPrioritizedConnections(excludedConnections); }
+ catch (Exception e) { e.printStackTrace(); }
+ });
+ poller.start(periodMs);
+ }
+
+ private void checkPrioritizedConnections(Collection excludedConnections) {
+ for (List prioritizedConnections : getConnectionsInAscendingPriority()) {
+ boolean hasConnection = checkConnections(prioritizedConnections, excludedConnections);
+ if (hasConnection) return;
+ }
+ }
+
+ private boolean checkConnections(Collection connections, Collection excludedConnections) {
+ try {
+
+ // start checking connections in parallel
+ int numTasks = 0;
+ ExecutorService pool = Executors.newFixedThreadPool(connections.size());
+ CompletionService completionService = new ExecutorCompletionService(pool);
+ for (MoneroRpcConnection connection : connections) {
+ if (excludedConnections != null && excludedConnections.contains(connection)) continue;
+ numTasks++;
+ completionService.submit(() -> {
+ boolean change = connection.checkConnection(timeoutMs);
+ if (change && connection == getConnection()) onConnectionChanged(connection);
+ return connection;
+ });
+ }
+
+ // wait for responses
+ pool.shutdown();
+ boolean hasConnection = false;
+ for (int i = 0; i < numTasks; i++) {
+ MoneroRpcConnection connection = completionService.take().get();
+ if (Boolean.TRUE.equals(connection.isConnected()) && !hasConnection) {
+ hasConnection = true;
+ if (!Boolean.TRUE.equals(isConnected()) && autoSwitch) setConnection(connection); // set first available connection if disconnected
+ }
+ }
+
+ // process responses
+ processResponses(connections);
+ return hasConnection;
+ } catch (Exception e) {
+ throw new MoneroError(e);
+ }
+ }
+
+ private void processResponses(Collection responses) {
+
+ // add non-existing connections
+ for (MoneroRpcConnection connection : responses) {
+ if (!responseTimes.containsKey(connection)) responseTimes.put(connection, new ArrayList());
+ }
+
+ // insert response times or null
+ for (Entry> responseTime : responseTimes.entrySet()) {
+ responseTime.getValue().add(0, responses.contains(responseTime.getKey()) ? responseTime.getKey().getResponseTime() : null);
+
+ // remove old response times
+ if (responseTime.getValue().size() > MIN_BETTER_RESPONSES) responseTime.getValue().remove(responseTime.getValue().size() - 1);
+ }
+
+ // update best connection based on responses and priority
+ updateBestConnectionInPriority();
+ }
+
+ private void updateBestConnectionInPriority() {
+ if (!autoSwitch) return;
+ for (List prioritizedConnections : getConnectionsInAscendingPriority()) {
+ if (updateBestConnectionFromResponses(prioritizedConnections) != null) break;
+ }
+ }
+
+ private MoneroRpcConnection updateBestConnectionFromResponses(Collection responses) {
+ MoneroRpcConnection bestConnection = Boolean.TRUE.equals(isConnected()) ? getConnection() : null;
+ if (bestConnection != null && (!responseTimes.containsKey(bestConnection) || responseTimes.get(bestConnection).size() < MIN_BETTER_RESPONSES)) return bestConnection;
+ if (Boolean.TRUE.equals(isConnected())) {
+
+ // check if connection is consistently better
+ for (MoneroRpcConnection connection : responses) {
+ if (connection == bestConnection) continue;
+ if (!responseTimes.containsKey(connection) || responseTimes.get(connection).size() < MIN_BETTER_RESPONSES) continue;
+ boolean better = true;
+ for (int i = 0; i < MIN_BETTER_RESPONSES; i++) {
+ if (responseTimes.get(connection).get(i) == null || responseTimes.get(connection).get(i) >= responseTimes.get(bestConnection).get(i)) {
+ better = false;
+ break;
+ }
+ }
+ if (better) bestConnection = connection;
+ }
+ } else {
+ for (MoneroRpcConnection connection : responses) {
+ if (Boolean.TRUE.equals(connection.isConnected()) && (bestConnection == null || connection.getResponseTime() < bestConnection.getResponseTime())) bestConnection = connection;
+ }
+ }
+ if (bestConnection != null) setConnection(bestConnection);
+ return bestConnection;
+ }
}
diff --git a/src/main/java/monero/wallet/MoneroWallet.java b/src/main/java/monero/wallet/MoneroWallet.java
index 841dde6c..383e4d9d 100644
--- a/src/main/java/monero/wallet/MoneroWallet.java
+++ b/src/main/java/monero/wallet/MoneroWallet.java
@@ -26,6 +26,8 @@
import java.util.Collection;
import java.util.List;
import java.util.Set;
+
+import monero.common.MoneroConnectionManager;
import monero.common.MoneroRpcConnection;
import monero.daemon.model.MoneroKeyImage;
import monero.daemon.model.MoneroVersion;
@@ -120,6 +122,20 @@ public interface MoneroWallet {
* @return the wallet's daemon connection
*/
public MoneroRpcConnection getDaemonConnection();
+
+ /**
+ * Set the wallet's daemon connection manager.
+ *
+ * @param connectionManager manages connections to monerod
+ */
+ public void setConnectionManager(MoneroConnectionManager connectionManager);
+
+ /**
+ * Get the wallet's daemon connection manager.
+ *
+ * @return the wallet's daemon connection manager
+ */
+ public MoneroConnectionManager getConnectionManager();
/**
* Set the Tor proxy to the daemon.
@@ -561,7 +577,7 @@ public interface MoneroWallet {
* serverUri - uri of the wallet's daemon (optional)
* serverUsername - username to authenticate with the daemon (optional)
* serverPassword - password to authenticate with the daemon (optional)
- * server - MoneroRpcConnection providing server configuration (optional)
+ * server - MoneroRpcConnection to a monero daemon (optional)
* isConfirmed - get txs that are confirmed or not (optional)
* inTxPool - get txs that are in the tx pool or not (optional)
* isRelayed - get txs that are relayed or not (optional)
diff --git a/src/main/java/monero/wallet/MoneroWalletDefault.java b/src/main/java/monero/wallet/MoneroWalletDefault.java
index e369425a..f2056bdb 100644
--- a/src/main/java/monero/wallet/MoneroWalletDefault.java
+++ b/src/main/java/monero/wallet/MoneroWalletDefault.java
@@ -30,6 +30,9 @@
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
+
+import monero.common.MoneroConnectionManager;
+import monero.common.MoneroConnectionManagerListener;
import monero.common.MoneroError;
import monero.common.MoneroRpcConnection;
import monero.daemon.model.MoneroBlock;
@@ -57,6 +60,8 @@
abstract class MoneroWalletDefault implements MoneroWallet {
protected Set listeners;
+ protected MoneroConnectionManager connectionManager;
+ protected MoneroConnectionManagerListener connectionManagerListener;
public MoneroWalletDefault() {
this.listeners = new LinkedHashSet();
@@ -88,6 +93,26 @@ public void setDaemonConnection(String uri, String username, String password) {
if (uri == null) setDaemonConnection((MoneroRpcConnection) null);
else setDaemonConnection(new MoneroRpcConnection(uri, username, password));
}
+
+ @Override
+ public void setConnectionManager(MoneroConnectionManager connectionManager) {
+ if (this.connectionManager != null) this.connectionManager.removeListener(connectionManagerListener);
+ this.connectionManager = connectionManager;
+ if (connectionManager == null) return;
+ if (connectionManagerListener == null) connectionManagerListener = new MoneroConnectionManagerListener() {
+ @Override
+ public void onConnectionChanged(MoneroRpcConnection connection) {
+ setDaemonConnection(connection);
+ }
+ };
+ connectionManager.addListener(connectionManagerListener);
+ setDaemonConnection(connectionManager.getConnection());
+ }
+
+ @Override
+ public MoneroConnectionManager getConnectionManager() {
+ return connectionManager;
+ }
@Override
public String getPrimaryAddress() {
@@ -368,6 +393,13 @@ public int importMultisigHex(String... multisigHexes) {
public void close() {
close(false); // close without saving
}
+
+ @Override
+ public void close(boolean save) {
+ if (connectionManager != null) connectionManager.removeListener(connectionManagerListener);
+ connectionManager = null;
+ connectionManagerListener = null;
+ }
protected MoneroTransferQuery normalizeTransferQuery(MoneroTransferQuery query) {
if (query == null) query = new MoneroTransferQuery();
diff --git a/src/main/java/monero/wallet/MoneroWalletFull.java b/src/main/java/monero/wallet/MoneroWalletFull.java
index 799f5c6a..e1eda897 100644
--- a/src/main/java/monero/wallet/MoneroWalletFull.java
+++ b/src/main/java/monero/wallet/MoneroWalletFull.java
@@ -176,7 +176,7 @@ public static MoneroWalletFull openWalletData(String password, MoneroNetworkType
* serverUri - uri of the wallet's daemon (optional)
* serverUsername - username to authenticate with the daemon (optional)
* serverPassword - password to authenticate with the daemon (optional)
- * server - MoneroRpcConnection providing server configuration (optional)
+ * server - MoneroRpcConnection to a monero daemon (optional)
*
*
* @param config configures the wallet to open
@@ -245,10 +245,11 @@ public static MoneroWalletFull openWallet(MoneroWalletConfig config) {
* privateSpendKey - private spend key of the wallet to create (optional)
* restoreHeight - block height to start scanning from (defaults to 0 unless generating random wallet)
* language - language of the wallet's seed (defaults to "English" or auto-detected)
+ * server - MoneroRpcConnection to a monero daemon (optional)
* serverUri - uri of the wallet's daemon (optional)
* serverUsername - username to authenticate with the daemon (optional)
* serverPassword - password to authenticate with the daemon (optional)
- * server - MoneroRpcConnection providing server configuration (optional)
+ * connectionManager - manage connections to monerod (optional)
* accountLookahead - number of accounts to scan (optional)
* subaddressLookahead - number of subaddresses per account to scan (optional)
*
@@ -266,26 +267,34 @@ public static MoneroWalletFull createWallet(MoneroWalletConfig config) {
throw new MoneroError("Wallet may be initialized with a seed or keys but not both");
}
if (Boolean.TRUE.equals(config.getSaveCurrent() != null)) throw new MoneroError("Cannot save current wallet when creating full wallet");
+
+ // set server from connection manager if provided
+ if (config.getConnectionManager() != null) {
+ if (config.getServer() != null) throw new MoneroError("Wallet can be initialized with a server or connection manager but not both");
+ config.setServer(config.getConnectionManager().getConnection());
+ }
// create wallet
+ MoneroWalletFull wallet;
if (config.getSeed() != null) {
if (config.getLanguage() != null) throw new MoneroError("Cannot specify language when creating wallet from seed");
- return createWalletFromSeed(config);
+ wallet = createWalletFromSeed(config);
} else if (config.getPrimaryAddress() != null || config.getPrivateSpendKey() != null) {
if (config.getSeedOffset() != null) throw new MoneroError("Cannot specify seed offset when creating wallet from keys");
- return createWalletFromKeys(config);
+ wallet = createWalletFromKeys(config);
} else {
if (config.getSeedOffset() != null) throw new MoneroError("Cannot specify seed offset when creating random wallet");
if (config.getRestoreHeight() != null) throw new MoneroError("Cannot specify restore height when creating random wallet");
- return createWalletRandom(config);
+ wallet = createWalletRandom(config);
}
+ wallet.setConnectionManager(config.getConnectionManager());
+ return wallet;
}
private static MoneroWalletFull createWalletFromSeed(MoneroWalletConfig config) {
if (config.getRestoreHeight() == null) config.setRestoreHeight(0l);
long jniWalletHandle = createWalletJni(serializeWalletConfig(config));
MoneroWalletFull wallet = new MoneroWalletFull(jniWalletHandle, config.getPassword());
- wallet.setDaemonConnection(config.getServer());
return wallet;
}
@@ -295,7 +304,6 @@ private static MoneroWalletFull createWalletFromKeys(MoneroWalletConfig config)
try {
long jniWalletHandle = createWalletJni(serializeWalletConfig(config));
MoneroWalletFull wallet = new MoneroWalletFull(jniWalletHandle, config.getPassword());
- wallet.setDaemonConnection(config.getServer());
return wallet;
} catch (Exception e) {
throw new MoneroError(e.getMessage());
@@ -1364,6 +1372,7 @@ public void save() {
@Override
public void close(boolean save) {
+ super.close(save);
if (isClosed) return; // closing a closed wallet has no effect
isClosed = true;
password = null;
diff --git a/src/main/java/monero/wallet/MoneroWalletRpc.java b/src/main/java/monero/wallet/MoneroWalletRpc.java
index 0ecf6965..559d00b1 100644
--- a/src/main/java/monero/wallet/MoneroWalletRpc.java
+++ b/src/main/java/monero/wallet/MoneroWalletRpc.java
@@ -331,10 +331,11 @@ public MoneroWalletRpc openWallet(MoneroWalletConfig config) {
* privateSpendKey - private spend key of the wallet to create (optional)
* restoreHeight - block height to start scanning from (defaults to 0 unless generating random wallet)
* language - language of the wallet's seed (defaults to "English" or auto-detected)
+ * server - MoneroRpcConnection to a monero daemon (optional)
* serverUri - uri of the daemon to use (optional, monero-wallet-rpc usually started with daemon config)
* serverUsername - username to authenticate with the daemon (optional)
* serverPassword - password to authenticate with the daemon (optional)
- * server - MoneroRpcConnection providing server configuration (optional)
+ * connectionManager - manage connections to monerod (optional)
*
*
* @param config configures the wallet to create
@@ -346,7 +347,7 @@ public MoneroWalletRpc createWallet(MoneroWalletConfig config) {
if (config == null) throw new MoneroError("Must specify config to create wallet");
if (config.getNetworkType() != null) throw new MoneroError("Cannot specify network type when creating RPC wallet");
if (config.getSeed() != null && (config.getPrimaryAddress() != null || config.getPrivateViewKey() != null || config.getPrivateSpendKey() != null)) {
- throw new MoneroError("Wallet may be initialized with a seed or keys but not both");
+ throw new MoneroError("Wallet can be initialized with a seed or keys but not both");
}
if (config.getAccountLookahead() != null || config.getSubaddressLookahead() != null) throw new MoneroError("monero-wallet-rpc does not support creating wallets with subaddress lookahead over rpc");
@@ -355,8 +356,14 @@ public MoneroWalletRpc createWallet(MoneroWalletConfig config) {
else if (config.getPrivateSpendKey() != null || config.getPrimaryAddress() != null) createWalletFromKeys(config);
else createWalletRandom(config);
- // set daemon if provided
- if (config.getServer() != null) setDaemonConnection(config.getServer());
+ // set daemon or connection manager
+ if (config.getConnectionManager() != null) {
+ if (config.getServer() != null) throw new MoneroError("Wallet can be initialized with a server or connection manager but not both");
+ setConnectionManager(config.getConnectionManager());
+ } else if (config.getServer() != null) {
+ setDaemonConnection(config.getServer());
+ }
+
return this;
}
@@ -1903,6 +1910,7 @@ public void save() {
@Override
public void close(boolean save) {
+ super.close(save);
clear();
Map params = new HashMap();
params.put("autosave_current", save);
diff --git a/src/main/java/monero/wallet/model/MoneroWalletConfig.java b/src/main/java/monero/wallet/model/MoneroWalletConfig.java
index c561b836..3db4c508 100644
--- a/src/main/java/monero/wallet/model/MoneroWalletConfig.java
+++ b/src/main/java/monero/wallet/model/MoneroWalletConfig.java
@@ -1,8 +1,10 @@
package monero.wallet.model;
+import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonProperty;
import common.utils.JsonUtils;
+import monero.common.MoneroConnectionManager;
import monero.common.MoneroRpcConnection;
import monero.daemon.model.MoneroNetworkType;
@@ -17,6 +19,7 @@ public class MoneroWalletConfig {
private MoneroRpcConnection server;
private String serverUsername;
private String serverPassword;
+ private MoneroConnectionManager connectionManager;
private String seed;
private String seedOffset;
private String primaryAddress;
@@ -39,7 +42,8 @@ public MoneroWalletConfig(MoneroWalletConfig config) {
path = config.getPath();
password = config.getPassword();
networkType = config.getNetworkType();
- if (config.getServer() != null) server = new MoneroRpcConnection(config.getServer());
+ server = config.getServer();
+ connectionManager = config.getConnectionManager();
seed = config.getSeed();
seedOffset = config.getSeedOffset();
primaryAddress = config.getPrimaryAddress();
@@ -106,8 +110,11 @@ public String getServerUri() {
}
public MoneroWalletConfig setServerUri(String serverUri) {
- if (server == null) setServer(new MoneroRpcConnection(serverUri));
- else server.setUri(serverUri);
+ if (serverUri == null || serverUri.isEmpty()) setServer(null);
+ else {
+ if (server == null) setServer(new MoneroRpcConnection(serverUri));
+ else server.setUri(serverUri);
+ }
return this;
}
@@ -130,6 +137,16 @@ public MoneroWalletConfig setServerPassword(String serverPassword) {
if (serverUsername != null && serverPassword != null) server.setCredentials(serverUsername, serverPassword);
return this;
}
+
+ @JsonIgnore
+ public MoneroConnectionManager getConnectionManager() {
+ return connectionManager;
+ }
+
+ public MoneroWalletConfig setConnectionManager(MoneroConnectionManager connectionManager) {
+ this.connectionManager = connectionManager;
+ return this;
+ }
public String getSeed() {
return seed;
diff --git a/src/test/java/test/TestMoneroConnectionManager.java b/src/test/java/test/TestMoneroConnectionManager.java
index 308c342d..16561901 100644
--- a/src/test/java/test/TestMoneroConnectionManager.java
+++ b/src/test/java/test/TestMoneroConnectionManager.java
@@ -10,6 +10,7 @@
import java.util.ArrayList;
import java.util.List;
import monero.common.MoneroRpcConnection;
+import monero.common.MoneroConnectionManager.PollType;
import monero.common.MoneroConnectionManager;
import monero.common.MoneroConnectionManagerListener;
import monero.wallet.MoneroWalletRpc;
@@ -24,13 +25,14 @@ public class TestMoneroConnectionManager {
@Test
public void testConnectionManager() throws InterruptedException, IOException {
List walletRpcs = new ArrayList();
+ MoneroConnectionManager connectionManager = null;
try {
// start monero-wallet-rpc instances as test server connections (can also use monerod servers)
for (int i = 0; i < 5; i++) walletRpcs.add(TestUtils.startWalletRpcProcess());
// create connection manager
- MoneroConnectionManager connectionManager = new MoneroConnectionManager();
+ connectionManager = new MoneroConnectionManager();
// listen for changes
ConnectionChangeCollector listener = new ConnectionChangeCollector();
@@ -51,16 +53,20 @@ public void testConnectionManager() throws InterruptedException, IOException {
assertTrue(orderedConnections.get(3) == walletRpcs.get(0).getRpcConnection());
assertEquals(orderedConnections.get(4).getUri(), walletRpcs.get(1).getRpcConnection().getUri());
for (MoneroRpcConnection connection : orderedConnections) assertNull(connection.isOnline());
+
+ // test getting connection by uri
+ assertTrue(connectionManager.hasConnection(walletRpcs.get(0).getRpcConnection().getUri()));
+ assertTrue(connectionManager.getConnectionByUri(walletRpcs.get(0).getRpcConnection().getUri()) == walletRpcs.get(0).getRpcConnection());
// test unknown connection
int numExpectedChanges = 0;
connectionManager.setConnection(orderedConnections.get(0));
assertEquals(null, connectionManager.isConnected());
assertEquals(++numExpectedChanges, listener.changedConnections.size());
-
+
// auto connect to best available connection
- connectionManager.setAutoSwitch(true);
- connectionManager.startCheckingConnection(TestUtils.SYNC_PERIOD_IN_MS);
+ connectionManager.startPolling(TestUtils.SYNC_PERIOD_IN_MS);
+ GenUtils.waitFor(TestUtils.AUTO_CONNECT_TIMEOUT_MS);
assertTrue(connectionManager.isConnected());
MoneroRpcConnection connection = connectionManager.getConnection();
assertTrue(connection.isOnline());
@@ -68,13 +74,13 @@ public void testConnectionManager() throws InterruptedException, IOException {
assertEquals(++numExpectedChanges, listener.changedConnections.size());
assertTrue(listener.changedConnections.get(listener.changedConnections.size() - 1) == connection);
connectionManager.setAutoSwitch(false);
- connectionManager.stopCheckingConnection();
+ connectionManager.stopPolling();
connectionManager.disconnect();
assertEquals(++numExpectedChanges, listener.changedConnections.size());
assertTrue(listener.changedConnections.get(listener.changedConnections.size() - 1) == null);
- // start periodically checking connection
- connectionManager.startCheckingConnection(TestUtils.SYNC_PERIOD_IN_MS);
+ // start periodically checking connection without auto switch
+ connectionManager.startPolling(TestUtils.SYNC_PERIOD_IN_MS, false, null, null, null);
// connect to best available connection in order of priority and response time
connection = connectionManager.getBestAvailableConnection();
@@ -108,10 +114,10 @@ public void testConnectionManager() throws InterruptedException, IOException {
// test connection order
orderedConnections = connectionManager.getConnections();
assertTrue(orderedConnections.get(0) == walletRpcs.get(4).getRpcConnection());
- assertTrue(orderedConnections.get(1) == walletRpcs.get(2).getRpcConnection());
- assertTrue(orderedConnections.get(2) == walletRpcs.get(3).getRpcConnection());
- assertTrue(orderedConnections.get(3) == walletRpcs.get(0).getRpcConnection());
- assertEquals(orderedConnections.get(4).getUri(), walletRpcs.get(1).getRpcConnection().getUri());
+ assertTrue(orderedConnections.get(1) == walletRpcs.get(0).getRpcConnection());
+ assertEquals(orderedConnections.get(2).getUri(), walletRpcs.get(1).getRpcConnection().getUri());
+ assertTrue(orderedConnections.get(3) == walletRpcs.get(2).getRpcConnection());
+ assertTrue(orderedConnections.get(4) == walletRpcs.get(3).getRpcConnection());
// check all connections
connectionManager.checkConnections();
@@ -192,7 +198,7 @@ public void testConnectionManager() throws InterruptedException, IOException {
assertTrue(listener.changedConnections.get(listener.changedConnections.size() - 1) == walletRpcs.get(0).getRpcConnection());
// set connection to new uri
- connectionManager.stopCheckingConnection();
+ connectionManager.stopPolling();
String uri = "http://localhost:49999";
connectionManager.setConnection(uri);
assertEquals(uri, connectionManager.getConnection().getUri());
@@ -217,10 +223,26 @@ public void testConnectionManager() throws InterruptedException, IOException {
connectionManager.checkConnections();
assertEquals(++numExpectedChanges, listener.changedConnections.size());
assertTrue(connectionManager.isConnected());
-
+
+ // test polling current connection
+ connectionManager.setConnection((String) null);
+ assertFalse(connectionManager.isConnected());
+ assertEquals(++numExpectedChanges, listener.changedConnections.size());
+ connectionManager.startPolling(TestUtils.SYNC_PERIOD_IN_MS, null, null, PollType.CURRENT, null);
+ GenUtils.waitFor(TestUtils.AUTO_CONNECT_TIMEOUT_MS);
+ assertTrue(connectionManager.isConnected());
+ assertEquals(++numExpectedChanges, listener.changedConnections.size());
+
+ // test polling all connections
+ connectionManager.setConnection((String) null);
+ assertEquals(++numExpectedChanges, listener.changedConnections.size());
+ connectionManager.startPolling(TestUtils.SYNC_PERIOD_IN_MS, null, null, PollType.ALL, null);
+ GenUtils.waitFor(TestUtils.AUTO_CONNECT_TIMEOUT_MS);
+ assertTrue(connectionManager.isConnected());
+ assertEquals(++numExpectedChanges, listener.changedConnections.size());
+
// shut down all connections
connection = connectionManager.getConnection();
- connectionManager.startCheckingConnection(TestUtils.SYNC_PERIOD_IN_MS);
for (MoneroWalletRpc walletRpc : walletRpcs) TestUtils.stopWalletRpcProcess(walletRpc);
GenUtils.waitFor(TestUtils.SYNC_PERIOD_IN_MS + 100);
assertFalse(connection.isOnline());
@@ -232,6 +254,9 @@ public void testConnectionManager() throws InterruptedException, IOException {
assertEquals(0, connectionManager.getConnections().size());
assertEquals(null, connectionManager.getConnection());
} finally {
+
+ // stop connection manager
+ if (connectionManager != null) connectionManager.reset();
// stop monero-wallet-rpc instances
for (MoneroWalletRpc walletRpc : walletRpcs) {
diff --git a/src/test/java/test/TestMoneroWalletCommon.java b/src/test/java/test/TestMoneroWalletCommon.java
index 0bbeb9f1..e8f18512 100644
--- a/src/test/java/test/TestMoneroWalletCommon.java
+++ b/src/test/java/test/TestMoneroWalletCommon.java
@@ -25,6 +25,8 @@
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
+
+import monero.common.MoneroConnectionManager;
import monero.common.MoneroError;
import monero.common.MoneroRpcConnection;
import monero.common.MoneroUtils;
@@ -385,34 +387,34 @@ public void testCreateWalletFromKeys() {
if (e1 != null) throw new RuntimeException(e1);
}
-
- // Can create wallets with subaddress lookahead
- @Test
- public void testSubaddressLookahead() {
- assumeTrue(TEST_NON_RELAYS);
- Exception e1 = null; // emulating Java "finally" but compatible with other languages
- MoneroWallet receiver = null;
- try {
+
+ // Can create wallets with subaddress lookahead
+ @Test
+ public void testSubaddressLookahead() {
+ assumeTrue(TEST_NON_RELAYS);
+ Exception e1 = null; // emulating Java "finally" but compatible with other languages
+ MoneroWallet receiver = null;
+ try {
- // create wallet with high subaddress lookahead
- receiver = createWallet(new MoneroWalletConfig().setAccountLookahead(1).setSubaddressLookahead(100000));
+ // create wallet with high subaddress lookahead
+ receiver = createWallet(new MoneroWalletConfig().setAccountLookahead(1).setSubaddressLookahead(100000));
- // transfer funds to subaddress with high index
- wallet.createTx(new MoneroTxConfig()
- .setAccountIndex(0)
- .addDestination(receiver.getSubaddress(0, 85000).getAddress(), TestUtils.MAX_FEE)
- .setRelay(true));
+ // transfer funds to subaddress with high index
+ wallet.createTx(new MoneroTxConfig()
+ .setAccountIndex(0)
+ .addDestination(receiver.getSubaddress(0, 85000).getAddress(), TestUtils.MAX_FEE)
+ .setRelay(true));
- // observe unconfirmed funds
- GenUtils.waitFor(1000);
- receiver.sync();
- assert(receiver.getBalance().compareTo(new BigInteger("0")) > 0);
- } catch (Exception e) {
- e1 = e;
- }
+ // observe unconfirmed funds
+ GenUtils.waitFor(1000);
+ receiver.sync();
+ assert(receiver.getBalance().compareTo(new BigInteger("0")) > 0);
+ } catch (Exception e) {
+ e1 = e;
+ }
- if (receiver != null) closeWallet(receiver);
- if (e1 != null) throw new RuntimeException(e1);
+ if (receiver != null) closeWallet(receiver);
+ if (e1 != null) throw new RuntimeException(e1);
}
// Can get the wallet's version
@@ -507,6 +509,56 @@ public void testSetDaemonConnection() {
}
}
+ // Can use a connection manager
+ @Test
+ public void testConnectionManager() {
+
+ // create connection manager with monerod connections
+ MoneroConnectionManager connectionManager = new MoneroConnectionManager();
+ MoneroRpcConnection connection1 = new MoneroRpcConnection(TestUtils.getDaemonRpc().getRpcConnection()).setPriority(1);
+ MoneroRpcConnection connection2 = new MoneroRpcConnection("localhost:48081").setPriority(2);
+ connectionManager.setConnection(connection1);
+ connectionManager.addConnection(connection2);
+
+ // create wallet with connection manager
+ MoneroWallet wallet = createWallet(new MoneroWalletConfig().setServerUri("").setConnectionManager(connectionManager));
+ assertEquals(TestUtils.getDaemonRpc().getRpcConnection(), wallet.getDaemonConnection());
+ assertTrue(wallet.isConnectedToDaemon());
+
+ // set manager's connection
+ connectionManager.setConnection(connection2);
+ GenUtils.waitFor(TestUtils.AUTO_CONNECT_TIMEOUT_MS);
+ assertEquals(connection2, wallet.getDaemonConnection());
+
+ // disconnect
+ connectionManager.setConnection((String) null);
+ assertEquals(null, wallet.getDaemonConnection());
+ assertFalse(wallet.isConnectedToDaemon());
+
+ // start polling connections
+ connectionManager.startPolling(TestUtils.SYNC_PERIOD_IN_MS);
+
+ // test that wallet auto connects
+ GenUtils.waitFor(TestUtils.AUTO_CONNECT_TIMEOUT_MS);
+ assertEquals(connection1, wallet.getDaemonConnection());
+ assertTrue(wallet.isConnectedToDaemon());
+
+ // set to another connection manager
+ MoneroConnectionManager connectionManager2 = new MoneroConnectionManager();
+ connectionManager2.setConnection(connection2);
+ wallet.setConnectionManager(connectionManager2);
+ assertEquals(connection2, wallet.getDaemonConnection());
+
+ // unset connection manager
+ wallet.setConnectionManager(null);
+ assertEquals(null, wallet.getConnectionManager());
+ assertEquals(connection2, wallet.getDaemonConnection());
+
+ // stop polling and close
+ connectionManager.stopPolling();
+ closeWallet(wallet);
+ }
+
// Can get the seed
@Test
public void testGetSeed() {
diff --git a/src/test/java/test/TestMoneroWalletFull.java b/src/test/java/test/TestMoneroWalletFull.java
index 4574fff2..e5a39a20 100644
--- a/src/test/java/test/TestMoneroWalletFull.java
+++ b/src/test/java/test/TestMoneroWalletFull.java
@@ -108,7 +108,7 @@ protected MoneroWalletFull createWallet(MoneroWalletConfig config, boolean start
if (config.getPath() == null) config.setPath(TestUtils.TEST_WALLETS_DIR + "/" + UUID.randomUUID().toString());
if (config.getPassword() == null) config.setPassword(TestUtils.WALLET_PASSWORD);
if (config.getNetworkType() == null) config.setNetworkType(TestUtils.NETWORK_TYPE);
- if (config.getServer() == null) config.setServer(daemon.getRpcConnection());
+ if (config.getServer() == null && config.getConnectionManager() == null) config.setServer(daemon.getRpcConnection());
if (config.getRestoreHeight() == null && !random) config.setRestoreHeight(0l);
// create wallet
@@ -1553,6 +1553,11 @@ public void testSetDaemonConnection() {
super.testSetDaemonConnection();
}
+ @Test
+ public void testConnectionManager() {
+ super.testConnectionManager();
+ }
+
@Override
@Test
public void testGetHeight() {
diff --git a/src/test/java/test/TestMoneroWalletRpc.java b/src/test/java/test/TestMoneroWalletRpc.java
index 5d78dde2..a5f9d724 100644
--- a/src/test/java/test/TestMoneroWalletRpc.java
+++ b/src/test/java/test/TestMoneroWalletRpc.java
@@ -97,7 +97,7 @@ protected MoneroWalletRpc openWallet(MoneroWalletConfig config) {
// open wallet
try {
wallet.openWallet(config.getPath(), config.getPassword());
- wallet.setDaemonConnection(config.getServer(), true, null); // set daemon as trusted
+ wallet.setDaemonConnection(wallet.getDaemonConnection(), true, null); // set daemon as trusted
if (wallet.isConnectedToDaemon()) wallet.startSyncing(TestUtils.SYNC_PERIOD_IN_MS);
return wallet;
} catch (MoneroError e) {
@@ -115,17 +115,16 @@ protected MoneroWalletRpc createWallet(MoneroWalletConfig config) {
if (config.getPath() == null) config.setPath(UUID.randomUUID().toString());
if (config.getPassword() == null) config.setPassword(TestUtils.WALLET_PASSWORD);
if (config.getRestoreHeight() == null && !random) config.setRestoreHeight(0l);
- if (config.getServer() == null) config.setServer(daemon.getRpcConnection());
+ if (config.getServer() == null && config.getConnectionManager() == null) config.setServer(daemon.getRpcConnection());
// create client connected to internal monero-wallet-rpc process
- boolean offline = config.getServerUri().equals(MoneroUtils.parseUri(TestUtils.OFFLINE_SERVER_URI).toString());
+ boolean offline = MoneroUtils.parseUri(TestUtils.OFFLINE_SERVER_URI).toString().equals(config.getServerUri());
MoneroWalletRpc wallet = TestUtils.startWalletRpcProcess(offline);
// create wallet
try {
wallet.createWallet(config);
- System.out.println("Setting server: " + config.getServer());
- wallet.setDaemonConnection(config.getServer(), true, null); // set daemon as trusted
+ wallet.setDaemonConnection(wallet.getDaemonConnection(), true, null); // set daemon as trusted
if (wallet.isConnectedToDaemon()) wallet.startSyncing(TestUtils.SYNC_PERIOD_IN_MS);
return wallet;
} catch (MoneroError e) {
@@ -494,6 +493,11 @@ public void testSetDaemonConnection() {
super.testSetDaemonConnection();
}
+ @Test
+ public void testConnectionManager() {
+ super.testConnectionManager();
+ }
+
@Override
@Test
public void testGetHeight() {
diff --git a/src/test/java/test/TestSampleCode.java b/src/test/java/test/TestSampleCode.java
index 9e0dc2d6..82e88b35 100644
--- a/src/test/java/test/TestSampleCode.java
+++ b/src/test/java/test/TestSampleCode.java
@@ -14,6 +14,7 @@
import monero.daemon.MoneroDaemonRpc;
import monero.daemon.model.MoneroNetworkType;
import monero.daemon.model.MoneroTx;
+import monero.wallet.MoneroWallet;
import monero.wallet.MoneroWalletFull;
import monero.wallet.MoneroWalletRpc;
import monero.wallet.model.MoneroOutputWallet;
@@ -131,6 +132,15 @@ public void testConnectionManagerDemo() {
// set current connection
connectionManager.setConnection(new MoneroRpcConnection("http://foo.bar", "admin", "password")); // connection is added if new
+
+ // create wallet with managed connections or set later
+ MoneroWalletFull walletFull = MoneroWalletFull.createWallet(new MoneroWalletConfig()
+ .setPath("./test_wallets/" + UUID.randomUUID().toString()) // *** CHANGE README TO "sample_wallet_full" ***
+ .setPassword("supersecretpassword123")
+ .setNetworkType(MoneroNetworkType.TESTNET)
+ .setConnectionManager(connectionManager)
+ .setSeed(TestUtils.SEED) // *** REPLACE WITH SEED IN README ***
+ .setRestoreHeight(TestUtils.FIRST_RECEIVE_HEIGHT)); // *** REPLACE WITH FIRST RECEIVE HEIGHT IN README ***
// check connection status
connectionManager.checkConnection();
@@ -146,11 +156,8 @@ public void onConnectionChanged(MoneroRpcConnection connection) {
}
});
- // check connection status every 10 seconds
- connectionManager.startCheckingConnection(10000l);
-
- // automatically switch to best available connection if disconnected
- connectionManager.setAutoSwitch(true);
+ // check connections every 10 seconds (in order of priority) and switch to the best
+ connectionManager.startPolling(10000l);
// get best available connection in order of priority then response time
MoneroRpcConnection bestConnection = connectionManager.getBestAvailableConnection();
diff --git a/src/test/java/utils/TestUtils.java b/src/test/java/utils/TestUtils.java
index 08890951..c360b57f 100644
--- a/src/test/java/utils/TestUtils.java
+++ b/src/test/java/utils/TestUtils.java
@@ -82,6 +82,7 @@ public class TestUtils {
public static final long FIRST_RECEIVE_HEIGHT = 171; // NOTE: this value must be the height of the wallet's first tx for tests
public static final long SYNC_PERIOD_IN_MS = 5000; // period between wallet syncs in milliseconds
public static final String OFFLINE_SERVER_URI = "offline_server_uri"; // dummy server uri to remain offline because wallet2 connects to default if not given
+ public static final long AUTO_CONNECT_TIMEOUT_MS = 1000;
// logger configuration
public static final Logger LOGGER = Logger.getLogger(TestUtils.class.getName());