wallet rpc: retain unlock notifications after long polling gaps
What changed, and why it matters
This change fixes a bug in a Monero wallet's long-polling notification system. Previously, if there was a long gap between polls, the wallet could use a height bound that was too recent and miss transactions that had since unlocked. The fix remembers the earlier height bound so notifications about unlocked funds are not lost. It is a reliability/functional bug fix rather than a clear security vulnerability.
Treat as a normal bug fix. Reviewers may verify that prevLockedTxsMinHeight is correctly reset on wallet refresh and that no other query bounds in the poller have similar aging-out issues. No urgent security response is indicated by the diff alone.
Security signals we found
Functional bug in wallet notification logic
Potential missed unlock notifications after polling gaps
No cryptographic, authentication, or input-validation changes
No explicit security relevance stated by vendor
Evidence from the diff
In MoneroWalletRpc.java’s WalletPoller, the code now stores prevLockedTxsMinHeight and uses it as the MinHeight query parameter when fetching transactions that are no longer locked. Before, it used the current poll’s minHeight, which could advance beyond the height at which previously tracked locked transactions were first seen. This caused getTxs() queries to potentially age out tracked transactions during long polling gaps, resulting in missed unlock notifications. The patch is a state-retention fix for notification completeness.
Changed components
src/main/java/monero/wallet/MoneroWalletRpc.javaWalletPoller inner classLong-polling unlock notification pathInspect captured patch +6 / −2
### src/main/java/monero/wallet/MoneroWalletRpc.java
@@ -2348,6 +2348,7 @@ private class WalletPoller {
private Long prevHeight;
private BigInteger[] prevBalances;
private List<MoneroTxWallet> prevLockedTxs = new ArrayList<MoneroTxWallet>();
+ private long prevLockedTxsMinHeight;
private Set<String> prevUnconfirmedNotifications = new HashSet<String>(); // tx hashes of previous notifications
private Set<String> prevConfirmedNotifications = new HashSet<String>(); // tx hashes of previously confirmed but not yet unlocked notifications
@@ -2394,6 +2395,7 @@ public void poll() {
prevHeight = null;
prevBalances = null;
prevLockedTxs.clear();
+ prevLockedTxsMinHeight = 0;
prevUnconfirmedNotifications.clear();
prevConfirmedNotifications.clear();
snapshotGeneration = pollGeneration;
@@ -2434,10 +2436,12 @@ public void poll() {
}
// save locked txs for next comparison
+ long prevMinHeight = prevLockedTxsMinHeight;
prevLockedTxs = lockedTxs;
+ prevLockedTxsMinHeight = minHeight;
- // fetch txs which are no longer locked
- List<MoneroTxWallet> unlockedTxs = noLongerLockedHashes.isEmpty() ? new ArrayList<MoneroTxWallet>() : getTxs(new MoneroTxQuery().setIsLocked(false).setMinHeight(minHeight).setHashes(noLongerLockedHashes).setIncludeOutputs(true));
+ // use the previous snapshot's bound so tracked txs do not age out between polls
+ List<MoneroTxWallet> unlockedTxs = noLongerLockedHashes.isEmpty() ? new ArrayList<MoneroTxWallet>() : getTxs(new MoneroTxQuery().setIsLocked(false).setMinHeight(prevMinHeight).setHashes(noLongerLockedHashes).setIncludeOutputs(true));
if (pollGeneration != generation.get()) return;
// announce new unconfirmed and confirmed txsWhy 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.