Skip to content

Conversation

@HashEngineering
Copy link
Collaborator

@HashEngineering HashEngineering commented Dec 26, 2025

Issue being fixed or feature implemented

What was done?

How Has This Been Tested?

Breaking Changes

Checklist:

  • I have performed a self-review of my own code
  • I have commented my code, particularly in hard-to-understand areas
  • I have added or updated relevant unit/integration/functional/e2e tests
  • I have made corresponding changes to the documentation

For repository code-owners and collaborators only

  • I have assigned this pull request to a milestone

Summary by CodeRabbit

  • Bug Fixes

    • Prevented null-pointer issues during shutdown/cleanup and synchronization checks in networking and quorum components.
    • Adjusted coinbase script-length validation to allow a narrower minimum in a specific coinbase case while preserving the existing maximum.
  • New Features

    • Per-wallet management for manual transaction confidence-change notifications.
    • APIs to enable/disable automatic notify-on-next-block, validate transactions, refresh transaction depths, and add/remove manual notify transactions.
  • Tests

    • One wallet test is now skipped (annotated to be ignored).

✏️ Tip: You can customize this high-level summary in your review settings.

@HashEngineering HashEngineering self-assigned this Dec 26, 2025
@coderabbitai
Copy link

coderabbitai bot commented Dec 26, 2025

📝 Walkthrough

Walkthrough

Defensive null-checks added in several close()/sync methods; coinbase input script-length validation refactored to compute a dynamic minimum; Wallet gains per-wallet manual confidence-change tracking, a notify-on-next-block flag and APIs to validate/update transaction depths; one test annotated to be ignored.

Changes

Cohort / File(s) Change Summary
Null-safety guards (resource cleanup & sync checks)
core/src/main/java/org/bitcoinj/evolution/SimplifiedMasternodeListManager.java, core/src/main/java/org/bitcoinj/quorums/ChainLocksHandler.java, core/src/main/java/org/bitcoinj/quorums/InstantSendManager.java
Added null checks before removing listeners or invoking handlers in close(). Added null guard in shouldProcessMNListDiff to ensure masternodeSync is non-null before accessing sync flags.
Coinbase validation refactor
core/src/main/java/org/bitcoinj/core/Transaction.java
In verify(), compute coinbase input script length once, introduce scriptMaxLength (default 2, set to 1 for TRANSACTION_COINBASE), and validate script length against [scriptMaxLength, 100] replacing the prior hard-coded lower bound.
Manual confidence-change notifications & depth APIs
core/src/main/java/org/bitcoinj/wallet/Wallet.java
Added manualConfidenceChangeTransactions list and notifyTxOnNextBlock flag with setter/getter. Added validateTransaction(Transaction), updateTransactionDepth(), addManualNotifyConfidenceChangeTransaction(Transaction), and removeManualNotifyConfidenceChangeTransaction(Transaction). notifyNewBestBlock() now branches based on notifyTxOnNextBlock.
Test adjustment
core/src/test/java/org/bitcoinj/wallet/LargeCoinJoinWalletTest.java
walletToStringTest() annotated with @Ignore in addition to @Test, causing the test to be skipped.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Poem

🐰 I hopped through code with bright-eyed cheer,
Added guards so stray NPEs fear,
Slimmed coinbase checks to fit the day,
Wallets mark confidence a new way,
A joyful hop — changes here! ✨

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 30.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title refers to a real and significant change (making transaction notification optional via notifyTxOnNextBlock), but understates the scope by grouping other substantial fixes (null-safety checks, transaction validation APIs) as unspecified 'other fixes'.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
✨ Finishing touches
  • 📝 Generate docstrings

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🧹 Nitpick comments (2)
core/src/main/java/org/bitcoinj/wallet/Wallet.java (2)

271-271: Clarify and de-duplicate depth-update logic around notifyTxOnNextBlock / manual tx list

