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