From c3cda161c293d2ddd04de13260253d7e5c74ed35 Mon Sep 17 00:00:00 2001 From: woodser <13068859+woodser@users.noreply.github.com> Date: Fri, 28 Aug 2026 08:16:01 -0400 Subject: [PATCH 1/5] test: update offline wallet expectations for ungated daemon calls getDaemonHeight surfaces wallet2's natural connection error and startSyncing succeeds offline like monero-wallet-rpc's auto_refresh. --- src/test/java/TestMoneroWalletFull.java | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/test/java/TestMoneroWalletFull.java b/src/test/java/TestMoneroWalletFull.java index 4275b2ba..8c77fc2a 100644 --- a/src/test/java/TestMoneroWalletFull.java +++ b/src/test/java/TestMoneroWalletFull.java @@ -251,8 +251,9 @@ public void testCreateWalletRandomFull() { // cannot get daemon chain height try { wallet.getDaemonHeight(); + fail("Should have thrown exception"); } catch (MoneroError e) { - assertEquals("Wallet is not connected to daemon", e.getMessage()); + assertEquals("daemon error", e.getMessage()); // wallet2 masks errors from untrusted daemons } // set daemon connection and check chain height @@ -307,7 +308,7 @@ public void testCreateWalletFromSeedFull() { assertFalse(wallet.isSynced()); assertEquals(1, wallet.getHeight()); assertEquals(0, wallet.getRestoreHeight()); - try { wallet.startSyncing(); } catch (MoneroError e) { assertEquals("Wallet is not connected to daemon", e.getMessage()); } + wallet.startSyncing(); // succeeds while offline, syncing when a daemon becomes reachable wallet.close(); // create wallet without restore height @@ -771,9 +772,7 @@ public void testStartStopSyncing() { assertNotNull(wallet.getSeed()); assertEquals(1, wallet.getHeight()); assertEquals(BigInteger.valueOf(0), wallet.getBalance()); - wallet.startSyncing(); - } catch (MoneroError e) { - assertEquals("Wallet is not connected to daemon", e.getMessage()); + wallet.startSyncing(); // succeeds while offline, syncing when a daemon becomes reachable } finally { wallet.close(); } From a6cd6890f46a4e20af47f2cef9f15b49b4073d5b Mon Sep 17 00:00:00 2001 From: woodser <13068859+woodser@users.noreply.github.com> Date: Fri, 28 Aug 2026 10:39:22 -0400 Subject: [PATCH 2/5] common: exit task looper under lock so restarts cannot reuse a dead loop A restart while a loop drained its final task or died interrupted could see it as alive and not spawn a new loop, leaving the looper dead. --- src/main/java/monero/common/TaskLooper.java | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/src/main/java/monero/common/TaskLooper.java b/src/main/java/monero/common/TaskLooper.java index 0afd82f8..a2d24c3a 100644 --- a/src/main/java/monero/common/TaskLooper.java +++ b/src/main/java/monero/common/TaskLooper.java @@ -54,15 +54,23 @@ public synchronized TaskLooper start(long periodInMs, boolean targetFixedPeriod) if (isStarted) return this; isStarted = true; - // start looping + // reuse a live loop, which observes isStarted under lock and continues if (isLooping) return this; isLooping = true; TaskLooper that = this; Thread loop = new Thread(new Runnable() { @Override public void run() { - while (isStarted && !Thread.currentThread().isInterrupted()) { - + while (true) { + + // decide to exit and clear isLooping atomically so a restart cannot reuse a dead loop + synchronized (that) { + if (!isStarted || Thread.currentThread().isInterrupted()) { + isLooping = false; + return; + } + } + // run the task long startTime = System.currentTimeMillis(); task.run(); @@ -71,12 +79,12 @@ public void run() { if (isStarted) { try { TimeUnit.MILLISECONDS.sleep(that.periodInMs - (targetFixedPeriod ? System.currentTimeMillis() - startTime : 0)); } // target fixed period by accounting for run time catch (Exception e) { - isLooping = false; + synchronized (that) { isLooping = false; } if (isStarted) throw new RuntimeException(e); + return; } } } - isLooping = false; } }); loop.start(); From cc87ba6c32e5cb719aab5f3ae0bffbbe96f0a6d2 Mon Sep 17 00:00:00 2001 From: woodser <13068859+woodser@users.noreply.github.com> Date: Fri, 28 Aug 2026 20:49:45 -0400 Subject: [PATCH 3/5] build: bump version to 0.8.56 --- README.md | 4 ++-- pom.xml | 2 +- src/main/java/monero/common/MoneroUtils.java | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index c076744f..391493eb 100644 --- a/README.md +++ b/README.md @@ -97,7 +97,7 @@ walletFull.close(true); #### For Gradle, add to build.gradle: -`compile 'io.github.woodser:monero-java:0.8.55'` +`compile 'io.github.woodser:monero-java:0.8.56'` #### For Maven, add to pom.xml: @@ -105,7 +105,7 @@ walletFull.close(true); io.github.woodser monero-java - 0.8.55 + 0.8.56 ``` diff --git a/pom.xml b/pom.xml index 51a31d74..387b765a 100644 --- a/pom.xml +++ b/pom.xml @@ -4,7 +4,7 @@ 4.0.0 io.github.woodser monero-java - 0.8.55 + 0.8.56 Monero Java Library A Java library for using Monero https://github.com/woodser/monero-java diff --git a/src/main/java/monero/common/MoneroUtils.java b/src/main/java/monero/common/MoneroUtils.java index ad397f0c..e8593ac5 100644 --- a/src/main/java/monero/common/MoneroUtils.java +++ b/src/main/java/monero/common/MoneroUtils.java @@ -42,7 +42,7 @@ public class MoneroUtils { * @return the version of this monero-java library */ public static String getVersion() { - return "0.8.55"; + return "0.8.56"; } /** From 226604a1731f65666806313a29c1ae4da459e1f5 Mon Sep 17 00:00:00 2001 From: woodser <13068859+woodser@users.noreply.github.com> Date: Sat, 29 Aug 2026 07:17:48 -0400 Subject: [PATCH 4/5] wallet: announce missed confirm transition when tx unlocks between polls The confirm notification was silently skipped when a tx confirmed and unlocked within one poll period, such as during fast mining. --- src/main/java/monero/wallet/MoneroWalletRpc.java | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/main/java/monero/wallet/MoneroWalletRpc.java b/src/main/java/monero/wallet/MoneroWalletRpc.java index 26e87934..cb6b2604 100644 --- a/src/main/java/monero/wallet/MoneroWalletRpc.java +++ b/src/main/java/monero/wallet/MoneroWalletRpc.java @@ -2421,8 +2421,14 @@ public void poll() { // announce new unlocked outputs for (MoneroTxWallet unlockedTx : unlockedTxs) { + boolean missedConfirm = Boolean.TRUE.equals(unlockedTx.isConfirmed()) && !prevConfirmedNotifications.contains(unlockedTx.getHash()); prevUnconfirmedNotifications.remove(unlockedTx.getHash()); // stop tracking tx notifications prevConfirmedNotifications.remove(unlockedTx.getHash()); + if (missedConfirm) { // announce missed confirm transition if tx unlocked between polls + MoneroTxWallet confirmedTx = unlockedTx.copy().setIsLocked(true); + confirmedTx.setBlock(unlockedTx.getBlock().copy().setTxs(confirmedTx)); + notifyOutputs(confirmedTx); + } notifyOutputs(unlockedTx); } From 2fac5f002bf8cc97fd9915d8b0edb37f6cce4e5d Mon Sep 17 00:00:00 2001 From: woodser <13068859+woodser@users.noreply.github.com> Date: Thu, 27 Aug 2026 12:38:38 -0400 Subject: [PATCH 5/5] build: update monero-cpp submodule Adapt JNI bridge to monero_tx_set::deserialize returning a shared pointer. --- external/monero-cpp | 2 +- src/main/cpp/monero_jni_bridge.cpp | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/external/monero-cpp b/external/monero-cpp index 8db4afab..6927f7bf 160000 --- a/external/monero-cpp +++ b/external/monero-cpp @@ -1 +1 @@ -Subproject commit 8db4afab28f394f3dc78de6b4c2d3473484ce1b9 +Subproject commit 6927f7bf52dabad2179dfe2a0a0e1a95e12290c1 diff --git a/src/main/cpp/monero_jni_bridge.cpp b/src/main/cpp/monero_jni_bridge.cpp index d7d22623..a2bed5a1 100644 --- a/src/main/cpp/monero_jni_bridge.cpp +++ b/src/main/cpp/monero_jni_bridge.cpp @@ -1428,10 +1428,10 @@ JNIEXPORT jstring JNICALL Java_monero_wallet_MoneroWalletFull_describeTxSetJni(J try { // deserialize tx set to describe - monero_tx_set tx_set = monero_tx_set::deserialize(tx_set_json); + shared_ptr tx_set = monero_tx_set::deserialize(tx_set_json); // describe tx set - monero_tx_set described_tx_set = wallet->describe_tx_set(tx_set); + monero_tx_set described_tx_set = wallet->describe_tx_set(*tx_set); // serialize, free, and return std::string monero_tx_set_json = described_tx_set.serialize();