The new notifyTxOnNextBlock flag and manual list give good control over which transactions get depth updates on each best block, but there are a few points worth tightening:

  • When notifyTxOnNextBlock is false, most BUILDING transactions will not have their depth incremented in notifyNewBestBlock, and ignoreNextNewBlock entries for those txs are not cleared until the flag is re-enabled. Callers must explicitly keep manualConfidenceChangeTransactions in sync or invoke updateTransactionDepth() to bring all depths back in line with lastBlockSeenHeight. This contract is subtle and should be documented in the new setter/javadoc so future callers understand they’re opting out of automatic depth maintenance.
  • The depth-update logic (BUILDING check, event-horizon clear, confidenceChanged.put(...)) is now duplicated three times:
    • notifyNewBestBlock full-wallet branch
    • notifyNewBestBlock manual-list branch
    • updateTransactionDepth()
      Extracting a small helper like updateDepthForTransactions(Iterable<Transaction> txs, boolean useIncrement) would reduce the risk of future divergence.

Consider adding brief Javadoc for:

  • setNotifyTxOnNextBlock(boolean) describing how it interacts with notifyNewBestBlock, ignoreNextNewBlock, and updateTransactionDepth().
  • updateTransactionDepth() clarifying when callers should use it (e.g. after long sync or when auto notifications are suppressed).

Also applies to: 1869-1875, 2632-2677, 2693-2720


1975-2012: Validate per-transaction consistency vs full-wallet checks – confirm coverage and remove dead comment

Replacing isConsistentOrThrow() in receive(...) with validateTransaction(tx) is a good performance win, but it also changes what invariants are checked:

  • validateTransaction only checks the single tx’s pool membership vs isTxConsistent(...).
  • It no longer verifies global invariants that isConsistentOrThrow() handled (e.g., duplicate tx hashes across pools, overall map sizes).

Because isConsistentOrThrow() is still called in other hot paths (maybeCommitTx, cleanup, reorganize), this is probably fine, but it’d be good to confirm you’re comfortable with not running a full wallet consistency check on every receiveFromBlock/notifyTransactionIsInBlock path anymore, and that tests cover regressions here.

Also, the commented-out call left in place is just noise now:

//        isConsistentOrThrow();
validateTransaction(tx);

It can be safely removed.

Also applies to: 2545-2547

📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between f04e6f4 and 2b09d9d.

📒 Files selected for processing (5)
  • core/src/main/java/org/bitcoinj/core/Transaction.java
  • core/src/main/java/org/bitcoinj/evolution/SimplifiedMasternodeListManager.java
  • core/src/main/java/org/bitcoinj/quorums/ChainLocksHandler.java
  • core/src/main/java/org/bitcoinj/quorums/InstantSendManager.java
  • core/src/main/java/org/bitcoinj/wallet/Wallet.java
🧰 Additional context used
🧠 Learnings (2)
📓 Common learnings
Learnt from: HashEngineering
Repo: dashpay/dashj PR: 284
File: core/src/main/java/org/bitcoinj/wallet/WalletEx.java:342-429
Timestamp: 2025-08-25T15:26:22.477Z
Learning: In WalletEx.java, the mapOutpointRoundsCache for CoinJoin rounds should only be cleared during blockchain reorganizations via the reorganize() method, not during normal wallet operations like adding transactions or moving transactions between pools. This design choice prioritizes performance for large wallets while ensuring correctness when the blockchain structure changes.
📚 Learning: 2025-08-25T15:26:22.477Z
Learnt from: HashEngineering
Repo: dashpay/dashj PR: 284
File: core/src/main/java/org/bitcoinj/wallet/WalletEx.java:342-429
Timestamp: 2025-08-25T15:26:22.477Z
Learning: In WalletEx.java, the mapOutpointRoundsCache for CoinJoin rounds should only be cleared during blockchain reorganizations via the reorganize() method, not during normal wallet operations like adding transactions or moving transactions between pools. This design choice prioritizes performance for large wallets while ensuring correctness when the blockchain structure changes.

Applied to files:

  • core/src/main/java/org/bitcoinj/wallet/Wallet.java
