update to monero-project v0.18.4.4 and update tests
What changed, and why it matters
This commit updates the monero-java library to support monero-project v0.18.4.4, bumps the version, refreshes documentation, and makes small code changes in how transaction hexes are returned and how wallet RPC connection checks behave. Most of the diff is test infrastructure changes (renaming helper methods, improving wallet synchronization, and adding rescan calls). There is no clear security fix or vulnerability being patched. The changes appear to be routine compatibility and test reliability improvements.
Treat as a routine version-bump and compatibility update. Review the small production changes in MoneroDaemonRpc.getTxHexes() and MoneroWalletRpc.isConnectedToDaemon() for API/behavior impact in downstream applications. Run the updated test suite to confirm wallet synchronization improvements. No urgent security action is indicated based solely on the supplied materials.
Security signals we found
Behavior change in getTxHexes(): now returns pruned hex by default when available, which could affect consumers expecting full hex
isConnectedToDaemon() now propagates error code -13 (no wallet file) instead of returning false
Failed outgoing transactions now correctly marked isRelayed=false
Test helper changes improve synchronization reliability but are not production code
No explicit security fix, CVE reference, or vendor security statement in commit or references
Evidence from the diff
The commit updates the monero-cpp submodule reference, README version strings, and regenerated PDF documentation. In MoneroDaemonRpc.java, getTxHexes() now prefers tx.getPrunedHex() if available, falling back to tx.getFullHex(), with a comment noting that transactions may be pruned regardless of configuration. In MoneroWalletRpc.java, the isConnectedToDaemon() check now rethrows MoneroError code -13 (no wallet file) instead of swallowing it, and a failed outgoing transaction is now marked as isRelayed=false instead of true. The bulk of the diff refactors test utilities: WalletTxTracker replaces waitForWalletTxsToClearPool with waitForTxsToClearPool/waitForTxsToClearWallets, TestUtils gains openWalletRpc/createWalletRpc helpers, and many tests add rescans or adjust balance expectations. No CVE, advisory, or vendor security disclosure is present in the materials.
Changed components
src/main/java/monero/daemon/MoneroDaemonRpc.javasrc/main/java/monero/wallet/MoneroWalletRpc.javaexternal/monero-cpp submoduleREADME.mddocs/monero-spec.drawiodocs/monero-spec.drawio.pdfsrc/test/java/* (multiple test and utility files)Inspect captured patch +305 / −263
diff --git a/README.md b/README.md
index 27f815b..146cd61 100644
--- a/README.md
+++ b/README.md
@@ -1,6 +1,6 @@
# Monero Java Library
-A Java library for creating Monero applications using RPC and JNI bindings to [monero v0.18.4.3 'Fluorine Fermi'](https://github.com/monero-project/monero/tree/v0.18.4.3).
+A Java library for creating Monero applications using RPC and JNI bindings to [monero v0.18.4.4 'Fluorine Fermi'](https://github.com/monero-project/monero/tree/v0.18.4.4).
* Supports wallet and daemon RPC clients.
* Supports client-side wallets using JNI bindings.
@@ -101,7 +101,7 @@ walletFull.close(true);
<dependency>
<groupId>io.github.woodser</groupId>
<artifactId>monero-java</artifactId>
- <version>0.8.9</version>
+ <version>0.8.40</version>
</dependency>
```
diff --git a/docs/monero-spec.drawio b/docs/monero-spec.drawio
index cedb18c..afecb9a 100644
--- a/docs/monero-spec.drawio
+++ b/docs/monero-spec.drawio
@@ -1901,8 +1901,8 @@
<mxCell id="e5HWKQ1rQ-4_89Y86yYL-12" value="<p style="line-height: 240% ; font-size: 24px"></p><div style="font-size: 25px"><font style="font-size: 25px">with implementations in Java, TypeScript, and C++&nbsp;</font></div><p style="font-size: 24px"></p>" style="text;html=1;resizable=0;points=[];autosize=1;align=center;verticalAlign=top;spacingTop=-4;fontStyle=2;fontSize=24;spacing=2;" parent="1" vertex="1">
<mxGeometry x="133.74999999999946" y="-872" width="582" height="81" as="geometry" />
</mxCell>
- <mxCell id="e5HWKQ1rQ-4_89Y86yYL-14" value="<p style="line-height: 0% ; font-size: 19px"></p><div style="font-size: 19px"><span style="font-size: 19px"><span style="font-size: 19px">Last updated October 14, 2025</span></span><br style="font-size: 19px"></div><p style="font-size: 19px"></p>" style="text;html=1;resizable=0;points=[];autosize=1;align=center;verticalAlign=top;spacingTop=-4;fontStyle=0;fontSize=19;spacing=2;" parent="1" vertex="1">
- <mxGeometry x="286.74999999999966" y="-756" width="280" height="67" as="geometry" />
+ <mxCell id="e5HWKQ1rQ-4_89Y86yYL-14" value="<p style="line-height: 0% ; font-size: 19px"></p><div style="font-size: 19px"><span style="font-size: 19px"><span style="font-size: 19px">Last updated November 24, 2025</span></span><br style="font-size: 19px"></div><p style="font-size: 19px"></p>" style="text;html=1;resizable=0;points=[];autosize=1;align=center;verticalAlign=top;spacingTop=-4;fontStyle=0;fontSize=19;spacing=2;" parent="1" vertex="1">
+ <mxGeometry x="276.74999999999966" y="-756" width="300" height="67" as="geometry" />
</mxCell>
<mxCell id="e5HWKQ1rQ-4_89Y86yYL-15" value="" style="shape=image;verticalLabelPosition=bottom;labelBackgroundColor=#ffffff;verticalAlign=top;aspect=fixed;imageAspect=0;image=data:image/png,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;" parent="1" vertex="1">
<mxGeometry x="373" y="13360" width="100" height="100" as="geometry" />
@@ -2074,7 +2074,7 @@
<mxCell id="skZ1SUWrHV_8W1a_cNyf-37" value="i2p: 4" style="text;strokeColor=none;fillColor=none;align=left;verticalAlign=top;spacingLeft=4;spacingRight=4;overflow=hidden;rotatable=0;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;fontFamily=Verdana;" parent="skZ1SUWrHV_8W1a_cNyf-32" vertex="1">
<mxGeometry y="154" width="211" height="28" as="geometry" />
</mxCell>
- <mxCell id="Whbs5CVNDTOYxdUCeosP-1" value="<p style="line-height: 240% ; font-size: 24px"></p><div style="font-size: 25px"><span>using monero v0.18.4.3 'Flourine Fermi'</span></div><span style="font-size: 25px"><br></span><p style="font-size: 24px"></p>" style="text;html=1;resizable=0;points=[];autosize=1;align=center;verticalAlign=top;spacingTop=-4;fontStyle=2;fontSize=24;spacing=2;" parent="1" vertex="1">
+ <mxCell id="Whbs5CVNDTOYxdUCeosP-1" value="<p style="line-height: 240% ; font-size: 24px"></p><div style="font-size: 25px"><span>using monero v0.18.4.4 'Flourine Fermi'</span></div><span style="font-size: 25px"><br></span><p style="font-size: 24px"></p>" style="text;html=1;resizable=0;points=[];autosize=1;align=center;verticalAlign=top;spacingTop=-4;fontStyle=2;fontSize=24;spacing=2;" parent="1" vertex="1">
<mxGeometry x="197.74999999999966" y="-835" width="457" height="114" as="geometry" />
</mxCell>
<mxCell id="UTYBNJX3NSuSBxuMePmN-21" value="" style="group" parent="1" vertex="1" connectable="0">
diff --git a/docs/monero-spec.drawio.pdf b/docs/monero-spec.drawio.pdf
new file mode 100644
index 0000000..874c235
Binary files /dev/null and b/docs/monero-spec.drawio.pdf differ
diff --git a/src/main/java/monero/daemon/MoneroDaemonRpc.java b/src/main/java/monero/daemon/MoneroDaemonRpc.java
index b27eb8e..5a3141e 100644
--- a/src/main/java/monero/daemon/MoneroDaemonRpc.java
+++ b/src/main/java/monero/daemon/MoneroDaemonRpc.java
@@ -532,7 +532,9 @@ public class MoneroDaemonRpc extends MoneroDaemonDefault {
@Override
public List<String> getTxHexes(Collection<String> txHashes, Boolean prune) {
List<String> hexes = new ArrayList<String>();
- for (MoneroTx tx : getTxs(txHashes, prune)) hexes.add(Boolean.TRUE.equals(prune) ? tx.getPrunedHex() : tx.getFullHex());
+ for (MoneroTx tx : getTxs(txHashes, prune)) {
+ hexes.add(tx.getPrunedHex() == null ? tx.getFullHex() : tx.getPrunedHex()); // tx may be pruned regardless of configuration
+ }
return hexes;
}
diff --git a/src/main/java/monero/wallet/MoneroWalletRpc.java b/src/main/java/monero/wallet/MoneroWalletRpc.java
index c089a47..8f685c4 100644
--- a/src/main/java/monero/wallet/MoneroWalletRpc.java
+++ b/src/main/java/monero/wallet/MoneroWalletRpc.java
@@ -524,6 +524,7 @@ public class MoneroWalletRpc extends MoneroWalletDefault {
checkReserveProof(getPrimaryAddress(), "", ""); // TODO (monero-project): provide better way to know if wallet rpc is connected to daemon
throw new RuntimeException("check reserve expected to fail");
} catch (MoneroError e) {
+ if (e.getCode() == -13) throw e; // no wallet file
return !e.getMessage().contains("Failed to connect to daemon");
}
}
@@ -3186,7 +3187,7 @@ public class MoneroWalletRpc extends MoneroWalletDefault {
isOutgoing = true;
tx.setIsConfirmed(false);
tx.setInTxPool(false);
- tx.setIsRelayed(true);
+ tx.setIsRelayed(false);
tx.setRelay(true);
tx.setIsFailed(true);
tx.setIsMinerTx(false);
diff --git a/src/test/java/TestMoneroDaemonRpc.java b/src/test/java/TestMoneroDaemonRpc.java
index 15d379e..595a366 100644
--- a/src/test/java/TestMoneroDaemonRpc.java
+++ b/src/test/java/TestMoneroDaemonRpc.java
@@ -97,7 +97,6 @@ public class TestMoneroDaemonRpc {
public static void beforeAll() throws Exception {
daemon = TestUtils.getDaemonRpc();
wallet = TestUtils.getWalletRpc();
- TestUtils.WALLET_TX_TRACKER.reset(); // all wallets need to wait for txs to confirm to reliably sync
}
@BeforeEach
@@ -492,7 +491,7 @@ public class TestMoneroDaemonRpc {
@Test
public void testGetTxsByHashesInPool() {
assumeTrue(TEST_NON_RELAYS);
- TestUtils.WALLET_TX_TRACKER.waitForWalletTxsToClearPool(wallet); // wait for wallet's txs in the pool to clear to ensure reliable sync
+ TestUtils.WALLET_TX_TRACKER.waitForTxsToClearPool(wallet); // wait for wallet's txs in the pool to clear to ensure reliable sync
// submit txs to the pool but don't relay
List<String> txHashes = new ArrayList<String>();
@@ -528,7 +527,7 @@ public class TestMoneroDaemonRpc {
// Can get a transaction hex by hash with and without pruning
@Test
- public void getTxHexByHash() {
+ public void testGetTxHexByHash() {
assumeTrue(TEST_NON_RELAYS);
// fetch transaction hashes to test
@@ -549,7 +548,7 @@ public class TestMoneroDaemonRpc {
assertNotNull(hexes.get(i));
assertNotNull(hexesPruned.get(i));
assertFalse(hexesPruned.isEmpty());
- assertTrue(hexes.get(i).length() > hexesPruned.get(i).length()); // pruned hex is shorter
+ assertTrue(hexes.get(i).length() >= hexesPruned.get(i).length()); // pruned hex is shorter
}
// fetch invalid hash
@@ -616,7 +615,7 @@ public class TestMoneroDaemonRpc {
@Test
public void testGetTxsInPool() {
assumeTrue(TEST_NON_RELAYS);
- TestUtils.WALLET_TX_TRACKER.waitForWalletTxsToClearPool(wallet);
+ TestUtils.WALLET_TX_TRACKER.waitForTxsToClearPool(wallet);
// submit tx to pool but don't relay
MoneroTx tx = getUnrelayedTx(wallet, 1);
@@ -664,7 +663,7 @@ public class TestMoneroDaemonRpc {
@Test
public void testGetTxPoolStatistics() {
assumeTrue(TEST_NON_RELAYS);
- TestUtils.WALLET_TX_TRACKER.waitForWalletTxsToClearPool(wallet);
+ TestUtils.WALLET_TX_TRACKER.waitForTxsToClearPool(wallet);
Throwable err = null;
Collection<String> txIds = new HashSet<String>();
try {
@@ -696,7 +695,7 @@ public class TestMoneroDaemonRpc {
@Test
public void testFlushTxsFromPool() {
assumeTrue(TEST_NON_RELAYS);
- TestUtils.WALLET_TX_TRACKER.waitForWalletTxsToClearPool(wallet);
+ TestUtils.WALLET_TX_TRACKER.waitForTxsToClearPool(wallet);
// preserve original transactions in the pool
List<MoneroTx> txPoolBefore = daemon.getTxPool();
@@ -730,7 +729,7 @@ public class TestMoneroDaemonRpc {
@Test
public void testFlushTxFromPoolByHash() {
assumeTrue(TEST_NON_RELAYS);
- TestUtils.WALLET_TX_TRACKER.waitForWalletTxsToClearPool(wallet);
+ TestUtils.WALLET_TX_TRACKER.waitForTxsToClearPool(wallet);
// preserve original transactions in the pool
List<MoneroTx> txPoolBefore = daemon.getTxPool();
@@ -766,7 +765,7 @@ public class TestMoneroDaemonRpc {
@Test
public void testFlushTxsFromPoolByHashes() {
assumeTrue(TEST_NON_RELAYS);
- TestUtils.WALLET_TX_TRACKER.waitForWalletTxsToClearPool(wallet);
+ TestUtils.WALLET_TX_TRACKER.waitForTxsToClearPool(wallet);
// preserve original transactions in the pool
List<MoneroTx> txPoolBefore = daemon.getTxPool();
@@ -793,7 +792,7 @@ public class TestMoneroDaemonRpc {
@Test
public void testGetSpentStatusOfKeyImages() {
assumeTrue(TEST_NON_RELAYS);
- TestUtils.WALLET_TX_TRACKER.waitForWalletTxsToClearPool(wallet);
+ TestUtils.WALLET_TX_TRACKER.waitForTxsToClearPool(wallet);
// submit txs to the pool to collect key images then flush them
List<MoneroTx> txs = new ArrayList<MoneroTx>();
@@ -1216,7 +1215,7 @@ public class TestMoneroDaemonRpc {
// wait one time for wallet txs in the pool to clear
// TODO monero-project: update from pool does not prevent creating double spend tx
- TestUtils.WALLET_TX_TRACKER.waitForWalletTxsToClearPool(wallet);
+ TestUtils.WALLET_TX_TRACKER.waitForTxsToClearPool(wallet);
// create 2 txs, the second will double spend outputs of first
MoneroTx tx1 = getUnrelayedTx(wallet, 2); // TODO: this test requires tx to be from/to different accounts else the occlusion issue (#4500) causes the tx to not be recognized by the wallet at all
@@ -1258,16 +1257,13 @@ public class TestMoneroDaemonRpc {
}
}
assertTrue(!found, "Tx2 should not be in the pool because it double spends tx1 which is in the pool");
-
- // all wallets will need to wait for tx to confirm in order to properly sync
- TestUtils.WALLET_TX_TRACKER.reset();
}
// Can submit a tx in hex format to the pool then relay
@Test
public void testSubmitThenRelayTxHex() {
assumeTrue(TEST_RELAYS && !LITE_MODE);
- TestUtils.WALLET_TX_TRACKER.waitForWalletTxsToClearPool(wallet);
+ TestUtils.WALLET_TX_TRACKER.waitForTxsToClearPool(wallet);
MoneroTx tx = getUnrelayedTx(wallet, 1);
testSubmitThenRelay(Arrays.asList(tx));
}
@@ -1276,7 +1272,7 @@ public class TestMoneroDaemonRpc {
@Test
public void testSubmitThenRelayTxHexes() {
assumeTrue(TEST_RELAYS && !LITE_MODE);
- TestUtils.WALLET_TX_TRACKER.waitForWalletTxsToClearPool(wallet);
+ TestUtils.WALLET_TX_TRACKER.waitForTxsToClearPool(wallet);
List<MoneroTx> txs = new ArrayList<MoneroTx>();
txs.add(getUnrelayedTx(wallet, 1));
txs.add(getUnrelayedTx(wallet, 2)); // TODO: accounts cannot be re-used across send tests else isRelayed is true; wallet needs to update?
@@ -1335,9 +1331,6 @@ public class TestMoneroDaemonRpc {
}
assertTrue(found, "Tx was not found after being submitted to the daemon's tx pool");
}
-
- // wallets will need to wait for tx to confirm in order to properly sync
- TestUtils.WALLET_TX_TRACKER.reset();
}
// -------------------------- NOTIFICATION TESTS ---------------------------
diff --git a/src/test/java/TestMoneroWalletCommon.java b/src/test/java/TestMoneroWalletCommon.java
index 41cc63c..1c518d1 100644
--- a/src/test/java/TestMoneroWalletCommon.java
+++ b/src/test/java/TestMoneroWalletCommon.java
@@ -113,7 +113,6 @@ public abstract class TestMoneroWalletCommon {
public void beforeAll() {
wallet = getTestWallet();
daemon = getTestDaemon();
- TestUtils.WALLET_TX_TRACKER.reset(); // all wallets need to wait for txs to confirm to reliably sync
}
@BeforeEach
@@ -781,7 +780,7 @@ public abstract class TestMoneroWalletCommon {
@Test
public void testWalletEqualityGroundTruth() {
assumeTrue(TEST_NON_RELAYS);
- TestUtils.WALLET_TX_TRACKER.waitForWalletTxsToClearPool(wallet);
+ TestUtils.WALLET_TX_TRACKER.waitForTxsToClearPool(wallet);
MoneroWallet walletGt = TestUtils.createWalletGroundTruth(TestUtils.NETWORK_TYPE, TestUtils.SEED, null, TestUtils.FIRST_RECEIVE_HEIGHT);
try {
WalletEqualityUtils.testWalletEqualityOnChain(walletGt, wallet);
@@ -2481,7 +2480,7 @@ public abstract class TestMoneroWalletCommon {
@Test
public void testViewOnlyAndOfflineWallets() {
assumeTrue(!LITE_MODE && (TEST_NON_RELAYS || TEST_RELAYS));
-
+
// create view-only and offline wallets
MoneroWallet viewOnlyWallet = createWallet(new MoneroWalletConfig().setPrimaryAddress(wallet.getPrimaryAddress()).setPrivateViewKey(wallet.getPrivateViewKey()).setRestoreHeight(TestUtils.FIRST_RECEIVE_HEIGHT));
MoneroWallet offlineWallet = createWallet(new MoneroWalletConfig().setPrimaryAddress(wallet.getPrimaryAddress()).setPrivateViewKey(wallet.getPrivateViewKey()).setPrivateSpendKey(wallet.getPrivateSpendKey()).setServerUri(TestUtils.OFFLINE_SERVER_URI).setRestoreHeight(0l));
@@ -2500,7 +2499,7 @@ public abstract class TestMoneroWalletCommon {
protected void testViewOnlyAndOfflineWallets(MoneroWallet viewOnlyWallet, MoneroWallet offlineWallet) {
// wait for txs to confirm and for sufficient unlocked balance
- TestUtils.WALLET_TX_TRACKER.waitForWalletTxsToClearPool(wallet);
+ TestUtils.WALLET_TX_TRACKER.waitForTxsToClearPool(wallet, viewOnlyWallet);
TestUtils.WALLET_TX_TRACKER.waitForUnlockedBalance(wallet, 0, null, TestUtils.MAX_FEE.multiply(new BigInteger("4")));
// test getting txs, transfers, and outputs from view-only wallet
@@ -2579,7 +2578,7 @@ public abstract class TestMoneroWalletCommon {
List<String> txHashes = viewOnlyWallet.submitTxs(signedTxSet.getSignedTxHex());
assertEquals(1, txHashes.size());
assertEquals(64, txHashes.get(0).length());
- TestUtils.WALLET_TX_TRACKER.waitForWalletTxsToClearPool(viewOnlyWallet); // wait for confirmation for other tests
+ TestUtils.WALLET_TX_TRACKER.waitForTxsToClearPool(viewOnlyWallet); // wait for confirmation for other tests
}
}
@@ -2894,12 +2893,12 @@ public abstract class TestMoneroWalletCommon {
.setAccountIndex(0)
.setAddress(wallet.getPrimaryAddress())
.setAmount(TestUtils.MAX_FEE.multiply(new BigInteger("5")))
- .setRelay(false));
+ .setRelay(true));
}
- // Can sync with txs submitted and discarded from the pool
+ // Can sync with txs submitted and flushed from the pool
@Test
- public void testSyncWithPoolSubmitAndDiscard() {
+ public void testSyncWithPoolSubmitAndFlush() {
assumeTrue(TEST_NON_RELAYS);
testSyncWithPoolSubmit(new MoneroTxConfig()
.setAccountIndex(2)
@@ -2923,13 +2922,17 @@ public abstract class TestMoneroWalletCommon {
// wait for txs to confirm and for sufficient unlocked balance
// TODO monero-project: update from pool does not prevent creating double spend tx
- TestUtils.WALLET_TX_TRACKER.waitForWalletTxsToClearPool(wallet);
+ TestUtils.WALLET_TX_TRACKER.waitForTxsToClearWallets(wallet);
assertNull(config.getSubaddressIndices());
TestUtils.WALLET_TX_TRACKER.waitForUnlockedBalance(wallet, config.getAccountIndex(), null, config.getAmount());
+ // rescan spent outputs for reliable before/after state
+ wallet.rescanSpent();
+
// record wallet balances before submitting tx to pool
BigInteger balanceBefore = wallet.getBalance();
BigInteger unlockedBalanceBefore = wallet.getUnlockedBalance();
+ List<MoneroTxWallet> txsBefore = wallet.getTxs();
// create tx but do not relay
MoneroTxConfig configNoRelay = config.copy().setRelay(false);
@@ -2954,13 +2957,12 @@ public abstract class TestMoneroWalletCommon {
// test result and flush on finally
try {
- // wallet should be aware of tx
- try {
- MoneroTxWallet fetched = wallet.getTx(tx.getHash());
- assertNotNull(fetched);
- } catch (MoneroError e) {
- fail("Wallet should be aware of its tx in pool after syncing");
- }
+ // test tx state
+ MoneroTxWallet fetched = wallet.getTx(tx.getHash());
+ assertNotNull(fetched);
+ assertFalse(fetched.isFailed(), "Submitted tx should not be failed");
+ //assertEquals(false, fetched.isRelayed(), "Submitted tx should be same relayed as configuration"); // TODO: relay flag is not correctly set, insufficient info comes from monero-wallet-rpc
+ assertTrue(fetched.inTxPool(), "Submitted tx should be in the pool");
// submit double spend tx
MoneroSubmitTxResult resultDoubleSpend = daemon.submitTxHex(txDoubleSpend.getFullHex(), true);
@@ -2992,7 +2994,8 @@ public abstract class TestMoneroWalletCommon {
if (result2.isDoubleSpend()) throw new RuntimeException("Wallet created double spend transaction after syncing with the pool: " + JsonUtils.serialize(result2));
assertTrue(result.isGood());
wallet.sync();
- wallet.getTx(tx2.getHash()); // wallet is aware of tx2
+ fetched = wallet.getTx(tx2.getHash()); // wallet is aware of tx2
+ if (fetched == null || fetched.isFailed()) throw new RuntimeException("Submitted tx should not be null or failed");
} finally {
daemon.flushTxPool(tx2.getHash());
}
@@ -3002,23 +3005,39 @@ public abstract class TestMoneroWalletCommon {
// flush the tx from the pool
daemon.flushTxPool(tx.getHash());
+
+ // clear tx from wallet
+ TestUtils.WALLET_TX_TRACKER.waitForTxsToClearWallets(wallet);
- // sync wallet which checks pool
- wallet.sync();
-
- // wallet should no longer be aware of tx
- try {
- wallet.getTx(tx.getHash());
- fail("Wallet should no longer be aware of tx which was not relayed and was manually removed from pool");
- } catch (MoneroError e) {
- // exception expected
+ // wallet should see failed state
+ MoneroTxWallet fetched = wallet.getTx(tx.getHash());
+ if (fetched != null) {
+ if (!fetched.isFailed()) throw new RuntimeException("Flushed tx should be failed");
+ if (fetched.inTxPool()) throw new RuntimeException("Flushed tx should not be in pool");
+ if (fetched.isRelayed()) throw new RuntimeException("Flushed tx should not be relayed");
+ }
+
+ // wallet txs are restored
+ List<MoneroTxWallet> txsAfter = wallet.getTxs();
+ for (MoneroTxWallet txBefore : txsBefore) {
+ if (txBefore.getHash().equals(tx.getHash())) continue;
+ boolean found = false;
+ for (MoneroTxWallet txAfter : txsAfter) {
+ if (txBefore.getHash().equals(txAfter.getHash())) {
+
+ // transfer fields which can update with confirmations // TODO: merge instead?
+ txBefore.setNumConfirmations(txAfter.getNumConfirmations()); // transfer number of confirmations
+ txBefore.setIsLocked(txAfter.isLocked());
+
+ assertEquals(txBefore, txAfter);
+ found = true;
+ }
+ }
+ if (!found) throw new RuntimeException("Tx " + txBefore.getHash() + " not found after flushing tx from pool without relaying");
}
- // wallet balances should be restored
+ // wallet balance should be restored
assertEquals(balanceBefore, wallet.getBalance(), "Wallet balance should be same as original since tx was flushed and not relayed");
- assertEquals(unlockedBalanceBefore, wallet.getUnlockedBalance(), "Wallet unlocked balance should be same as original since tx was flushed and not relayed");
- } else {
- TestUtils.WALLET_TX_TRACKER.reset(); // all wallets need to wait for tx to confirm in order to reliably sync
}
}
}
@@ -3029,8 +3048,7 @@ public abstract class TestMoneroWalletCommon {
assumeTrue(TEST_RELAYS && !LITE_MODE);
// wait one time for wallet txs in the pool to clear
- // TODO monero-project: update from pool does not prevent creating double spend tx
- TestUtils.WALLET_TX_TRACKER.waitForWalletTxsToClearPool(wallet);
+ TestUtils.WALLET_TX_TRACKER.waitForTxsToClearPool(wallet);
// record wallet balances before submitting tx to pool
BigInteger balanceBefore = wallet.getBalance();
@@ -3058,55 +3076,51 @@ public abstract class TestMoneroWalletCommon {
// collect issues to report at end of test
List<String> issues = new ArrayList<String>();
-
+
+ // wallet should be aware of tx
try {
-
- // wallet should be aware of tx
- try {
- MoneroTxWallet fetched = wallet.getTx(tx.getHash());
- assertNotNull(fetched);
- } catch (MoneroError e) {
- throw new RuntimeException("Wallet should be aware of its tx in pool after syncing");
- }
-
- // test wallet balances
- if (unlockedBalanceBefore.compareTo(wallet.getUnlockedBalance()) != 1) issues.add("WARNING: unlocked balance should have decreased after relaying tx directly to the daemon");
- BigInteger expectedBalance = balanceBefore.subtract(tx.getOutgoingAmount()).subtract(tx.getFee());
- if (!expectedBalance.equals(wallet.getBalance())) issues.add("WARNING: expected balance after relaying tx directly to the daemon to be " + expectedBalance + " but was " + wallet.getBalance());
-
- // submit double spend tx
- MoneroSubmitTxResult resultDoubleSpend = daemon.submitTxHex(txDoubleSpend.getFullHex(), true);
- if (resultDoubleSpend.isGood()) {
- daemon.flushTxPool(txDoubleSpend.getHash());
- throw new RuntimeException("Tx submit result should have been double spend");
- }
+ MoneroTxWallet fetched = wallet.getTx(tx.getHash());
+ assertNotNull(fetched);
+ } catch (MoneroError e) {
+ throw new RuntimeException("Wallet should be aware of its tx in pool after syncing");
+ }
+
+ // test wallet balances
+ if (unlockedBalanceBefore.compareTo(wallet.getUnlockedBalance()) != 1) issues.add("WARNING: unlocked balance should have decreased after relaying tx directly to the daemon");
+ if (balanceBefore.compareTo(wallet.getBalance()) != 1) issues.add("WARNING: balance should have decreased after relaying tx directly to the daemon");
+ BigInteger expectedBalance = balanceBefore.subtract(tx.getFee());
+ if (!expectedBalance.equals(wallet.getBalance())) issues.add("WARNING: expected balance after relaying tx directly to the daemon to be " + expectedBalance + " but was " + wallet.getBalance());
+
+ // submit double spend tx
+ MoneroSubmitTxResult resultDoubleSpend = daemon.submitTxHex(txDoubleSpend.getFullHex(), true);
+ if (resultDoubleSpend.isGood()) {
+ daemon.flushTxPool(txDoubleSpend.getHash());
+ issues.add("Tx submit result should have been double spend");
+ }
- // sync wallet which updates from pool
+ // sync wallet which updates from pool
+ wallet.sync();
+
+ // create tx using same config which is no longer double spend
+ MoneroTxWallet tx2 = null;
+ try {
+ tx2 = wallet.createTx(config.copy().setRelay(true));
+ } catch (Exception e) {
+ issues.add("WARNING: creating and sending tx through wallet should succeed after syncing wallet with pool but creates a double spend"); // TODO monero-project: this fails meaning wallet did not recognize tx relayed directly to pool
+ }
+
+ // submit the transaction to the pool and test
+ if (tx2 != null) {
+ MoneroSubmitTxResult result2 = daemon.submitTxHex(tx2.getFullHex(), true);
+ if (result2.isDoubleSpend()) issues.add("WARNING: creating and submitting tx to daemon should succeed after syncing wallet with pool but was a double spend: " + JsonUtils.serialize(result2));
+ else assertTrue(result2.isGood());
wallet.sync();
-
- // create tx using same config which is no longer double spend
- MoneroTxWallet tx2 = null;
- try {
- tx2 = wallet.createTx(config.copy().setRelay(true));
- } catch (Exception e) {
- issues.add("WARNING: creating and sending tx through wallet should succeed after syncing wallet with pool but creates a double spend"); // TODO monero-project: this fails meaning wallet did not recognize tx relayed directly to pool
- }
-
- // submit the transaction to the pool and test
- if (tx2 != null) {
- MoneroSubmitTxResult result2 = daemon.submitTxHex(tx2.getFullHex(), true);
- if (result2.isDoubleSpend()) issues.add("WARNING: creating and submitting tx to daemon should succeed after syncing wallet with pool but was a double spend: " + JsonUtils.serialize(result2));
- else assertTrue(result.isGood());
- wallet.sync();
- wallet.getTx(tx2.getHash()); // wallet is aware of tx2
- daemon.flushTxPool(tx2.getHash());
- }
-
- // should be no issues
- assertEquals(0, issues.size(), "testSyncWithPoolRelay() issues: " + issues);
- } finally {
- TestUtils.WALLET_TX_TRACKER.reset(); // all wallets need to wait for tx to confirm to reliably sync tx
+ if (wallet.getTx(tx2.getHash()) == null) issues.add("WARNING: wallet should be aware of tx created and relayed through it after syncing with pool");
+ daemon.flushTxPool(tx2.getHash());
}
+
+ // should be no issues
+ assertEquals(0, issues.size(), "testSyncWithPoolRelay() issues: " + issues);
}
// Can send to self
@@ -3115,7 +3129,7 @@ public abstract class TestMoneroWalletCommon {
assumeTrue(TEST_RELAYS);
// wait for txs to confirm and for sufficient unlocked balance
- TestUtils.WALLET_TX_TRACKER.waitForWalletTxsToClearPool(wallet);
+ TestUtils.WALLET_TX_TRACKER.waitForTxsToClearPool(wallet);
BigInteger amount = TestUtils.MAX_FEE.multiply(BigInteger.valueOf(3));
TestUtils.WALLET_TX_TRACKER.waitForUnlockedBalance(wallet, 0, null, amount);
@@ -3160,7 +3174,7 @@ public abstract class TestMoneroWalletCommon {
try {
// wait for txs to confirm and for sufficient unlocked balance
- TestUtils.WALLET_TX_TRACKER.waitForWalletTxsToClearPool(wallet);
+ TestUtils.WALLET_TX_TRACKER.waitForTxsToClearPool(wallet);
BigInteger amount = TestUtils.MAX_FEE.multiply(BigInteger.valueOf(3));
TestUtils.WALLET_TX_TRACKER.waitForUnlockedBalance(wallet, 0, null, amount);
@@ -3209,7 +3223,7 @@ public abstract class TestMoneroWalletCommon {
}
private void testSendFromMultiple(MoneroTxConfig config) {
- TestUtils.WALLET_TX_TRACKER.waitForWalletTxsToClearPool(wallet);
+ TestUtils.WALLET_TX_TRACKER.waitForTxsToClearPool(wallet);
if (config == null) config = new MoneroTxConfig();
int NUM_SUBADDRESSES = 2; // number of subaddresses to send from
@@ -3364,7 +3378,7 @@ public abstract class TestMoneroWalletCommon {
}
private void testSendToSingle(MoneroTxConfig config) {
- TestUtils.WALLET_TX_TRACKER.waitForWalletTxsToClearPool(wallet);
+ TestUtils.WALLET_TX_TRACKER.waitForTxsToClearPool(wallet);
if (config == null) config = new MoneroTxConfig();
// find a non-primary subaddress to send from
@@ -3502,11 +3516,6 @@ public abstract class TestMoneroWalletCommon {
}
assertTrue(found, "Created txs should be among locked txs");
}
-
- // if tx was relayed in separate step, all wallets will need to wait for tx to confirm in order to reliably sync
- if (!Boolean.TRUE.equals(config.getRelay())) {
- TestUtils.WALLET_TX_TRACKER.reset(); // TODO: resetExcept(wallet), or does this test wallet also need to be waited on?
- }
}
// Can send to multiple addresses in a single transaction
@@ -3557,7 +3566,7 @@ public abstract class TestMoneroWalletCommon {
private void testSendToMultiple(int numAccounts, int numSubaddressesPerAccount, boolean canSplit) { testSendToMultiple(numAccounts, numSubaddressesPerAccount, canSplit, null); }
private void testSendToMultiple(int numAccounts, int numSubaddressesPerAccount, boolean canSplit, BigInteger sendAmountPerSubaddress) { testSendToMultiple(numAccounts, numSubaddressesPerAccount, canSplit, sendAmountPerSubaddress, false); }
private void testSendToMultiple(int numAccounts, int numSubaddressesPerAccount, boolean canSplit, BigInteger sendAmountPerSubaddress, boolean subtractFeeFromDestinations) {
- TestUtils.WALLET_TX_TRACKER.waitForWalletTxsToClearPool(wallet);
+ TestUtils.WALLET_TX_TRACKER.waitForTxsToClearPool(wallet);
// compute the minimum account unlocked balance needed in order to fulfill the config
BigInteger minAccountAmount = null;
@@ -3683,7 +3692,7 @@ public abstract class TestMoneroWalletCommon {
@Test
public void testSweepOutputs() {
assumeTrue(TEST_RELAYS);
- TestUtils.WALLET_TX_TRACKER.waitForWalletTxsToClearPool(wallet);
+ TestUtils.WALLET_TX_TRACKER.waitForTxsToClearPool(wallet);
// test config
int numOutputs = 3;
@@ -3736,7 +3745,7 @@ public abstract class TestMoneroWalletCommon {
@Test
public void testSweepDustNoRelay() {
assumeTrue(TEST_RELAYS);
- TestUtils.WALLET_TX_TRACKER.waitForWalletTxsToClearPool(wallet);
+ TestUtils.WALLET_TX_TRACKER.waitForTxsToClearPool(wallet);
// sweep dust which returns empty list if no dust to sweep (dust does not exist after rct)
List<MoneroTxWallet> txs = wallet.sweepDust(false);
@@ -3770,7 +3779,7 @@ public abstract class TestMoneroWalletCommon {
@Test
public void testSweepDust() {
assumeTrue(TEST_RELAYS);
- TestUtils.WALLET_TX_TRACKER.waitForWalletTxsToClearPool(wallet);
+ TestUtils.WALLET_TX_TRACKER.waitForTxsToClearPool(wallet);
// sweep dust which returns empty list if no dust to sweep (dust does not exist after rct)
List<MoneroTxWallet> txs = wallet.sweepDust(true);
@@ -4048,7 +4057,7 @@ public abstract class TestMoneroWalletCommon {
}
// wait for txs to confirm and for sufficient unlocked balance
- TestUtils.WALLET_TX_TRACKER.waitForWalletTxsToClearPool(wallet);
+ TestUtils.WALLET_TX_TRACKER.waitForTxsToClearPool(wallet);
TestUtils.WALLET_TX_TRACKER.waitForUnlockedBalance(wallet, 0, null, TestUtils.MAX_FEE.multiply(new BigInteger("20")));
// send funds from the main test wallet to destinations in the first multisig wallet
@@ -4365,7 +4374,7 @@ public abstract class TestMoneroWalletCommon {
private void testSendAndUpdateTxs(MoneroTxConfig config) {
// wait for txs to confirm and for sufficient unlocked balance
- TestUtils.WALLET_TX_TRACKER.waitForWalletTxsToClearPool(wallet);
+ TestUtils.WALLET_TX_TRACKER.waitForTxsToClearPool(wallet);
assertNull(config.getSubaddressIndices());
TestUtils.WALLET_TX_TRACKER.waitForUnlockedBalance(wallet, config.getAccountIndex(), null, TestUtils.MAX_FEE.multiply(new BigInteger("2")));
@@ -4524,7 +4533,7 @@ public abstract class TestMoneroWalletCommon {
@Test
public void testSweepSubaddresses() {
assumeTrue(TEST_RESETS);
- TestUtils.WALLET_TX_TRACKER.waitForWalletTxsToClearPool(wallet);
+ TestUtils.WALLET_TX_TRACKER.waitForTxsToClearPool(wallet);
final int NUM_SUBADDRESSES_TO_SWEEP = 2;
@@ -4609,7 +4618,7 @@ public abstract class TestMoneroWalletCommon {
@Test
public void testSweepAccounts() {
assumeTrue(TEST_RESETS);
- TestUtils.WALLET_TX_TRACKER.waitForWalletTxsToClearPool(wallet);
+ TestUtils.WALLET_TX_TRACKER.waitForTxsToClearPool(wallet);
final int NUM_ACCOUNTS_TO_SWEEP = 1;
@@ -4695,7 +4704,7 @@ public abstract class TestMoneroWalletCommon {
}
private void testSweepWallet(Boolean sweepEachSubaddress) {
- TestUtils.WALLET_TX_TRACKER.waitForWalletTxsToClearPool(wallet);
+ TestUtils.WALLET_TX_TRACKER.waitForTxsToClearPool(wallet);
// verify 2 subaddresses with enough unlocked balance to cover the fee
List<MoneroSubaddress> subaddressesBalance = new ArrayList<MoneroSubaddress>();
@@ -4858,7 +4867,7 @@ public abstract class TestMoneroWalletCommon {
for (int i = 0; i < 4 - numAccounts; i++) receiver.createAccount();
// wait for unlocked funds in source account
- TestUtils.WALLET_TX_TRACKER.waitForWalletTxsToClearPool(sender);
+ TestUtils.WALLET_TX_TRACKER.waitForTxsToClearPool(sender);
TestUtils.WALLET_TX_TRACKER.waitForUnlockedBalance(sender, 0, null, TestUtils.MAX_FEE.multiply(new BigInteger("10")));
// get balances to compare after sending
@@ -5167,7 +5176,7 @@ public abstract class TestMoneroWalletCommon {
receiver.addListener(myListener);
// wait for txs to confirm and for sufficient unlocked balance
- TestUtils.WALLET_TX_TRACKER.waitForWalletTxsToClearPool(wallet);
+ TestUtils.WALLET_TX_TRACKER.waitForTxsToClearPool(wallet);
TestUtils.WALLET_TX_TRACKER.waitForUnlockedBalance(wallet, 0, null, TestUtils.MAX_FEE);
// send funds to the receiver
diff --git a/src/test/java/TestMoneroWalletFull.java b/src/test/java/TestMoneroWalletFull.java
index a57c261..dc96c9e 100644
--- a/src/test/java/TestMoneroWalletFull.java
+++ b/src/test/java/TestMoneroWalletFull.java
@@ -31,6 +31,7 @@ import monero.wallet.model.MoneroOutputWallet;
import monero.wallet.model.MoneroSyncResult;
import monero.wallet.model.MoneroTransfer;
import monero.wallet.model.MoneroTransferQuery;
+import monero.wallet.model.MoneroTxQuery;
import monero.wallet.model.MoneroTxWallet;
import monero.wallet.model.MoneroWalletConfig;
import monero.wallet.model.MoneroWalletListener;
@@ -120,7 +121,8 @@ public class TestMoneroWalletFull extends TestMoneroWalletCommon {
@Override
public void closeWallet(MoneroWallet wallet, boolean save) {
- wallet.close(save);
+ if (wallet instanceof MoneroWalletRpc) TestUtils.stopWalletRpcProcess((MoneroWalletRpc) wallet);
+ else wallet.close(save);
}
@Override
@@ -388,14 +390,14 @@ public class TestMoneroWalletFull extends TestMoneroWalletCommon {
String walletName = GenUtils.getUUID();
MoneroWalletRpc walletRpc = TestUtils.getWalletRpc();
walletRpc.createWallet(new MoneroWalletConfig().setPath(walletName).setPassword(TestUtils.WALLET_PASSWORD).setSeed(TestUtils.SEED).setRestoreHeight(TestUtils.FIRST_RECEIVE_HEIGHT));
- walletRpc.sync();
+ TestUtils.WALLET_TX_TRACKER.waitForTxsToClearPool(walletRpc);
BigInteger balance = walletRpc.getBalance();
String outputsHex = walletRpc.exportOutputs();
walletRpc.close(true);
// open as full wallet
MoneroWalletFull walletFull = MoneroWalletFull.openWallet(new MoneroWalletConfig().setPath(TestUtils.WALLET_RPC_LOCAL_WALLET_DIR + "/" + walletName).setPassword(TestUtils.WALLET_PASSWORD).setNetworkType(TestUtils.NETWORK_TYPE).setServerUri(TestUtils.DAEMON_RPC_URI).setServerUsername(TestUtils.DAEMON_RPC_USERNAME).setServerPassword(TestUtils.DAEMON_RPC_PASSWORD));
- walletFull.sync();
+ TestUtils.WALLET_TX_TRACKER.waitForTxsToClearPool(walletFull);
assertEquals(TestUtils.SEED, walletFull.getSeed());
assertEquals(TestUtils.ADDRESS, walletFull.getPrimaryAddress());
assertEquals(balance, walletFull.getBalance());
@@ -423,7 +425,7 @@ public class TestMoneroWalletFull extends TestMoneroWalletCommon {
assertEquals(TestUtils.ADDRESS, walletRpc.getPrimaryAddress());
assertEquals(balance, walletRpc.getBalance());
assertEquals(outputsHex.length(), walletRpc.exportOutputs().length());
- walletRpc.close(true);
+ walletRpc.close(true); // TODO: this will not get called if there was an error above, leaving the wallet open
}
// Is compatible with monero-wallet-rpc outputs and offline transaction signing
@@ -433,8 +435,7 @@ public class TestMoneroWalletFull extends TestMoneroWalletCommon {
assumeTrue(!LITE_MODE && (TEST_NON_RELAYS || TEST_RELAYS));
// create view-only wallet in wallet rpc process
- MoneroWalletRpc viewOnlyWallet = TestUtils.startWalletRpcProcess();
- viewOnlyWallet.createWallet(new MoneroWalletConfig().setPath(GenUtils.getUUID()).setPassword(TestUtils.WALLET_PASSWORD).setPrimaryAddress(wallet.getPrimaryAddress()).setPrivateViewKey(wallet.getPrivateViewKey()).setRestoreHeight(TestUtils.FIRST_RECEIVE_HEIGHT));
+ MoneroWalletRpc viewOnlyWallet = TestUtils.createWalletRpc(new MoneroWalletConfig().setPath(GenUtils.getUUID()).setPassword(TestUtils.WALLET_PASSWORD).setPrimaryAddress(wallet.getPrimaryAddress()).setPrivateViewKey(wallet.getPrivateViewKey()).setRestoreHeight(TestUtils.FIRST_RECEIVE_HEIGHT));
viewOnlyWallet.sync();
// create offline full wallet
@@ -456,8 +457,8 @@ public class TestMoneroWalletFull extends TestMoneroWalletCommon {
// create participants with full wallet and monero-wallet-rpc
List<MoneroWallet> participants = new ArrayList<MoneroWallet>();
- participants.add(TestUtils.startWalletRpcProcess().createWallet(new MoneroWalletConfig().setPath(GenUtils.getUUID()).setPassword(TestUtils.WALLET_PASSWORD)));
- participants.add(TestUtils.startWalletRpcProcess().createWallet(new MoneroWalletConfig().setPath(GenUtils.getUUID()).setPassword(TestUtils.WALLET_PASSWORD)));
+ participants.add(TestUtils.createWalletRpc(new MoneroWalletConfig().setPath(GenUtils.getUUID()).setPassword(TestUtils.WALLET_PASSWORD)));
+ participants.add(TestUtils.createWalletRpc(new MoneroWalletConfig().setPath(GenUtils.getUUID()).setPassword(TestUtils.WALLET_PASSWORD)));
participants.add(createWallet(new MoneroWalletConfig()));
// test multisig
@@ -470,10 +471,7 @@ public class TestMoneroWalletFull extends TestMoneroWalletCommon {
catch (MoneroError e) { }
// save and close participants
- if (participants.get(0) instanceof MoneroWalletRpc) TestUtils.stopWalletRpcProcess((MoneroWalletRpc) participants.get(0));
- else participants.get(0).close(true); // multisig tests might restore wallet from seed
- TestUtils.stopWalletRpcProcess((MoneroWalletRpc) participants.get(1));
- closeWallet(participants.get(2), true);
+ for (MoneroWallet participant : participants) closeWallet(participant, true);
}
};
@@ -597,6 +595,9 @@ public class TestMoneroWalletFull extends TestMoneroWalletCommon {
private void testSyncSeed(Long startHeight, Long restoreHeight, boolean skipGtComparison, boolean testPostSyncNotifications) {
assertTrue(daemon.isConnected(), "Not connected to daemon");
if (startHeight != null && restoreHeight != null) assertTrue(startHeight <= TestUtils.FIRST_RECEIVE_HEIGHT || restoreHeight <= TestUtils.FIRST_RECEIVE_HEIGHT);
+
+ // wait for txs to clear pool
+ TestUtils.WALLET_TX_TRACKER.waitForTxsToClearPool(wallet);
// create wallet from seed
MoneroWalletFull wallet = createWallet(new MoneroWalletConfig().setSeed(TestUtils.SEED).setRestoreHeight(restoreHeight), false);
@@ -862,6 +863,15 @@ public class TestMoneroWalletFull extends TestMoneroWalletCommon {
// Is equal to the RPC wallet.
@Test
public void testWalletEqualityRpc() {
+
+ // wait for txs to clear pool
+ TestUtils.WALLET_TX_TRACKER.waitForTxsToClearPool(TestUtils.getWalletRpc(), wallet);
+
+ // TODO: rescanning spent outputs is necessary for equality test to mark as spent/unspent correctly
+ wallet.rescanSpent();
+ TestUtils.getWalletRpc().rescanSpent();
+
+ // compare wallets based on on-chain data
WalletEqualityUtils.testWalletEqualityOnChain(TestUtils.getWalletRpc(), wallet);
}
@@ -1479,8 +1489,16 @@ public class TestMoneroWalletFull extends TestMoneroWalletCommon {
assertNotNull(walletTesterPrevHeight);
assertNotNull(prevOutputReceived);
assertNotNull(prevOutputSpent);
- BigInteger balance = incomingTotal.subtract(outgoingTotal);
- assertEquals(balance, wallet.getBalance());
+ BigInteger expectedBalance = incomingTotal.subtract(outgoingTotal);
+
+ // output notifications do not include pool fees or outgoing amount
+ BigInteger poolSpendAmount = BigInteger.ZERO;
+ for (MoneroTxWallet poolTx : wallet.getTxs(new MoneroTxQuery().setInTxPool(true))) {
+ poolSpendAmount = poolSpendAmount.add(poolTx.getFee()).add(poolTx.getOutgoingAmount() == null ? BigInteger.ZERO : poolTx.getOutgoingAmount());
+ }
+ expectedBalance = expectedBalance.subtract(poolSpendAmount);
+
+ assertEquals(expectedBalance, wallet.getBalance());
assertEquals(prevBalance, wallet.getBalance());
assertEquals(prevUnlockedBalance, wallet.getUnlockedBalance());
}
@@ -1939,8 +1957,8 @@ public class TestMoneroWalletFull extends TestMoneroWalletCommon {
@Override
@Test
- public void testSyncWithPoolSubmitAndDiscard() {
- super.testSyncWithPoolSubmitAndDiscard();
+ public void testSyncWithPoolSubmitAndFlush() {
+ super.testSyncWithPoolSubmitAndFlush();
}
@Override
diff --git a/src/test/java/TestMoneroWalletRpc.java b/src/test/java/TestMoneroWalletRpc.java
index c93974d..e31bbb9 100644
--- a/src/test/java/TestMoneroWalletRpc.java
+++ b/src/test/java/TestMoneroWalletRpc.java
@@ -50,7 +50,7 @@ public class TestMoneroWalletRpc extends TestMoneroWalletCommon {
// if full tests ran, wait for full wallet's pool txs to confirm
if (TestMoneroWalletFull.FULL_TESTS_RUN) {
MoneroWallet walletFull = TestUtils.getWalletFull();
- TestUtils.WALLET_TX_TRACKER.waitForWalletTxsToClearPool(walletFull);
+ TestUtils.WALLET_TX_TRACKER.waitForTxsToClearPool(walletFull);
walletFull.close(true);
}
}
@@ -84,53 +84,12 @@ public class TestMoneroWalletRpc extends TestMoneroWalletCommon {
@Override
protected MoneroWalletRpc openWallet(MoneroWalletConfig config) {
-
- // assign defaults
- if (config == null) config = new MoneroWalletConfig();
- if (config.getPassword() == null) config.setPassword(TestUtils.WALLET_PASSWORD);
- if (config.getServer() == null && config.getConnectionManager() == null) config.setServer(daemon.getRpcConnection());
-
- // create client connected to internal monero-wallet-rpc process
- boolean offline = TestUtils.OFFLINE_SERVER_URI.equals(config.getServerUri());
- MoneroWalletRpc wallet = TestUtils.startWalletRpcProcess(offline);
-
- // open wallet
- try {
- wallet.openWallet(config);
- 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) {
- try { TestUtils.stopWalletRpcProcess(wallet); } catch (Exception e2) { throw new RuntimeException(e2); }
- throw e;
- }
+ return TestUtils.openWalletRpc(config);
}
@Override
protected MoneroWalletRpc createWallet(MoneroWalletConfig config) {
-
- // assign defaults
- if (config == null) config = new MoneroWalletConfig();
- boolean random = config.getSeed() == null && config.getPrimaryAddress() == null;
- 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.getConnectionManager() == null) config.setServer(daemon.getRpcConnection());
-
- // create client connected to internal monero-wallet-rpc process
- boolean offline = MoneroUtils.parseUri(TestUtils.OFFLINE_SERVER_URI).toString().equals(config.getServerUri());
- MoneroWalletRpc wallet = TestUtils.startWalletRpcProcess(offline);
-
- // create wallet
- try {
- wallet.createWallet(config);
- 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) {
- try { TestUtils.stopWalletRpcProcess(wallet); } catch (Exception e2) { throw new RuntimeException(e2); }
- throw e;
- }
+ return TestUtils.createWalletRpc(config);
}
@Override
@@ -873,8 +832,8 @@ public class TestMoneroWalletRpc extends TestMoneroWalletCommon {
@Override
@Test
- public void testSyncWithPoolSubmitAndDiscard() {
- super.testSyncWithPoolSubmitAndDiscard();
+ public void testSyncWithPoolSubmitAndFlush() {
+ super.testSyncWithPoolSubmitAndFlush();
}
@Override
diff --git a/src/test/java/TestSampleCode.java b/src/test/java/TestSampleCode.java
index 359c37f..bee1f18 100644
--- a/src/test/java/TestSampleCode.java
+++ b/src/test/java/TestSampleCode.java
@@ -36,9 +36,6 @@ public class TestSampleCode {
@BeforeAll
public static void setUpBeforeClass() throws Exception {
- // all wallets need to wait for txs to confirm to reliably sync
- TestUtils.WALLET_TX_TRACKER.reset();
-
// pre-create test wallet
MoneroWalletRpc wallet = TestUtils.getWalletRpc();
wallet.close();
@@ -100,7 +97,7 @@ public class TestSampleCode {
List<MoneroTxWallet> txs = walletRpc.getTxs(); // get transactions containing transfers to/from the wallet
// send funds from RPC wallet to full wallet
- TestUtils.WALLET_TX_TRACKER.waitForWalletTxsToClearPool(walletRpc); // *** REMOVE FROM README SAMPLE ***
+ TestUtils.WALLET_TX_TRACKER.waitForTxsToClearPool(walletRpc); // *** REMOVE FROM README SAMPLE ***
TestUtils.WALLET_TX_TRACKER.waitForUnlockedBalance(walletRpc, 0, null, new BigInteger("250000000000")); // *** REMOVE FROM README SAMPLE ***
MoneroTxWallet createdTx = walletRpc.createTx(new MoneroTxConfig()
.setAccountIndex(0)
diff --git a/src/test/java/utils/FundWallets.java b/src/test/java/utils/FundWallets.java
index aae6251..9fa53cb 100644
--- a/src/test/java/utils/FundWallets.java
+++ b/src/test/java/utils/FundWallets.java
@@ -37,7 +37,7 @@ public class FundWallets {
* @param numSubaddressesPerAccount is the number of subaddresses to fund in each account (num accounts * num subaddresses must be less than or equal to 16) // TODO: support funding more subaddresses
*/
public static void fundWallets(MoneroWallet srcWallet, int numWallets, int numAccounts, int numSubaddressesPerAccount) {
- TestUtils.WALLET_TX_TRACKER.waitForWalletTxsToClearPool(srcWallet);
+ TestUtils.WALLET_TX_TRACKER.waitForTxsToClearPool(srcWallet);
for (int i = 0; i < NUM_WALLETS; i++) {
// create wallet and create and collect subaddresses
diff --git a/src/test/java/utils/TestUtils.java b/src/test/java/utils/TestUtils.java
index 59c7281..4331f4a 100644
--- a/src/test/java/utils/TestUtils.java
+++ b/src/test/java/utils/TestUtils.java
@@ -13,8 +13,11 @@ import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
+import java.util.UUID;
import java.util.logging.LogManager;
import java.util.logging.Logger;
+
+import monero.common.MoneroError;
import monero.common.MoneroRpcConnection;
import monero.common.MoneroRpcError;
import monero.common.MoneroUtils;
@@ -151,13 +154,62 @@ public class TestUtils {
// return cached wallet rpc
return walletRpc;
}
+
+ public static MoneroWalletRpc openWalletRpc(MoneroWalletConfig config) {
+
+ // assign defaults
+ if (config == null) config = new MoneroWalletConfig();
+ if (config.getPassword() == null) config.setPassword(TestUtils.WALLET_PASSWORD);
+ if (config.getServer() == null && config.getConnectionManager() == null) config.setServer(TestUtils.getDaemonRpc().getRpcConnection());
+
+ // create client connected to internal monero-wallet-rpc process
+ MoneroWalletRpc wallet = TestUtils.startWalletRpcProcess(config.getServerUri(), config.getServerUsername(), config.getServerPassword());
+
+ // open wallet
+ try {
+ wallet.stopSyncing();
+ wallet.openWallet(config);
+ 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) {
+ try { TestUtils.stopWalletRpcProcess(wallet); } catch (Exception e2) { throw new RuntimeException(e2); }
+ throw e;
+ }
+ }
+
+ public static MoneroWalletRpc createWalletRpc(MoneroWalletConfig config) {
+
+ // assign defaults
+ if (config == null) config = new MoneroWalletConfig();
+ boolean random = config.getSeed() == null && config.getPrimaryAddress() == null;
+ 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.getConnectionManager() == null) config.setServer(TestUtils.getDaemonRpc().getRpcConnection());
+
+ // create client connected to internal monero-wallet-rpc process
+ MoneroWalletRpc wallet = TestUtils.startWalletRpcProcess(config.getServerUri(), config.getServerUsername(), config.getServerPassword());
+
+ // create wallet
+ try {
+ wallet.stopSyncing();
+ wallet.createWallet(config);
+ 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) {
+ try { TestUtils.stopWalletRpcProcess(wallet); } catch (Exception e2) { throw new RuntimeException(e2); }
+ throw e;
+ }
+ }
/**
* Start a monero-wallet-rpc process bound to the next available port.
*/
public static Map<MoneroWalletRpc, Integer> WALLET_PORT_OFFSETS = new HashMap<MoneroWalletRpc, Integer>();
- public static MoneroWalletRpc startWalletRpcProcess() { return TestUtils.startWalletRpcProcess(false); }
- public static MoneroWalletRpc startWalletRpcProcess(boolean offline) {
+ public static MoneroWalletRpc startWalletRpcProcess() { return TestUtils.startWalletRpcProcess(null, null, null); }
+ public static MoneroWalletRpc startWalletRpcProcess(String daemonUri, String daemonUsername, String daemonPassword) {
// get next available offset of ports to bind to
int portOffset = 1;
@@ -171,9 +223,12 @@ public class TestUtils {
"--rpc-login", TestUtils.WALLET_RPC_USERNAME + ":" + TestUtils.WALLET_RPC_PASSWORD,
"--wallet-dir", TestUtils.WALLET_RPC_LOCAL_WALLET_DIR,
"--rpc-access-control-origins", TestUtils.WALLET_RPC_ACCESS_CONTROL_ORIGINS));
+ boolean offline = TestUtils.OFFLINE_SERVER_URI.equals(daemonUri);
if (offline) cmd.add("--offline");
- else cmd.addAll(Arrays.asList("--daemon-address", TestUtils.DAEMON_RPC_URI));
- if (TestUtils.DAEMON_RPC_USERNAME != null && !TestUtils.DAEMON_RPC_USERNAME.equals("")) cmd.addAll(Arrays.asList("--daemon-login", TestUtils.DAEMON_RPC_USERNAME + ":" + TestUtils.DAEMON_RPC_PASSWORD));
+ else if (daemonUri != null && !daemonUri.isEmpty()) {
+ cmd.addAll(Arrays.asList("--daemon-address", daemonUri));
+ if (daemonUsername != null && !daemonUsername.equals("")) cmd.addAll(Arrays.asList("--daemon-login", daemonUsername + ":" + daemonPassword));
+ }
// start with zmq if enabled
if (WALLET_RPC_ZMQ_ENABLED) {
@@ -198,7 +253,7 @@ public class TestUtils {
*
* @param walletRpc - wallet created with internal monero-wallet-rpc process
*/
- public static void stopWalletRpcProcess(MoneroWalletRpc walletRpc) throws InterruptedException {
+ public static void stopWalletRpcProcess(MoneroWalletRpc walletRpc) {
WALLET_PORT_OFFSETS.remove(walletRpc);
walletRpc.stopProcess();
}
diff --git a/src/test/java/utils/WalletEqualityUtils.java b/src/test/java/utils/WalletEqualityUtils.java
index 7a5dc8e..b30db1f 100644
--- a/src/test/java/utils/WalletEqualityUtils.java
+++ b/src/test/java/utils/WalletEqualityUtils.java
@@ -8,8 +8,10 @@ import java.math.BigInteger;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
+import java.util.HashSet;
import java.util.List;
import java.util.Map;
+import java.util.Set;
import monero.daemon.model.MoneroOutput;
import monero.daemon.model.MoneroTx;
@@ -40,16 +42,21 @@ public class WalletEqualityUtils {
* @param w2 a wallet to compare
*/
public static void testWalletEqualityOnChain(MoneroWallet w1, MoneroWallet w2) {
- TestUtils.WALLET_TX_TRACKER.reset(); // all wallets need to wait for txs to confirm to reliably sync
// wait for relayed txs associated with wallets to clear pool
assertEquals(w1.isConnectedToDaemon(), w2.isConnectedToDaemon());
- if (w1.isConnectedToDaemon()) TestUtils.WALLET_TX_TRACKER.waitForWalletTxsToClearPool(w1, w2);
-
- // sync the wallets until same height
- while (w1.getHeight() != w2.getHeight()) {
+ if (w1.isConnectedToDaemon()) {
+
+ // sync wallets until same height
w1.sync();
w2.sync();
+ while (w1.getHeight() != w2.getHeight()) {
+ w1.sync();
+ w2.sync();
+ }
+
+ // wait for txs to clear the pool
+ TestUtils.WALLET_TX_TRACKER.waitForTxsToClearPool(w1, w2);
}
// test that wallets are equal using only on-chain data
@@ -61,7 +68,7 @@ public class WalletEqualityUtils {
MoneroTxQuery txQuery = new MoneroTxQuery().setIsConfirmed(true);
testTxWalletsEqualOnChain(w1.getTxs(txQuery), w2.getTxs(txQuery));
txQuery.setIncludeOutputs(true);
- testTxWalletsEqualOnChain(w1.getTxs(txQuery), w2.getTxs(txQuery)); // fetch and compare outputs
+ testTxWalletsEqualOnChain(w1.getTxs(txQuery), w2.getTxs(txQuery)); // fetch and compare outputs
testAccountsEqualOnChain(w1.getAccounts(true), w2.getAccounts(true));
assertEquals(w1.getBalance(), w2.getBalance());
assertEquals(w1.getUnlockedBalance(), w2.getUnlockedBalance());
@@ -134,7 +141,21 @@ public class WalletEqualityUtils {
assertEquals(subaddress1, subaddress2);
}
- private static void testTxWalletsEqualOnChain(List<MoneroTxWallet> txs1, List<MoneroTxWallet> txs2) {
+ public static void testTxWalletsEqualOnChain(List<MoneroTxWallet> txs1, List<MoneroTxWallet> txs2) {
+
+ // remove pool or failed txs for comparison
+ txs1 = new ArrayList<MoneroTxWallet>(txs1);
+ Set<MoneroTxWallet> toRemove = new HashSet<MoneroTxWallet>();
+ for (MoneroTxWallet tx : txs1) {
+ if (Boolean.TRUE.equals(tx.inTxPool()) || Boolean.TRUE.equals(tx.isFailed())) toRemove.add(tx);
+ }
+ txs1.removeAll(toRemove);
+ txs2 = new ArrayList<MoneroTxWallet>(txs2);
+ toRemove.clear();
+ for (MoneroTxWallet tx : txs2) {
+ if (Boolean.TRUE.equals(tx.inTxPool()) || Boolean.TRUE.equals(tx.isFailed())) toRemove.add(tx);
+ }
+ txs2.removeAll(toRemove);
// nullify off-chain data for comparison
List<MoneroTxWallet> allTxs = new ArrayList<MoneroTxWallet>(txs1);
@@ -160,7 +181,7 @@ public class WalletEqualityUtils {
transferCachedInfo(tx2, tx1);
}
- // test tx equality by merging
+ // test tx equality
assertTrue(TestUtils.txsMergeable(tx1, tx2), "Txs are not mergeable");
assertEquals(tx1, tx2);
found = true;
diff --git a/src/test/java/utils/WalletTxTracker.java b/src/test/java/utils/WalletTxTracker.java
index 8e482e3..eb4b443 100644
--- a/src/test/java/utils/WalletTxTracker.java
+++ b/src/test/java/utils/WalletTxTracker.java
@@ -8,6 +8,7 @@ import monero.daemon.MoneroDaemon;
import monero.daemon.model.MoneroMiningStatus;
import monero.daemon.model.MoneroTx;
import monero.wallet.MoneroWallet;
+import monero.wallet.model.MoneroTxQuery;
import monero.wallet.model.MoneroTxWallet;
/**
@@ -21,70 +22,65 @@ import monero.wallet.model.MoneroTxWallet;
*/
public class WalletTxTracker {
- private Set<MoneroWallet> clearedWallets;
-
- public WalletTxTracker() {
- clearedWallets = new HashSet<MoneroWallet>();
- }
-
- public void reset() {
- clearedWallets.clear();
+ /**
+ * Wait for pending wallet transactions to clear the pool.
+ *
+ * @param wallets may have transactions to clear
+ */
+ public void waitForTxsToClearPool(MoneroWallet... wallets) {
+ waitForTxsToClear(false, wallets);
}
-
-// /**
-// * Reset the tracker such that all wallets except the given sending wallet will
-// * need to wait for pool txs to confirm in order to reliably sync.
-// *
-// * @param sendingWallet is the wallet which sent the tx and therefore should not cause txs to be waited on
-// */
-// public void resetExcept(MoneroWallet sendingWallet) {
-// boolean found = clearedWallets.contains(sendingWallet);
-// clearedWallets.clear();
-// if (found) clearedWallets.add(sendingWallet);
-// }
-
+
/**
- * Waits for transactions in the pool belonging to the given wallets to clear.
+ * Wait for pending wallet transasctions to clear from the wallets.
*
- * @param wallets have transactions to wait on if in the pool
+ * @param wallets may have transactions to clear
*/
- public void waitForWalletTxsToClearPool(MoneroWallet... wallets) {
-
- // get wallet tx hashes
- Set<String> txHashesWallet = new HashSet<String>();
- for (MoneroWallet wallet : wallets) {
- if (!clearedWallets.contains(wallet)) {
- wallet.sync();
- for (MoneroTxWallet tx : wallet.getTxs()) {
- txHashesWallet.add(tx.getHash());
- }
- }
- }
-
- // loop until all wallet txs clear from pool
+ public void waitForTxsToClearWallets(MoneroWallet... wallets) {
+ waitForTxsToClear(true, wallets);
+ }
+
+ private void waitForTxsToClear(boolean clearFromWallet, MoneroWallet... wallets) {
+
+ // loop until pending txs cleared
boolean isFirst = true;
boolean miningStarted = false;
MoneroDaemon daemon = TestUtils.getDaemonRpc();
while (true) {
-
- // get hashes of relayed, non-failed txs in the pool
+
+ // get pending wallet tx hashes
+ Set<String> txHashesWallet = new HashSet<String>();
+ for (MoneroWallet wallet : wallets) {
+ wallet.sync();
+ for (MoneroTxWallet tx : wallet.getTxs(new MoneroTxQuery().setInTxPool(true))) {
+ if (!tx.isRelayed()) continue;
+ else if (tx.isFailed()) daemon.flushTxPool(tx.getHash()); // flush tx if failed
+ else txHashesWallet.add(tx.getHash());
+ }
+ }
+
+ // get pending txs to wait for
Set<String> txHashesPool = new HashSet<String>();
- for (MoneroTx tx : daemon.getTxPool()) {
- if (!tx.isRelayed()) continue;
- else if (tx.isFailed()) daemon.flushTxPool(tx.getHash()); // flush tx if failed
- else txHashesPool.add(tx.getHash());
+ if (clearFromWallet) {
+ txHashesPool.addAll(txHashesWallet);
+ } else {
+ for (MoneroTx tx : daemon.getTxPool()) {
+ if (!tx.isRelayed()) continue;
+ else if (tx.isFailed()) daemon.flushTxPool(tx.getHash()); // flush tx if failed
+ else if (txHashesWallet.contains(tx.getHash())) txHashesPool.add(tx.getHash());
+ }
}
- // get hashes to wait for as intersection of wallet and pool txs
- txHashesPool.retainAll(txHashesWallet);
-
// break if no txs to wait for
- if (txHashesPool.isEmpty()) break;
+ if (txHashesPool.isEmpty()) {
+ if (miningStarted) daemon.stopMining(); // stop mining if started mining
+ break;
+ }
- // if first time waiting, log message and start mining
+ // log message and start mining if first iteration
if (isFirst) {
isFirst = false;
- System.out.println("Waiting for wallet txs to clear from the pool in order to fully sync and avoid double spend attempts (known issue)");
+ System.out.println("Waiting for wallet txs to clear from the pool in order to fully sync and avoid double spend attempts: " + txHashesPool);
MoneroMiningStatus miningStatus = daemon.getMiningStatus();
if (!miningStatus.isActive()) {
try {
@@ -94,19 +90,10 @@ public class WalletTxTracker {
}
}
- // sleep for a moment
+ // sleep for sync period
try { TimeUnit.MILLISECONDS.sleep(TestUtils.SYNC_PERIOD_IN_MS); }
catch (InterruptedException e) { throw new RuntimeException(e); }
}
-
- // stop mining if started mining
- if (miningStarted) daemon.stopMining();
-
- // sync wallets with the pool
- for (MoneroWallet wallet : wallets) {
- wallet.sync();
- clearedWallets.add(wallet);
- }
}
public BigInteger waitForUnlockedBalance(MoneroWallet wallet, Integer accountIndex, Integer subaddressIndex, BigInteger minAmount) {
Why this scored 20/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.