wallet rpc: bound getTxs re-fetch on inconsistency
What changed, and why it matters
This change fixes a potential infinite recursion bug in the Monero wallet RPC client. Previously, when the software detected inconsistent transaction data from multiple RPC calls, it would repeatedly re-fetch the data forever. Now it limits retries to 5 attempts and throws a clear error if consistency still cannot be achieved. This prevents the client from getting stuck in an endless loop, which could cause denial of service (unresponsiveness or stack overflow) rather than stealing funds or exposing secrets.
Treat as a reliability/availability hardening fix. Review whether 5 attempts is appropriate and ensure callers handle MoneroError. No immediate emergency response is indicated, but users relying on wallet RPC should update to avoid stuck or crashed clients when RPC state is inconsistent.
Security signals we found
Unbounded recursion / retry loop replaced with bounded retry limit
Potential denial of service via stack overflow or unresponsive wallet RPC client
Error handling added for unresolvable data inconsistency
Evidence from the diff
The patch refactors getTxs(MoneroTxQuery) to delegate to a new private overload getTxs(MoneroTxQuery, int maxAttempts). The public method starts with maxAttempts=5. The existing consistency check that recursively called getTxs(query) now decrements maxAttempts and throws a MoneroError when the budget is exhausted. This bounds what was previously unbounded recursion caused by transient or persistent RPC state inconsistency.
Changed components
src/main/java/monero/wallet/MoneroWalletRpc.javaMoneroWalletRpc.getTxs()Monero wallet RPC transaction retrievalInspect captured patch +7 / −2
diff --git a/src/main/java/monero/wallet/MoneroWalletRpc.java b/src/main/java/monero/wallet/MoneroWalletRpc.java
index 4d3dcb0..c4db5d0 100644
--- a/src/main/java/monero/wallet/MoneroWalletRpc.java
+++ b/src/main/java/monero/wallet/MoneroWalletRpc.java
@@ -978,7 +978,11 @@ public class MoneroWalletRpc extends MoneroWalletDefault {
@Override
public List<MoneroTxWallet> getTxs(MoneroTxQuery query) {
-
+ return getTxs(query, 5);
+ }
+
+ private List<MoneroTxWallet> getTxs(MoneroTxQuery query, int maxAttempts) {
+
// copy and normalize query
query = query == null ? new MoneroTxQuery() : query.copy();
if (query.getInputQuery() != null) query.getInputQuery().setTxQuery(query);
@@ -1045,8 +1049,9 @@ public class MoneroWalletRpc extends MoneroWalletDefault {
// special case: re-fetch txs if inconsistency caused by needing to make multiple rpc calls
for (MoneroTxWallet tx : txs) {
if (tx.isConfirmed() && tx.getBlock() == null || !tx.isConfirmed() && tx.getBlock() != null) {
+ if (maxAttempts <= 1) throw new MoneroError("Unable to build consistent txs from multiple rpc calls");
LOGGER.warning("Inconsistency detected building txs from multiple rpc calls, re-fetching");
- return getTxs(query);
+ return getTxs(query, maxAttempts - 1);
}
}
Why this scored 23/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.