🧬 Code graph analysis (2)
core/src/main/java/org/bitcoinj/evolution/SimplifiedMasternodeListManager.java (1)
core/src/main/java/org/bitcoinj/core/MasternodeSync.java (1)
  • MasternodeSync (30-616)
core/src/main/java/org/bitcoinj/wallet/Wallet.java (1)
core/src/main/java/org/bitcoinj/core/TransactionConfidence.java (1)
  • TransactionConfidence (66-714)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: Analyze (java)
  • GitHub Check: JAVA 11 OS macOS-latest Gradle
  • GitHub Check: JAVA 11 OS ubuntu-latest Gradle
🔇 Additional comments (4)
core/src/main/java/org/bitcoinj/quorums/ChainLocksHandler.java (1)

133-135: LGTM! Defensive null check prevents NPE during cleanup.

The null check on peerGroup before removing the listener is appropriate and prevents potential NullPointerException if close() is called when peerGroup was never initialized or already set to null.

core/src/main/java/org/bitcoinj/quorums/InstantSendManager.java (1)

148-150: LGTM! Null guard prevents NPE during listener cleanup.

The null check on chainLocksHandler before removing the chain lock listener is appropriate, as chainLocksHandler is conditionally initialized in setBlockChain() (line 130-133) and may be null when close() is called.

core/src/main/java/org/bitcoinj/evolution/SimplifiedMasternodeListManager.java (2)

303-304: LGTM! Null guard prevents NPE when checking sync flags.

The null check on masternodeSync before evaluating sync flags is appropriate. Since masternodeSync is initialized externally via setBlockChain() (line 422), this guard prevents NullPointerException if shouldProcessMNListDiff() is called before initialization.


446-448: LGTM! Defensive null check prevents NPE during cleanup.

The null check on peerGroup before removing the listener is appropriate and consistent with similar cleanup patterns in ChainLocksHandler.java and InstantSendManager.java. This prevents potential NullPointerException during resource cleanup.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

♻️ Duplicate comments (2)
core/src/main/java/org/bitcoinj/wallet/Wallet.java (2)

239-239: Guard manualConfidenceChangeTransactions with lock to avoid races/ConcurrentModificationException

manualConfidenceChangeTransactions is a plain ArrayList written from addManualNotifyConfidenceChangeTransaction / removeManualNotifyConfidenceChangeTransaction without taking lock, but it is iterated in notifyNewBestBlock while the wallet lock is held. If these methods are ever called from a different thread than the block-processing thread, this is a data race and can throw ConcurrentModificationException or yield stale views.

This list should follow the same locking discipline as the rest of the wallet state.

Proposed fix: guard list with wallet lock
-    private final ArrayList<Transaction> manualConfidenceChangeTransactions = Lists.newArrayList();
+    @GuardedBy("lock")
+    private final List<Transaction> manualConfidenceChangeTransactions = Lists.newArrayList();
@@
-    public void addManualNotifyConfidenceChangeTransaction(Transaction tx) {
-        manualConfidenceChangeTransactions.add(tx);
-    }
+    public void addManualNotifyConfidenceChangeTransaction(Transaction tx) {
+        lock.lock();
+        try {
+            manualConfidenceChangeTransactions.add(tx);
+        } finally {
+            lock.unlock();
+        }
+    }
@@
-    public void removeManualNotifyConfidenceChangeTransaction(Transaction tx) {
-        manualConfidenceChangeTransactions.remove(tx);
-    }
+    public void removeManualNotifyConfidenceChangeTransaction(Transaction tx) {
+        lock.lock();
+        try {
+            manualConfidenceChangeTransactions.remove(tx);
+        } finally {
+            lock.unlock();
+        }
+    }

Also applies to: 2633-2676, 6585-6591


2694-2721: Fix off‑by‑one and negative depth risk in updateTransactionDepth

updateTransactionDepth() currently sets:

confidence.setDepthInBlocks(lastBlockSeenHeight - confidence.getAppearedAtChainHeight());

This yields depth 0 when a tx has just appeared in the block at appearedHeight == lastBlockSeenHeight, and is one less than expected for existing BUILDING transactions. It can also go negative if lastBlockSeenHeight is behind appearedAtChainHeight. Depth semantics elsewhere treat the appearance height as depth 1.

Align this with the usual formula and guard against out-of-order heights.

Proposed fix for depth calculation
-                    confidence.setDepthInBlocks(lastBlockSeenHeight - confidence.getAppearedAtChainHeight());
+                    int appearedHeight = confidence.getAppearedAtChainHeight();
+                    if (lastBlockSeenHeight >= appearedHeight) {
+                        confidence.setDepthInBlocks(lastBlockSeenHeight - appearedHeight + 1);
+                    }
                     if (confidence.getDepthInBlocks() > context.getEventHorizon())
                         confidence.clearBroadcastBy();
🧹 Nitpick comments (2)
core/src/main/java/org/bitcoinj/wallet/Wallet.java (2)

1975-2013: Make validateTransaction operate on the canonical wallet transaction instance

validateTransaction determines pool membership by txHash but runs isTxConsistent(...) on the Transaction instance passed in. If a caller provides a logically equivalent Transaction that is not the same instance as the one stored in the pools, the spent/unspent wiring (getOutputs(), getSpentBy(), isAvailableForSpending()) may not reflect actual wallet state, leading to spurious IllegalStateExceptions or missed inconsistencies.

To make this helper robust and self-contained, resolve the canonical transaction from the appropriate pool map before validating, and fall back early when the hash is not tracked by the wallet.

Proposed refactor to use canonical pool instance
     public boolean validateTransaction(Transaction tx) {
         lock.lock();
         try {
-            Sha256Hash txHash = tx.getTxId();
-
-            // Determine which pool the transaction is in and validate accordingly
-            if (spent.containsKey(txHash)) {
-                if (!isTxConsistent(tx, true)) {
-                    throw new IllegalStateException("Inconsistent spent tx: " + txHash);
-                }
-            } else if (unspent.containsKey(txHash)) {
-                if (!isTxConsistent(tx, false)) {
-                    throw new IllegalStateException("Inconsistent unspent tx: " + txHash);
-                }
-            } else if (pending.containsKey(txHash) || dead.containsKey(txHash)) {
-                // For pending/dead transactions, check as unspent
-                if (!isTxConsistent(tx, false)) {
-                    throw new IllegalStateException("Inconsistent pending/dead tx: " +
-                            txHash);
-                }
-            }
-            // If not in any pool, nothing to validate
-
-            return true;
+            Sha256Hash txHash = tx.getTxId();
+
+            Transaction walletTx = spent.get(txHash);
+            if (walletTx != null) {
+                if (!isTxConsistent(walletTx, true)) {
+                    throw new IllegalStateException("Inconsistent spent tx: " + txHash);
+                }
+                return true;
+            }
+
+            walletTx = unspent.get(txHash);
+            if (walletTx != null) {
+                if (!isTxConsistent(walletTx, false)) {
+                    throw new IllegalStateException("Inconsistent unspent tx: " + txHash);
+                }
+                return true;
+            }
+
+            walletTx = pending.get(txHash);
+            if (walletTx != null) {
+                if (!isTxConsistent(walletTx, false)) {
+                    throw new IllegalStateException("Inconsistent pending tx: " + txHash);
+                }
+                return true;
+            }
+
+            walletTx = dead.get(txHash);
+            if (walletTx != null) {
+                if (!isTxConsistent(walletTx, false)) {
+                    throw new IllegalStateException("Inconsistent dead tx: " + txHash);
+                }
+                return true;
+            }
+
+            // If not in any pool, nothing to validate against this wallet
+            return true;
         } finally {
             lock.unlock();
         }
     }

2547-2547: Avoid leaving commented-out validateTransaction(tx) call

The commented-out // validateTransaction(tx); in receive(...) is easy to forget and can become stale. Either remove it entirely or replace it with a more explicit TODO referencing the failing test or issue so future maintainers know why it’s intentionally disabled.

📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 2b09d9d and 2984148.

📒 Files selected for processing (1)
  • core/src/main/java/org/bitcoinj/wallet/Wallet.java
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-08-25T15:26:22.477Z
Learnt from: HashEngineering
Repo: dashpay/dashj PR: 284
File: core/src/main/java/org/bitcoinj/wallet/WalletEx.java:342-429
Timestamp: 2025-08-25T15:26:22.477Z
Learning: In WalletEx.java, the mapOutpointRoundsCache for CoinJoin rounds should only be cleared during blockchain reorganizations via the reorganize() method, not during normal wallet operations like adding transactions or moving transactions between pools. This design choice prioritizes performance for large wallets while ensuring correctness when the blockchain structure changes.

Applied to files:

  • core/src/main/java/org/bitcoinj/wallet/Wallet.java
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: Analyze (java)
  • GitHub Check: JAVA 11 OS ubuntu-latest Gradle
  • GitHub Check: JAVA 11 OS macOS-latest Gradle
🔇 Additional comments (1)
core/src/main/java/org/bitcoinj/wallet/Wallet.java (1)

271-271: notifyTxOnNextBlock flag and accessors look correct

The new notifyTxOnNextBlock toggle defaults to true, preserving existing behavior, and the simple getter/setter cleanly expose control over whether notifyNewBestBlock walks all transactions or only the manual list. No issues with this change as written.

Also applies to: 1869-1875

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

♻️ Duplicate comments (2)
core/src/main/java/org/bitcoinj/wallet/Wallet.java (2)

239-239: Manual confidence-change tracking now correctly locked; consider Set + @GuardedBy for stronger invariants

The race/ConcurrentModificationException risk around manualConfidenceChangeTransactions is addressed by taking lock in addManualNotifyConfidenceChangeTransaction/removeManualNotifyConfidenceChangeTransaction, and by iterating under lock in notifyNewBestBlock(). That aligns this state with the rest of the wallet’s locking model.

Two small robustness improvements you may want to fold in:

  • Mark the field as guarded by lock to document the invariant and help static analysis:
    • @GuardedBy("lock") private final … manualConfidenceChangeTransactions
  • Use a Set<Transaction> instead of a List to avoid accidental duplicate registrations, which would currently double-increment depth per block for that tx when notifyTxOnNextBlock == false.
Proposed refactor for `manualConfidenceChangeTransactions`
-    private final ArrayList<Transaction> manualConfidenceChangeTransactions = Lists.newArrayList();
+    @GuardedBy("lock")
+    private final Set<Transaction> manualConfidenceChangeTransactions = Sets.newHashSet();
@@
-            manualConfidenceChangeTransactions.add(tx);
+            manualConfidenceChangeTransactions.add(tx);
@@
-            manualConfidenceChangeTransactions.remove(tx);
+            manualConfidenceChangeTransactions.remove(tx);

Also applies to: 2633-2675, 6585-6602


2694-2721: Depth recomputation fix is correct; optional defensive guard on heights

updateTransactionDepth() now uses:

confidence.setDepthInBlocks(lastBlockSeenHeight - confidence.getAppearedAtChainHeight() + 1);

which matches the setAppearedAtChainHeight() semantics (depth 1 at appearance height) and fixes the previous off‑by‑one.

To make this more robust against any pathological state (e.g., if lastBlockSeenHeight were ever behind appearedAtChainHeight), you might add a simple guard:

-                    confidence.setDepthInBlocks(lastBlockSeenHeight - confidence.getAppearedAtChainHeight() + 1);
+                    int appeared = confidence.getAppearedAtChainHeight();
+                    if (lastBlockSeenHeight >= appeared && appeared > 0) {
+                        confidence.setDepthInBlocks(lastBlockSeenHeight - appeared + 1);
+                    }

Functionally, though, the current change resolves the main correctness issue.

🧹 Nitpick comments (1)
core/src/main/java/org/bitcoinj/wallet/Wallet.java (1)

271-271: notifyTxOnNextBlock flagging looks good; consider memory-visibility tightening

The new notifyTxOnNextBlock flag and branching in notifyNewBestBlock() behave as intended: existing users retain the original “update all BUILDING txs per block” path, and callers can opt into manual per-tx updates via manualConfidenceChangeTransactions.

If setNotifyTxOnNextBlock(boolean) can be called from arbitrary threads while block processing is running, you may want to either:

  • Guard the flag with lock in both setter and reads, or
  • Declare it volatile

to guarantee timely visibility across threads. The rest of the logic is fine as-is.

Also applies to: 1869-1875, 2633-2675

📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 2984148 and 92b277d.

📒 Files selected for processing (1)
  • core/src/main/java/org/bitcoinj/wallet/Wallet.java
🧰 Additional context used
🧠 Learnings (2)
📓 Common learnings
Learnt from: HashEngineering
Repo: dashpay/dashj PR: 288
File: core/src/main/java/org/bitcoinj/core/Transaction.java:1572-1577
Timestamp: 2025-12-26T19:03:14.265Z
Learning: In Dash, TRANSACTION_COINBASE (type 5, CbTx) coinbase transactions require only 1 byte minimum scriptSig length (not 2), because the block height is encoded in the CbTx special payload rather than the coinbase scriptSig. Regular coinbase transactions still require 2–100 bytes.
📚 Learning: 2025-08-25T15:26:22.477Z
Learnt from: HashEngineering
Repo: dashpay/dashj PR: 284
File: core/src/main/java/org/bitcoinj/wallet/WalletEx.java:342-429
Timestamp: 2025-08-25T15:26:22.477Z
Learning: In WalletEx.java, the mapOutpointRoundsCache for CoinJoin rounds should only be cleared during blockchain reorganizations via the reorganize() method, not during normal wallet operations like adding transactions or moving transactions between pools. This design choice prioritizes performance for large wallets while ensuring correctness when the blockchain structure changes.

Applied to files:

  • core/src/main/java/org/bitcoinj/wallet/Wallet.java
🧬 Code graph analysis (1)
core/src/main/java/org/bitcoinj/wallet/Wallet.java (1)
core/src/main/java/org/bitcoinj/core/TransactionConfidence.java (1)
  • TransactionConfidence (66-714)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: JAVA 11 OS macOS-latest Gradle
  • GitHub Check: Analyze (java)
  • GitHub Check: JAVA 11 OS ubuntu-latest Gradle

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (1)
core/src/test/java/org/bitcoinj/wallet/LargeCoinJoinWalletTest.java (1)

99-99: Track this ignored test for future resolution.

Ignoring the test is a reasonable short-term fix for the OutOfMemoryError. However, ensure this is tracked so the underlying issue can be addressed later—either by optimizing the toString() implementation for large wallets, increasing the test's heap allocation, or redesigning the test approach.

Would you like me to open a GitHub issue to track this test failure, or help investigate potential fixes such as JVM configuration for this specific test or memory optimization in the toString() methods?

📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 92b277d and b84056c.

📒 Files selected for processing (1)
  • core/src/test/java/org/bitcoinj/wallet/LargeCoinJoinWalletTest.java
🧰 Additional context used
🧠 Learnings (2)
📓 Common learnings
Learnt from: HashEngineering
Repo: dashpay/dashj PR: 288
File: core/src/main/java/org/bitcoinj/core/Transaction.java:1572-1577
Timestamp: 2025-12-26T19:03:14.265Z
Learning: In Dash, TRANSACTION_COINBASE (type 5, CbTx) coinbase transactions require only 1 byte minimum scriptSig length (not 2), because the block height is encoded in the CbTx special payload rather than the coinbase scriptSig. Regular coinbase transactions still require 2–100 bytes.
📚 Learning: 2025-08-25T15:26:22.477Z
Learnt from: HashEngineering
Repo: dashpay/dashj PR: 284
File: core/src/main/java/org/bitcoinj/wallet/WalletEx.java:342-429
Timestamp: 2025-08-25T15:26:22.477Z
Learning: In WalletEx.java, the mapOutpointRoundsCache for CoinJoin rounds should only be cleared during blockchain reorganizations via the reorganize() method, not during normal wallet operations like adding transactions or moving transactions between pools. This design choice prioritizes performance for large wallets while ensuring correctness when the blockchain structure changes.

Applied to files:

  • core/src/test/java/org/bitcoinj/wallet/LargeCoinJoinWalletTest.java
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: JAVA 11 OS macOS-latest Gradle
  • GitHub Check: Analyze (java)
  • GitHub Check: JAVA 11 OS ubuntu-latest Gradle

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

♻️ Duplicate comments (2)
core/src/main/java/org/bitcoinj/wallet/Wallet.java (2)

1975-2013: validateTransaction scope/semantics already tracked in follow‑up issue

The current validateTransaction() still treats any tx in pending/dead as “unspent” (isTxConsistent(tx, false)), which is the behavior already discussed in this PR and captured in the follow‑up issue you requested (re: pending pure‑spend txs). Given that, it’s fine to keep this helper as‑is in this PR and leave the semantic change to that dedicated issue.

If you haven’t already, please confirm that the new issue remains open and link it from this Javadoc/TODO so future readers understand why this method isn’t used from receive() yet.


2547-2548: Commented‑out validateTransaction(tx) call aligns with current TODO

The TODO here correctly reflects that validateTransaction() isn’t yet robust enough to replace isConsistentOrThrow() (as discussed earlier in the PR and captured in the follow‑up issue). Keeping this call commented out in receive() is appropriate until that work lands; just ensure the TODO or Javadoc on validateTransaction() cross‑references the tracking issue so it doesn’t get forgotten.

🧹 Nitpick comments (5)
core/src/main/java/org/bitcoinj/wallet/Wallet.java (5)

239-239: Lock-guarded manual list looks good; consider documenting the guard

manualConfidenceChangeTransactions is only accessed under lock now (including the new add/remove methods and notifyNewBestBlock), which fixes the earlier race. For future maintainers, you may want to annotate this field with @GuardedBy("lock") like spentOutpointsIndex to make the intended synchronization explicit.


271-271: Synchronize notifyTxOnNextBlock access for consistency with other wallet flags

notifyTxOnNextBlock is written/read without lock or volatile, but is consumed inside notifyNewBestBlock under lock and may be toggled from other threads via setNotifyTxOnNextBlock(). To avoid a (low‑risk) data race and match patterns like setAcceptRiskyTransactions() and setRiskAnalyzer(), consider guarding the setter/getter with lock (or making the field volatile) and optionally using the accessor from notifyNewBestBlock as well.

Also applies to: 1869-1875


2634-2677: Manual vs automatic depth updates: clarify interaction with ignoreNextNewBlock and updateTransactionDepth

The new notifyTxOnNextBlock switch and manualConfidenceChangeTransactions path correctly avoid scanning all txs when you only care about a subset, while preserving the “skip once after receive()” behavior for those manual txs via ignoreNextNewBlock.

One subtle edge case: for BUILDING transactions not in manualConfidenceChangeTransactions, ignoreNextNewBlock entries are left untouched while notifyTxOnNextBlock == false, so when you later flip it back to true, those txs will skip a single depth increment (until you call updateTransactionDepth()). If that matters for callers, consider either:

  • Documenting that the expected pattern when switching modes is: disable → run updateTransactionDepth() → re‑enable; or
  • Clearing any stale entries in ignoreNextNewBlock when running in manual mode (e.g., ignoreNextNewBlock.clear() at the end of the else branch) so future automatic updates can proceed cleanly.

2695-2722: Depth recomputation logic is now correct; optional defensive guard

updateTransactionDepth() now uses lastBlockSeenHeight - appearedAtChainHeight + 1, which matches the intended BUILDING‑depth semantics and fixes the earlier off‑by‑one. To be extra defensive against any future call sites where lastBlockSeenHeight might lag appearedAtChainHeight (e.g., unusual restore/reorg flows), you could wrap the setDepthInBlocks call with a simple guard:

Optional safety guard
-                if (confidence.getConfidenceType() == ConfidenceType.BUILDING) {
+                if (confidence.getConfidenceType() == ConfidenceType.BUILDING) {
                     // ...
-                    confidence.setDepthInBlocks(lastBlockSeenHeight - confidence.getAppearedAtChainHeight() + 1);
+                    int appeared = confidence.getAppearedAtChainHeight();
+                    if (lastBlockSeenHeight >= appeared) {
+                        confidence.setDepthInBlocks(lastBlockSeenHeight - appeared + 1);
+                    }

Not mandatory if you’re confident the invariant always holds, but it would prevent any accidental negative depths.


6587-6603: Manual confidence‑change list now thread‑safe; consider set semantics to avoid duplicates

Wrapping addManualNotifyConfidenceChangeTransaction / removeManualNotifyConfidenceChangeTransaction in lock fixes the earlier race on this list and makes its use from notifyNewBestBlock safe.

One behavioral edge case: because manualConfidenceChangeTransactions is an ArrayList, callers can add the same Transaction multiple times, which would cause incrementDepthInBlocks() to run more than once per block for that tx in manual mode. If you want to harden this API against misuse, consider switching the field to a LinkedHashSet<Transaction> (or checking contains() before add/remove) so each tx appears at most once.

📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between b84056c and 56681cd.

📒 Files selected for processing (1)
  • core/src/main/java/org/bitcoinj/wallet/Wallet.java
🧰 Additional context used
🧠 Learnings (2)
📓 Common learnings
Learnt from: HashEngineering
Repo: dashpay/dashj PR: 284
File: core/src/main/java/org/bitcoinj/wallet/WalletEx.java:342-429
Timestamp: 2025-08-25T15:26:22.477Z
Learning: In WalletEx.java, the mapOutpointRoundsCache for CoinJoin rounds should only be cleared during blockchain reorganizations via the reorganize() method, not during normal wallet operations like adding transactions or moving transactions between pools. This design choice prioritizes performance for large wallets while ensuring correctness when the blockchain structure changes.
Learnt from: HashEngineering
Repo: dashpay/dashj PR: 288
File: core/src/main/java/org/bitcoinj/core/Transaction.java:1572-1577
Timestamp: 2025-12-26T19:03:21.760Z
Learning: In Dash, TRANSACTION_COINBASE (type 5, CbTx) coinbase transactions require only 1 byte minimum scriptSig length (not 2), because the block height is encoded in the CbTx special payload rather than the coinbase scriptSig. Regular coinbase transactions still require 2–100 bytes.
📚 Learning: 2025-08-25T15:26:22.477Z
Learnt from: HashEngineering
Repo: dashpay/dashj PR: 284
File: core/src/main/java/org/bitcoinj/wallet/WalletEx.java:342-429
Timestamp: 2025-08-25T15:26:22.477Z
Learning: In WalletEx.java, the mapOutpointRoundsCache for CoinJoin rounds should only be cleared during blockchain reorganizations via the reorganize() method, not during normal wallet operations like adding transactions or moving transactions between pools. This design choice prioritizes performance for large wallets while ensuring correctness when the blockchain structure changes.

Applied to files:

  • core/src/main/java/org/bitcoinj/wallet/Wallet.java
🧬 Code graph analysis (1)
core/src/main/java/org/bitcoinj/wallet/Wallet.java (1)
core/src/main/java/org/bitcoinj/core/TransactionConfidence.java (1)
  • TransactionConfidence (66-714)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: JAVA 11 OS macOS-latest Gradle
  • GitHub Check: JAVA 11 OS ubuntu-latest Gradle
  • GitHub Check: Analyze (java)

@HashEngineering HashEngineering merged commit 0fe4195 into master Dec 30, 2025
6 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants