Skip to content

Conversation

@PastaPastaPasta
Copy link
Member

@PastaPastaPasta PastaPastaPasta commented Nov 10, 2025

Issue being fixed or feature implemented

Today's testing on testnet showed under load, cs_main was a major contender:

(however, note the major contenders from before #6912 / #6468 are now significantly reduced)

========================================================================================================================
TOP 40 LOCATIONS BY TOTAL CONTENTION TIME (AGGREGATE)
========================================================================================================================
Lock Name                                Location                               Count    Total(μs)    Avg(μs)    Max(μs)  Nodes
------------------------------------------------------------------------------------------------------------------------
cs                                       llmq/signing_shares.cpp:507            11952     81357095     6807.0     299734      8
::cs_main                                instantsend/signing.cpp:210            75241     62985844      837.1      93240      8
cs_main                                  index/base.cpp:340                     29584     27040349      914.0    1613365      8
cs_main                                  net_processing.cpp:5462                69291     19784473      285.5    1329191      8
cs                                       llmq/signing_shares.cpp:1732            2355     17466035     7416.6     127471      8
::cs_main                                llmq/quorums.cpp:1214                  38975     15478543      397.1     883884      8
m_nodes_mutex                            net.cpp:2043                           33457     14736059      440.4      31622      8
::cs_main                                llmq/quorums.cpp:526                   10587     10577503      999.1    2083501      8
pnode->cs_vSend                          net.cpp:2435                           68528      9671050      141.1      37265      8
cs_main                                  net_processing.cpp:4196                 4945      8608157     1740.8    1326146      8
::cs_main                                instantsend/instantsend.cpp:272         5602      8416269     1502.4    1260997      8
cs                                       txmempool.cpp:1319                      7942      8059684     1014.8     356199      8
::cs_main                                validation.cpp:3747                      309      6468144    20932.5    1225274      8
::cs_main                                validation.cpp:6009                    21855      3393152      155.3     228195      8
pnode->cs_vSend                          net.cpp:4709                            7532      2299813      305.3      41543      8
m_nodes_mutex                            ./net.h:1374                            6229      1998506      320.8      15593      8
inv_relay->m_tx_inventory_mutex          net_processing.cpp:1169                 7687      1871859      243.5      16292      8
cs_db                                    instantsend/db.cpp:239                  4323      1527297      353.3      20779      8
cs_cache                                 spork.cpp:244                          18083      1331472       73.6      27722      8
::cs_main                                chainlock/signing.cpp:58                  98      1312496    13392.8    1273691      8
cs_db                                    ./instantsend/db.h:139                  4381      1305738      298.0      29777      8
mutexMsgProc                             net.cpp:2604                            4877      1243737      255.0      24641      8
  • Add unordered_lru_cache for block heights in CInstantSendManager
  • Provide GetTipHeight() (non-optional) and GetBlockHeight() helpers
  • Use cache in InstantSendSigner::CheckCanLock
  • Cache-first for cycleHash height in ISLOCK message path and batch verify
  • Cache-first mined-height for HasChainLock and WriteInstantSendLockMined

What was done?

How Has This Been Tested?

Breaking Changes

Please describe any breaking changes your code introduces

Checklist:

Go over all the following points, and put an x in all the boxes that apply.

  • 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
  • I have assigned this pull request to a milestone (for repository code-owners and collaborators only)

@PastaPastaPasta PastaPastaPasta added this to the 23.1 milestone Nov 10, 2025
@github-actions
Copy link

github-actions bot commented Nov 10, 2025

⚠️ Potential Merge Conflicts Detected

This PR has potential conflicts with the following open PRs:

Please coordinate with the authors of these PRs to avoid merge conflicts.

@DashCoreAutoGuix
Copy link

Guix Automation has began to build this PR tagged as v23.0.0-devpr6953.ce5601c6. A new comment will be made when the image is pushed.

@coderabbitai
Copy link

coderabbitai bot commented Nov 10, 2025

Walkthrough

Adds a thread-safe block-height cache to CInstantSendManager (m_cached_block_heights, m_cached_tip_height) and exposes APIs: CacheBlockHeightInternal, CacheBlockHeight, GetBlockHeight, CacheTipHeight, and GetTipHeight. InstantSend flows (P2P message handling, ProcessInstantSendLock, ResolveBlockConflicts, RemoveConflictingLock, signing, and net handling) now consult and update the height cache, use std::optional for missing heights, and persist minedHeight when available. BlockConnected, BlockDisconnected, and UpdatedBlockTip keep the cache and cached tip height synchronized. Several methods gained cs_height_cache locking annotations and adjusted signatures to enforce cache locking discipline. ChainLock checks and islock drop logic now use cached heights when available.

Sequence Diagram(s)

sequenceDiagram
    autonumber
    participant P2P as P2P
    participant ISM as InstantSendManager
    participant Cache as HeightCache
    participant Chain as ChainState
    participant Mempool as Mempool

    P2P->>ISM: Receive ISLOCK (ProcessMessage)
    ISM->>Cache: GetBlockHeight(cycleHash)
    alt cache hit
        Cache-->>ISM: cycleHeight (optional<int>)
    else cache miss
        ISM->>Chain: LookupBlockIndex(cycleHash)
        Chain-->>ISM: CBlockIndex or null
        ISM->>Cache: CacheBlockHeight(CBlockIndex) if found
    end
    ISM->>ISM: validate cycleHeight vs dkgInterval (may bail on missing)
    ISM->>ISM: ProcessInstantSendLock(...)
    ISM->>Mempool: Find TX / check inputs
    alt TX mined
        ISM->>Cache: GetBlockHeight(hashBlock)
        alt cache hit
            Cache-->>ISM: minedHeight
        else cache miss
            ISM->>Chain: LookupBlockIndex(hashBlock)
            Chain-->>ISM: CBlockIndex or null
            ISM->>Cache: CacheBlockHeight(CBlockIndex) if found
        end
        ISM->>ISM: WriteInstantSendLockMined(minedHeight) when available
        ISM->>ISM: Drop ISLOCK uses minedHeight and HasChainLock(hashBlock, minedHeight)
    else TX unmined
        ISM->>ISM: mark pending / mempool flow
    end
Loading
sequenceDiagram
    autonumber
    participant Node as Node
    participant ISM as InstantSendManager
    participant Cache as HeightCache
    participant Chain as ChainState

    Node->>ISM: BlockConnected(pindex)
    ISM->>Cache: CacheBlockHeightInternal(pindex)
    ISM->>Cache: CacheTipHeight(pindex) -> update m_cached_tip_height

    Node->>ISM: BlockDisconnected(pindex)
    ISM->>Cache: erase cached height for pindex->GetBlockHash()
    ISM->>Chain: lookup pindex->pprev (new tip)
    alt new tip found
        ISM->>Cache: CacheTipHeight(pprev) -> update m_cached_tip_height
    else no tip
        ISM-->>Cache: set m_cached_tip_height = -1
    end
Loading
sequenceDiagram
    autonumber
    participant Signer as InstantSendSigner
    participant ISM as InstantSendManager
    participant Cache as HeightCache

    Signer->>ISM: Request validation (signing path)
    ISM->>Cache: GetBlockHeight(hashBlock)
    ISM->>Cache: GetTipHeight()
    alt heights available
        Cache-->>ISM: blockHeight, tipHeight
        ISM->>Signer: compute nTxAge = tipHeight - blockHeight + 1
    else missing
        ISM-->>Signer: bail/log debug (missing height)
    end
Loading
sequenceDiagram
    autonumber
    participant ISM as InstantSendManager
    participant Cache as HeightCache
    participant Chain as ChainState

    ISM->>Cache: GetTipHeight()
    ISM->>ISM: RemoveConflictingLock uses GetTipHeight() to decide conflicts
    alt tip known
        ISM->>ISM: resolve conflicts using tipHeight
    else unknown
        ISM->>Chain: fallback behavior (previous chain query)
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Areas requiring extra attention:

  • Concurrency and locking: correctness of cs_height_cache use, EXCLUSIVE_LOCKS_REQUIRED annotations, and interactions with cs_main and other locks.
  • Cache semantics and eviction: unordered_lru_cache behavior, MAX_BLOCK_HEIGHT_CACHE, CacheBlockHeightInternal correctness, and invalidation on reorgs.
  • Reorg and tip-sync logic: BlockDisconnected/UpdatedBlockTip correctness under reorg stress.
  • Call sites: correct handling of std::optional from GetBlockHeight in instantsend.cpp, signing.cpp, and net_instantsend.cpp.
  • ChainLock and islock drop logic: passing height with HasChainLock and potential consensus/validation implications.

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.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 accurately summarizes the main changes: introducing height caching in InstantSend to reduce cs_main lock contention, which is the primary objective of the PR.
Description check ✅ Passed The description explains the motivation (cs_main contention under load), the changes made (height caching, helper functions, cache-first lookups), and provides profiling data showing the impact.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

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.

@DashCoreAutoGuix
Copy link

Guix Automation has completed; a release should be present here: https://github.com/dashpay/dash-dev-branches/releases/tag/v23.0.0-devpr6953.ce5601c6. The image should be on dockerhub soon.

@DashCoreAutoGuix
Copy link

Guix Automation has began to build this PR tagged as v23.0.0-devpr6953.8fc12bc1. A new comment will be made when the image is pushed.

@DashCoreAutoGuix
Copy link

Guix Automation has completed; a release should be present here: https://github.com/dashpay/dash-dev-branches/releases/tag/v23.0.0-devpr6953.8fc12bc1. The image should be on dockerhub soon.

@DashCoreAutoGuix
Copy link

Guix Automation has began to build this PR tagged as v23.0.0-devpr6953.8a5b845a. A new comment will be made when the image is pushed.

@DashCoreAutoGuix
Copy link

Guix Automation has completed; a release should be present here: https://github.com/dashpay/dash-dev-branches/releases/tag/v23.0.0-devpr6953.8a5b845a. The image should be on dockerhub soon.

@DashCoreAutoGuix
Copy link

Guix Automation has began to build this PR tagged as v23.0.0-devpr6953.8bdb71d4. A new comment will be made when the image is pushed.

@DashCoreAutoGuix
Copy link

Guix Automation has completed; a release should be present here: https://github.com/dashpay/dash-dev-branches/releases/tag/v23.0.0-devpr6953.8bdb71d4. The image should be on dockerhub soon.

Uint256HashMap<int64_t> timingsTxSeen GUARDED_BY(cs_timingsTxSeen);

mutable Mutex cs_height_cache;
static constexpr size_t MAX_BLOCK_HEIGHT_CACHE{16384};
Copy link
Collaborator

Choose a reason for hiding this comment

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

isn't 16k a bit too much? rotation quorums are not alive that much; also it's very unlikely situation that tx will exist that long before it will be mined.

16k is roughly 28 days

@PastaPastaPasta
Copy link
Member Author

Deployed analysis during load on testnet. Note; that these 2 nodes can't be compared 1:1, they may be slightly differently connected, but we see significant improvements.

🔍 Overall Contention Summary

Metric Before After Change
Total Lock Contentions 134,190 157,051 +17% increase
Total Contention Time (μs) 140,162,326 126,780,252 −9.5% decrease
Average Contention Time (μs) 1,044.5 807.3 −22.7% decrease
Unique Lock Locations 171 164 −4% fewer locks

Interpretation:
Although the number of lock contentions increased (~17%), the total contention time and average wait time per contention both improved notably.
This suggests higher concurrency efficiency — the system is hitting more locks but each contention is shorter, likely due to improved lock granularity or reduced critical section duration.


⚙️ Top Locks by Total Contention Time

1. Major Improvements

  • ::cs_main instantsend/signing.cpp:210
    Removed entirely.
    This lock was the 2nd heaviest in “before,” with 25M μs total contention.
    Its removal likely shifted contention away from InstantSend signing to other modules (notably llmq/quorums.cpp).

  • cs_main index/base.cpp:340
    → Contention time dropped from 13.1M → 9.7M μs (−26%).
    Average contention decreased from 1589 → 950 μs, a major win for block index operations.

  • m_nodes_mutex net.cpp:2043
    → Total contention time decreased from 5.23M → 5.98M μs, but per-lock average dropped from 1022 → 686 μs — contention increased but each block was shorter.

  • ::cs_main validation.cpp:6009
    → Total contention dropped from 2.31M → 0.53M μs, with average from 452 → 99 μs — likely due to better validation flow parallelism.

  • ::cs_main validation.cpp:3747
    → Still present, but total contention time ~1M μs vs 0.82M μs, about the same but now less dominant in overall share.

2. New or Increased Contention

  • ::cs_main llmq/quorums.cpp:1214
    → Total contention jumped from 8.25M → 29.7M μs (+260%).
    Average ~949 μs.
    This moved to the 2 hotspot overall, implying new concurrency bottlenecks in quorum processing (likely after InstantSend refactors moved more work here).

  • cs llmq/signing_shares.cpp:507
    → Slight decrease in total contention (41.0M → 37.7M μs), but remains the 1 contention hotspot.
    Average lock duration improved from 11.6 ms → 8.96 ms (−23%).

  • cs llmq/signing_shares.cpp:1732
    → Increased slightly (10.56M → 10.28M μs).
    Still high average (10.2 ms), suggesting signing share aggregation is still a major contention source.

  • txmempool.cpp:1319
    → Total contention rose from 7.00M → 9.34M μs (+33%), with a small rise in average duration (~1977 μs).
    Indicates heavier mempool locking — possibly due to more transaction throughput.


📈 Top Locks by Contention Count

Lock (Location) Before Count After Count ΔCount Avg Time Change
pnode->cs_vSend net.cpp:2435 22,502 34,780 +55% +11% (130 → 145 μs)
::cs_main llmq/quorums.cpp:1214 9,017 31,341 +247% +4%
cs_main net_processing.cpp:5462 16,558 14,634 −12% −24%
cs_main index/base.cpp:340 8,285 10,230 +24% −40%
m_nodes_mutex net.cpp:2043 5,115 8,713 +70% −33%
::cs_main validation.cpp:6009 5,107 5,406 +6% −78%

Observations:

  • The highest-frequency locks (e.g. pnode->cs_vSend, m_nodes_mutex) show more contention events but much faster execution — again supporting that thread concurrency increased and lock contention periods shortened.
  • Network-related locks (cs_vSend, m_nodes_mutex, connman.m_nodes_mutex) became more active but more efficient, suggesting better message batching or thread handling.

🔄 Structural & Behavioral Shifts

✅ Improved

  • InstantSend & Validation path: contention drastically reduced or removed (instantsend/signing.cpp lock gone, validation.cpp locks improved).
    Likely due to better critical section partitioning or async signing refactor.
  • Average contention time per lock dropped across most major areas.

⚠️ Regressed

  • Quorum operations (llmq/quorums.cpp) now dominate total contention time.
    This suggests some of the synchronization load from InstantSend was moved to the LLMQ code.
  • txmempool.cpp contention increased, hinting at heavier mempool updates (potentially due to faster block validation or more aggressive transaction relay).

🧠 Interpretation

Overall, this transition indicates:

  • Refactor Success: The shift reduced overall contention time and made locking more granular — a clear performance gain.
  • New Bottleneck: Contention has migrated to LLMQ quorum signing and sharing, particularly around llmq/quorums.cpp:1214 and signing_shares.cpp:507.
  • Networking Improvements: Higher cs_vSend counts but low average time suggest increased concurrency and better throughput.
  • InstantSend codepaths were streamlined and now cause negligible lock contention.

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 (1)
src/instantsend/instantsend.h (1)

97-97: Reconsider the cache size constant.

The MAX_BLOCK_HEIGHT_CACHE constant is set to 16384, which represents approximately 28 days of blocks (at ~2.5 minute block time). As noted in previous review feedback, this may be excessive for the typical lifetime of transactions in the InstantSend flow. Consider reducing this to a more reasonable value based on typical InstantSend lock processing times and memory constraints.

🧹 Nitpick comments (2)
src/instantsend/instantsend.cpp (2)

131-141: Consider minor optimization in cache update.

When the block index is found via fallback lookup (line 133-138), you're calling blockIndex->GetBlockHash() again on line 139. Since islock->cycleHash should already match the block hash, you could use islock->cycleHash directly for caching.

Apply this diff:

-        CacheBlockHeight(blockIndex->GetBlockHash(), blockIndex->nHeight);
+        CacheBlockHeight(islock->cycleHash, blockIndex->nHeight);

391-399: Simplify fallback logic by relying on GetBlockHeight.

The fallback pattern here (lines 393-399) duplicates logic already present in GetBlockHeight (lines 977-995). Since GetBlockHeight already performs the lookup and caching internally, the explicit fallback here may be redundant. The current code calls GetBlockHeight once (line 391), then does manual lookup and caching if it returns nullopt (lines 393-399).

However, GetBlockHeight already does this internally. The only scenario where this explicit fallback helps is if you need to ensure caching happens even when the block isn't initially found. But GetBlockHeight already caches on successful lookup.

Consider whether this duplication is necessary or if a single GetBlockHeight call suffices.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 8fc12bc and 13727fb.

📒 Files selected for processing (3)
  • src/instantsend/instantsend.cpp (9 hunks)
  • src/instantsend/instantsend.h (6 hunks)
  • src/instantsend/signing.cpp (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/instantsend/signing.cpp
🧰 Additional context used
🧠 Learnings (7)
📓 Common learnings
Learnt from: kwvg
Repo: dashpay/dash PR: 6543
File: src/wallet/receive.cpp:240-251
Timestamp: 2025-02-06T14:34:30.466Z
Learning: Pull request #6543 is focused on move-only changes and refactoring, specifically backporting from Bitcoin. Behavior changes should be proposed in separate PRs.
Learnt from: knst
Repo: dashpay/dash PR: 6692
File: src/llmq/blockprocessor.cpp:217-224
Timestamp: 2025-08-19T14:57:31.801Z
Learning: In PR #6692, knst acknowledged a null pointer dereference issue in ProcessBlock() method where LookupBlockIndex may return nullptr but is passed to gsl::not_null, and created follow-up PR #6789 to address it, consistent with avoiding scope creep in performance-focused PRs.
📚 Learning: 2025-08-19T14:57:31.801Z
Learnt from: knst
Repo: dashpay/dash PR: 6692
File: src/llmq/blockprocessor.cpp:217-224
Timestamp: 2025-08-19T14:57:31.801Z
Learning: In PR #6692, knst acknowledged a null pointer dereference issue in ProcessBlock() method where LookupBlockIndex may return nullptr but is passed to gsl::not_null, and created follow-up PR #6789 to address it, consistent with avoiding scope creep in performance-focused PRs.

Applied to files:

  • src/instantsend/instantsend.cpp
📚 Learning: 2025-07-15T14:53:04.819Z
Learnt from: knst
Repo: dashpay/dash PR: 6691
File: src/test/llmq_params_tests.cpp:148-151
Timestamp: 2025-07-15T14:53:04.819Z
Learning: In the Dash Core LLMQ implementation, signingActiveQuorumCount is never 0 in the actual parameters defined in params.h, making division by zero scenarios unrealistic in the max_cycles() function.

Applied to files:

  • src/instantsend/instantsend.cpp
📚 Learning: 2025-05-05T12:45:44.781Z
Learnt from: knst
Repo: dashpay/dash PR: 6658
File: src/evo/creditpool.cpp:177-185
Timestamp: 2025-05-05T12:45:44.781Z
Learning: The GetAncestor() function in CBlockIndex safely handles negative heights by returning nullptr rather than asserting, making it safe to call with potentially negative values.

Applied to files:

  • src/instantsend/instantsend.cpp
📚 Learning: 2025-10-21T11:09:34.688Z
Learnt from: kwvg
Repo: dashpay/dash PR: 6849
File: src/governance/governance.cpp:1339-1343
Timestamp: 2025-10-21T11:09:34.688Z
Learning: In the Dash Core codebase, `CacheMap` (defined in src/cachemap.h) is internally thread-safe and uses its own `mutable CCriticalSection cs` to protect access to its members. Methods like `GetSize()`, `Insert()`, `Get()`, `HasKey()`, etc., can be called without holding external locks.

Applied to files:

  • src/instantsend/instantsend.h
📚 Learning: 2025-01-02T21:50:00.967Z
Learnt from: kwvg
Repo: dashpay/dash PR: 6504
File: src/llmq/context.cpp:42-43
Timestamp: 2025-01-02T21:50:00.967Z
Learning: LLMQContext manages concurrency for the `CInstantSendManager`. Previously, this was handled globally; now it's handled as a class member in `LLMQContext`, but the concurrency control remains consistent.

Applied to files:

  • src/instantsend/instantsend.h
📚 Learning: 2025-09-09T21:36:11.833Z
Learnt from: kwvg
Repo: dashpay/dash PR: 6837
File: src/stats/rawsender.cpp:0-0
Timestamp: 2025-09-09T21:36:11.833Z
Learning: In RawSender class (src/stats/rawsender.cpp), cs_net is the appropriate mutex for protecting socket access (m_sock) and network operations, not additional custom locks. The implementation correctly uses cs_net with GUARDED_BY annotations and EXCLUSIVE_LOCKS_REQUIRED to synchronize socket access between SendDirectly() and ReconnectThread().

Applied to files:

  • src/instantsend/instantsend.h
🧬 Code graph analysis (2)
src/instantsend/instantsend.cpp (1)
src/node/interfaces.cpp (23)
  • hash (893-910)
  • hash (893-893)
  • hash (932-936)
  • hash (932-932)
  • hash (958-963)
  • hash (958-958)
  • LOCK (581-585)
  • LOCK (591-598)
  • LOCK (599-606)
  • LOCK (868-877)
  • LOCK (911-915)
  • LOCK (1095-1099)
  • height (563-573)
  • height (563-563)
  • height (878-885)
  • height (878-878)
  • height (886-892)
  • height (886-886)
  • height (937-941)
  • height (937-937)
  • tip (97-105)
  • tip (586-590)
  • tip (607-615)
src/instantsend/instantsend.h (2)
src/saltedhasher.h (1)
  • uint256 (38-44)
src/instantsend/instantsend.cpp (14)
  • CacheBlockHeightInternal (965-969)
  • CacheBlockHeightInternal (965-965)
  • ProcessPendingInstantSendLocks (183-246)
  • ProcessPendingInstantSendLocks (183-183)
  • ProcessPendingInstantSendLocks (248-360)
  • ProcessPendingInstantSendLocks (248-251)
  • ProcessInstantSendLock (362-449)
  • ProcessInstantSendLock (362-363)
  • CacheBlockHeight (971-975)
  • CacheBlockHeight (971-971)
  • GetBlockHeight (977-995)
  • GetBlockHeight (977-977)
  • GetTipHeight (997-1015)
  • GetTipHeight (997-997)
🪛 GitHub Actions: Clang Diff Format Check
src/instantsend/instantsend.cpp

[error] 393-395: Clang format differences found. Apply clang-format changes to src/instantsend/instantsend.cpp. Exit code 1 due to unformatted code.

⏰ 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). (10)
  • GitHub Check: linux64_tsan-build / Build source
  • GitHub Check: arm-linux-build / Build source
  • GitHub Check: win64-build / Build source
  • GitHub Check: mac-build / Build source
  • GitHub Check: linux64_ubsan-build / Build source
  • GitHub Check: linux64_fuzz-build / Build source
  • GitHub Check: linux64-build / Build source
  • GitHub Check: linux64_sqlite-build / Build source
  • GitHub Check: linux64_nowallet-build / Build source
  • GitHub Check: Lint / Run linters
🔇 Additional comments (10)
src/instantsend/instantsend.cpp (9)

25-25: LGTM!

Adding the cassert include is appropriate for the assert statements used in the implementation.


280-292: LGTM!

The height cache integration correctly uses GetBlockHeight to fetch the cycle height and GetTipHeight to determine the appropriate signing height for quorum selection. The thread-safe cache access reduces cs_main contention as intended.


509-513: LGTM!

The block height caching during connection correctly maintains the cache and updates the tip height atomically under the lock. This ensures the cache stays synchronized with chain state.


542-550: LGTM with minor observation.

The cache invalidation logic correctly removes the disconnected block and updates the tip. The code handles the case where there's no previous block (genesis disconnect) by setting m_cached_tip_height to -1.

One minor note: when caching new_tip (line 548), we assume pprev is a valid block. This is safe because pprev is either a valid ancestor or nullptr, and we guard the caching with if (new_tip).


673-677: LGTM!

The tip height caching in UpdatedBlockTip correctly maintains synchronization between the chain state and the height cache.


859-859: LGTM!

Using GetTipHeight here reduces cs_main contention as intended by the PR objectives.


965-975: LGTM!

The internal/external caching method pattern correctly separates lock acquisition from the locked operation, allowing efficient batch caching when the lock is already held.


977-995: LGTM!

The GetBlockHeight implementation correctly:

  1. Returns std::nullopt for null/unknown hashes
  2. Checks the cache first to avoid cs_main contention
  3. Falls back to block index lookup when necessary
  4. Caches the result before returning

This achieves the PR's goal of reducing cs_main contention in the InstantSend flow.


997-1015: Document assumption in GetTipHeight assertion.

The assertion at line 1007 (assert(tip != nullptr)) assumes the chain tip is always valid. While this is typically true after genesis, consider whether this is always guaranteed in all call contexts (e.g., during initialization or chain reorgs).

If this assumption is architectural (i.e., GetTipHeight should never be called before the chain is initialized), the assert is appropriate but could benefit from a comment explaining the invariant. Otherwise, consider returning a sentinel value or throwing a more descriptive error.

Based on learnings about safe handling of potential nullptr returns from chain operations.

src/instantsend/instantsend.h (1)

98-99: StaticSaltedHasher is properly compatible with uint256.

The SaltedHasherImpl<uint256> specialization exists and uses SipHashUint256(k0, k1, v) for hashing uint256 values. StaticSaltedHasher's templated operator() delegates to SaltedHasherImpl<T>::CalcHash, which for uint256 keys provides proper cryptographic distribution. This pattern is used consistently throughout the codebase for LRU caches with uint256 keys (e.g., masternode/meta.h, llmq/signing.h), and there's even a dedicated Uint256LruHashMap type alias for this use case.

Comment on lines 391 to 174
std::optional<int> minedHeight = GetBlockHeight(hashBlock);
if (found_transaction) {
if (!minedHeight.has_value()) {
const CBlockIndex* pindexMined = WITH_LOCK(::cs_main, return m_chainstate.m_blockman.LookupBlockIndex(hashBlock));
if (pindexMined != nullptr) {
CacheBlockHeight(pindexMined->GetBlockHash(), pindexMined->nHeight);
minedHeight = pindexMined->nHeight;
}
}
// Let's see if the TX that was locked by this islock is already mined in a ChainLocked block. If yes,
// we can simply ignore the islock, as the ChainLock implies locking of all TXs in that chain
if (pindexMined != nullptr && clhandler.HasChainLock(pindexMined->nHeight, pindexMined->GetBlockHash())) {
LogPrint(BCLog::INSTANTSEND, "CInstantSendManager::%s -- txlock=%s, islock=%s: dropping islock as it already got a ChainLock in block %s, peer=%d\n", __func__,
islock->txid.ToString(), hash.ToString(), hashBlock.ToString(), from);
if (minedHeight.has_value() && clhandler.HasChainLock(*minedHeight, hashBlock)) {
LogPrint(BCLog::INSTANTSEND, /* Continued */
"CInstantSendManager::%s -- txlock=%s, islock=%s: dropping islock as it already got a " /* Continued */
"ChainLock in block %s, peer=%d\n",
__func__, islock->txid.ToString(), hash.ToString(), hashBlock.ToString(), from);
return {};
}
}

if (found_transaction) {
db.WriteNewInstantSendLock(hash, islock);
if (pindexMined) {
db.WriteInstantSendLockMined(hash, pindexMined->nHeight);
if (minedHeight.has_value()) {
db.WriteInstantSendLockMined(hash, *minedHeight);
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Fix clang-format issue.

The pipeline indicates clang-format differences at lines 393-395. Please run clang-format to resolve the formatting issue.

🧰 Tools
🪛 GitHub Actions: Clang Diff Format Check

[error] 393-395: Clang format differences found. Apply clang-format changes to src/instantsend/instantsend.cpp. Exit code 1 due to unformatted code.

🤖 Prompt for AI Agents
In src/instantsend/instantsend.cpp around lines 391 to 415, the submitted
changes trigger a clang-format mismatch specifically around lines 393-395;
reformat the file (or at least that region) with the project's clang-format
configuration to fix spacing/line breaks so the code matches style rules, then
stage and commit the formatted file. Ensure no semantic changes are
introduced—only whitespace/formatting adjustments.

@DashCoreAutoGuix
Copy link

Guix Automation has began to build this PR tagged as v23.0.0-devpr6953.13727fb3. A new comment will be made when the image is pushed.

@DashCoreAutoGuix
Copy link

Guix Automation has completed; a release should be present here: https://github.com/dashpay/dash-dev-branches/releases/tag/v23.0.0-devpr6953.13727fb3. The image should be on dockerhub soon.

@UdjinM6
Copy link

UdjinM6 commented Nov 14, 2025

pls consider ffd84b6

@PastaPastaPasta
Copy link
Member Author

@UdjinM6 applied

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 (1)
src/instantsend/instantsend.cpp (1)

393-406: Run clang-format to fix formatting violations

CI’s “Clang Diff Format Check” is still failing for this file around these lines. Please run the repository’s clang-format tooling (or clang-format-diff script mentioned in the pipeline output) on src/instantsend/instantsend.cpp and commit the resulting whitespace‑only changes so the pipeline goes green.

🧹 Nitpick comments (2)
src/instantsend/instantsend.cpp (2)

131-141: Avoid double LookupBlockIndex for cycleHash in ProcessMessage

Functionally this is fine: a missing cycleHash still leads to a small misbehaving score and a present block height gets cached and checked against dkgInterval. However, when GetBlockHeight(islock->cycleHash) returns std::nullopt for a non‑null hash, it already implies LookupBlockIndex returned nullptr, so the explicit second LookupBlockIndex call in the fallback path is redundant work.

You could simplify by treating a nullopt result directly as the “unknown cycleHash” case (and ban with score 1) or by adjusting GetBlockHeight to optionally return both height and CBlockIndex* so you don’t need to re‑lookup here.

Also applies to: 147-149


954-965: Height-cache helper implementations and locking look correct

The LRU-backed helpers (CacheBlockHeightInternal, CacheBlockHeight, GetBlockHeight, CacheTipHeight, GetTipHeight) only hold cs_height_cache around cache operations and perform cs_main lookups (LookupBlockIndex, m_chainstate.m_chain.Tip()) entirely outside that scope, so there is no cs_main+cs_height_cache lock nesting.

Using StaticSaltedHasher for the unordered_lru_cache<uint256, int, ...> key matches other salted-hash usage in the codebase, and the m_cached_tip_height == -1 sentinel plus CacheTipHeight(nullptr) branch are coherent. You might add a short comment documenting the -1 sentinel for m_cached_tip_height to make the intent obvious to future readers.

Based on learnings

Also applies to: 966-984, 986-995, 997-1010

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 13727fb and 99fac53.

📒 Files selected for processing (2)
  • src/instantsend/instantsend.cpp (9 hunks)
  • src/instantsend/instantsend.h (6 hunks)
🧰 Additional context used
🧠 Learnings (8)
📓 Common learnings
Learnt from: kwvg
Repo: dashpay/dash PR: 6543
File: src/wallet/receive.cpp:240-251
Timestamp: 2025-02-06T14:34:30.466Z
Learning: Pull request #6543 is focused on move-only changes and refactoring, specifically backporting from Bitcoin. Behavior changes should be proposed in separate PRs.
Learnt from: knst
Repo: dashpay/dash PR: 6692
File: src/llmq/blockprocessor.cpp:217-224
Timestamp: 2025-08-19T14:57:31.801Z
Learning: In PR #6692, knst acknowledged a null pointer dereference issue in ProcessBlock() method where LookupBlockIndex may return nullptr but is passed to gsl::not_null, and created follow-up PR #6789 to address it, consistent with avoiding scope creep in performance-focused PRs.
📚 Learning: 2025-10-21T11:09:34.688Z
Learnt from: kwvg
Repo: dashpay/dash PR: 6849
File: src/governance/governance.cpp:1339-1343
Timestamp: 2025-10-21T11:09:34.688Z
Learning: In the Dash Core codebase, `CacheMap` (defined in src/cachemap.h) is internally thread-safe and uses its own `mutable CCriticalSection cs` to protect access to its members. Methods like `GetSize()`, `Insert()`, `Get()`, `HasKey()`, etc., can be called without holding external locks.

Applied to files:

  • src/instantsend/instantsend.h
📚 Learning: 2025-01-02T21:50:00.967Z
Learnt from: kwvg
Repo: dashpay/dash PR: 6504
File: src/llmq/context.cpp:42-43
Timestamp: 2025-01-02T21:50:00.967Z
Learning: LLMQContext manages concurrency for the `CInstantSendManager`. Previously, this was handled globally; now it's handled as a class member in `LLMQContext`, but the concurrency control remains consistent.

Applied to files:

  • src/instantsend/instantsend.h
📚 Learning: 2025-09-09T21:36:11.833Z
Learnt from: kwvg
Repo: dashpay/dash PR: 6837
File: src/stats/rawsender.cpp:0-0
Timestamp: 2025-09-09T21:36:11.833Z
Learning: In RawSender class (src/stats/rawsender.cpp), cs_net is the appropriate mutex for protecting socket access (m_sock) and network operations, not additional custom locks. The implementation correctly uses cs_net with GUARDED_BY annotations and EXCLUSIVE_LOCKS_REQUIRED to synchronize socket access between SendDirectly() and ReconnectThread().

Applied to files:

  • src/instantsend/instantsend.h
📚 Learning: 2025-02-14T15:15:58.165Z
Learnt from: kwvg
Repo: dashpay/dash PR: 6529
File: src/rpc/governance.cpp:1074-1089
Timestamp: 2025-02-14T15:15:58.165Z
Learning: Code blocks marked with `// clang-format off` and `// clang-format on` directives should be excluded from clang-format suggestions as they are intentionally formatted manually for better readability.

Applied to files:

  • src/instantsend/instantsend.cpp
📚 Learning: 2025-07-09T15:05:36.250Z
Learnt from: kwvg
Repo: dashpay/dash PR: 6729
File: src/rpc/evo.cpp:1273-1273
Timestamp: 2025-07-09T15:05:36.250Z
Learning: When clang-format suggestions significantly harm readability (like splitting logical parameter groups across multiple lines), it's acceptable to use `// clang-format off` and `// clang-format on` directives to exclude the problematic section from automatic formatting, prioritizing code readability over strict line length compliance.

Applied to files:

  • src/instantsend/instantsend.cpp
📚 Learning: 2025-07-15T14:53:04.819Z
Learnt from: knst
Repo: dashpay/dash PR: 6691
File: src/test/llmq_params_tests.cpp:148-151
Timestamp: 2025-07-15T14:53:04.819Z
Learning: In the Dash Core LLMQ implementation, signingActiveQuorumCount is never 0 in the actual parameters defined in params.h, making division by zero scenarios unrealistic in the max_cycles() function.

Applied to files:

  • src/instantsend/instantsend.cpp
📚 Learning: 2025-05-05T12:45:44.781Z
Learnt from: knst
Repo: dashpay/dash PR: 6658
File: src/evo/creditpool.cpp:177-185
Timestamp: 2025-05-05T12:45:44.781Z
Learning: The GetAncestor() function in CBlockIndex safely handles negative heights by returning nullptr rather than asserting, making it safe to call with potentially negative values.

Applied to files:

  • src/instantsend/instantsend.cpp
🧬 Code graph analysis (2)
src/instantsend/instantsend.h (2)
src/saltedhasher.h (1)
  • uint256 (38-44)
src/instantsend/instantsend.cpp (16)
  • CacheBlockHeightInternal (954-958)
  • CacheBlockHeightInternal (954-954)
  • TransactionRemovedFromMempool (486-501)
  • TransactionRemovedFromMempool (486-486)
  • BlockDisconnected (535-546)
  • BlockDisconnected (535-536)
  • RemoveConflictingLock (844-855)
  • RemoveConflictingLock (844-844)
  • CacheBlockHeight (960-964)
  • CacheBlockHeight (960-960)
  • GetBlockHeight (966-984)
  • GetBlockHeight (966-966)
  • CacheTipHeight (986-995)
  • CacheTipHeight (986-986)
  • GetTipHeight (997-1010)
  • GetTipHeight (997-997)
src/instantsend/instantsend.cpp (2)
src/rpc/rawtransaction.cpp (1)
  • WITH_LOCK (651-651)
src/node/interfaces.cpp (15)
  • hash (893-910)
  • hash (893-893)
  • hash (932-936)
  • hash (932-932)
  • hash (958-963)
  • hash (958-958)
  • LOCK (581-585)
  • LOCK (591-598)
  • LOCK (599-606)
  • LOCK (868-877)
  • LOCK (911-915)
  • LOCK (1095-1099)
  • tip (97-105)
  • tip (586-590)
  • tip (607-615)
🪛 GitHub Actions: Clang Diff Format Check
src/instantsend/instantsend.cpp

[error] 393-394: Clang format differences found. Run 'clang-format' (or the repository's clang-format-diff script) to reformat this file. Command failed: git diff -U0 origin/develop -- $(git ls-files -- $(cat test/util/data/non-backported.txt)) | ./contrib/devtools/clang-format-diff.py -p1 > diff_output.txt

⏰ 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). (7)
  • GitHub Check: Lint / Run linters
  • GitHub Check: x86_64-w64-mingw32 / Build depends
  • GitHub Check: x86_64-apple-darwin / Build depends
  • GitHub Check: x86_64-pc-linux-gnu / Build depends
  • GitHub Check: x86_64-pc-linux-gnu_multiprocess / Build depends
  • GitHub Check: arm-linux-gnueabihf / Build depends
  • GitHub Check: x86_64-pc-linux-gnu_nowallet / Build depends
🔇 Additional comments (8)
src/instantsend/instantsend.cpp (4)

25-26: Assert header inclusion matches usage

Adding <cassert> aligns with the assert(false) in GetIdsFromLockable and avoids relying on indirect includes. No issues here.


280-283: Cycle/tip height usage in batch verify looks correct

Using GetBlockHeight(islock->cycleHash) plus GetTipHeight() to derive nSignHeight keeps cs_main access encapsulated and preserves existing behaviour (tip height falls back to -1 before the first tip, matching CChain::Height() semantics). The handling of missing cycleHash as a bad source without crashing is also appropriate.

Also applies to: 288-292


391-399: Unified minedHeight handling and chainlock check in ProcessInstantSendLock

The new minedHeight flow computes the height once via GetBlockHeight(hashBlock) with a cs_main fallback and then reuses it both for HasChainLock(*minedHeight, hashBlock) and for WriteInstantSendLockMined. This addresses the earlier duplication issue and keeps the single source of truth for the mined height.

The early GetBlockHeight(hashBlock) is safe even when hashBlock is null (it returns std::nullopt) and you correctly guard all subsequent uses with has_value(). Overall the change looks sound.

Also applies to: 402-407, 413-414


509-510: Tip-height cache maintenance in block/tip handlers looks consistent

Calling CacheTipHeight(pindex) in BlockConnected, erasing the disconnected block and then CacheTipHeight(pindexDisconnected->pprev) in BlockDisconnected, and CacheTipHeight(pindexNew) in UpdatedBlockTip, together keep m_cached_tip_height and the block-height cache aligned with the active chain. RemoveConflictingLock then correctly uses GetTipHeight() instead of touching m_chainstate directly, avoiding additional cs_main contention.

All these call sites only touch cs_height_cache locally and don’t hold it while calling out to other subsystems, so I don’t see new lock‑order risks here.

Also applies to: 538-543, 666-667, 848-849

src/instantsend/instantsend.h (4)

21-25: New includes align with added functionality

Including <optional> and <saltedhasher.h> matches the new std::optional<int> return type and StaticSaltedHasher template parameter usage. No concerns here.


96-103: Height-cache state and locking are well-scoped

Defining a dedicated cs_height_cache, the bounded MAX_BLOCK_HEIGHT_CACHE, and m_cached_block_heights plus m_cached_tip_height under that mutex cleanly isolates the height cache from the other InstantSend manager state. The private CacheBlockHeightInternal helper restricted to EXCLUSIVE_LOCKS_REQUIRED(cs_height_cache) matches the cpp usage.


127-136: Thread-safety annotations for height-cache usage are consistent

Marking the main processing methods (ProcessPendingInstantSendLocks, ProcessInstantSendLock, ResolveBlockConflicts, ProcessMessage, TransactionRemovedFromMempool, BlockConnected, BlockDisconnected, UpdatedBlockTip, RemoveConflictingLock) as EXCLUSIVE_LOCKS_REQUIRED(!cs_height_cache) and the cache helpers (CacheBlockHeight, GetBlockHeight, CacheTipHeight, GetTipHeight) as EXCLUSIVE_LOCKS_REQUIRED(!cs_height_cache) matches the implementation pattern: none of the external call sites enter with cs_height_cache held, and the helpers themselves acquire it internally.

This keeps the new cache lock integrated into the existing LLMQ/InstantSend locking scheme managed via LLMQContext without introducing obvious lock-order conflicts.

Based on learnings

Also applies to: 148-149, 161-163, 164-167, 168-170, 180-183, 189-192


189-192: New height-cache API on InstantSendSignerParent looks good

Exposing CacheBlockHeight, GetBlockHeight(const uint256&) -> std::optional<int>, CacheTipHeight, and GetTipHeight() -> int on the manager (and overriding the parent interface) is a clean way to centralize height lookups and caching. The optional return for unknown heights is already handled defensively at call sites and should help keep cs_main usage down.

UdjinM6
UdjinM6 previously approved these changes Nov 17, 2025
Copy link

@UdjinM6 UdjinM6 left a comment

Choose a reason for hiding this comment

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

utACK 99fac53

Copy link
Collaborator

@knst knst left a comment

Choose a reason for hiding this comment

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

LGTM overall 99fac53

I think you may need to squash commits; so far as some have commit message just "fixes"

if (found_transaction && !hashBlock.IsNull()) {
pindexMined = WITH_LOCK(::cs_main, return m_chainstate.m_blockman.LookupBlockIndex(hashBlock));

std::optional<int> minedHeight = GetBlockHeight(hashBlock);
Copy link
Collaborator

Choose a reason for hiding this comment

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

nit: use snake_case for local variables for new code; it should be mined_height

doc/developer-nodes.md:

- **Symbol naming conventions**. These are preferred in new code, but are not
required when doing so would need changes to significant pieces of existing
code.
  - Variable (including function arguments) and namespace names are all lowercase and may use `_` to
    separate words (snake_case).
    - Class member variables have a `m_` prefix.
    - Global variables have a `g_` prefix.
  - Constant names are all uppercase, and use `_` to separate words.
  - Enumerator constants may be `snake_case`, `PascalCase` or `ALL_CAPS`.
    This is a more tolerant policy than the [C++ Core
    Guidelines](https://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines#Renum-caps),
    which recommend using `snake_case`.  Please use what seems appropriate.
  - Class names, function names, and method names are UpperCamelCase
    (PascalCase). Do not prefix class names with `C`. See [Internal interface
    naming style](#internal-interface-naming-style) for an exception to this
    convention.

islock->txid.ToString(), hash.ToString(), hashBlock.ToString(), from);
if (minedHeight.has_value() && clhandler.HasChainLock(*minedHeight, hashBlock)) {
LogPrint(BCLog::INSTANTSEND, /* Continued */
"CInstantSendManager::%s -- txlock=%s, islock=%s: dropping islock as it already got a " /* Continued */
Copy link
Collaborator

Choose a reason for hiding this comment

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

nit: no need 2nd time continued; look like accidentally added after multiple rebase or clang-format

Copy link
Collaborator

Choose a reason for hiding this comment

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

up

Copy link
Member Author

Choose a reason for hiding this comment

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

if you give an exact diff; I can try it, I remember needing this to appease linter

Copy link
Collaborator

Choose a reason for hiding this comment

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

$ git diff
diff --git a/src/instantsend/instantsend.cpp b/src/instantsend/instantsend.cpp
index 0145d73e01..68766bf0c7 100644
--- a/src/instantsend/instantsend.cpp
+++ b/src/instantsend/instantsend.cpp
@@ -160,7 +160,7 @@ std::variant<uint256, CTransactionRef, std::monostate> CInstantSendManager::Proc
         // we can simply ignore the islock, as the ChainLock implies locking of all TXs in that chain
         if (minedHeight.has_value() && clhandler.HasChainLock(*minedHeight, hashBlock)) {
             LogPrint(BCLog::INSTANTSEND, /* Continued */
-                     "CInstantSendManager::%s -- txlock=%s, islock=%s: dropping islock as it already got a " /* Continued */
+                     "CInstantSendManager::%s -- txlock=%s, islock=%s: dropping islock as it already got a "
                      "ChainLock in block %s, peer=%d\n",
                      __func__, islock->txid.ToString(), hash.ToString(), hashBlock.ToString(), from);
             return std::monostate{};

@DashCoreAutoGuix
Copy link

Guix Automation has began to build this PR tagged as v23.0.0-devpr6953.99fac535. A new comment will be made when the image is pushed.

@DashCoreAutoGuix
Copy link

Guix Automation has completed; a release should be present here: https://github.com/dashpay/dash-dev-branches/releases/tag/v23.0.0-devpr6953.99fac535. The image should be on dockerhub soon.

@github-actions
Copy link

This pull request has conflicts, please rebase.

@DashCoreAutoGuix
Copy link

Guix Automation has began to build this PR tagged as v23.0.0-devpr6953.617ed07e. A new comment will be made when the image is pushed.

@PastaPastaPasta
Copy link
Member Author

Squashed down commits

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 (1)
src/instantsend/instantsend.cpp (1)

1-200: Fix clang-format diff reported by CI

The Clang Diff Format Check is failing for this file. Please run the project’s formatter (for example:

./contrib/devtools/clang-format-diff.py -p1

) and commit the resulting changes so CI passes. Ensure no semantic changes are introduced—only formatting.

🧹 Nitpick comments (1)
src/instantsend/instantsend.cpp (1)

146-174: Avoid unnecessary cs_main locking for mempool txs in ProcessInstantSendLock

minedHeight is initialized via GetBlockHeight(hashBlock), which is cheap when hashBlock.IsNull() (returns immediately, no cs_main). However, the fallback:

if (found_transaction) {
    if (!minedHeight.has_value()) {
        const CBlockIndex* pindexMined =
            WITH_LOCK(::cs_main, return m_chainstate.m_blockman.LookupBlockIndex(hashBlock));
        if (pindexMined != nullptr) {
            CacheBlockHeight(pindexMined);
            minedHeight = pindexMined->nHeight;
        }
    }
    if (minedHeight.has_value() && clhandler.HasChainLock(*minedHeight, hashBlock)) {
        ...
    }
}

runs for all found_transaction cases, including the common case where the tx is only in the mempool and hashBlock.IsNull(). That means you still take ::cs_main on a hot path where no block height can exist, which undermines the contention reduction this PR is targeting.

A small refactor gating the height lookup on !hashBlock.IsNull() avoids taking ::cs_main for mempool-only txs while keeping the fallback for the (rare) non-null hash cases:

-    // we ignore failure here as we must be able to propagate the lock even if we don't have the TX locally
-    std::optional<int> minedHeight = GetBlockHeight(hashBlock);
-    if (found_transaction) {
-        if (!minedHeight.has_value()) {
-            const CBlockIndex* pindexMined = WITH_LOCK(::cs_main, return m_chainstate.m_blockman.LookupBlockIndex(hashBlock));
-            if (pindexMined != nullptr) {
-                CacheBlockHeight(pindexMined);
-                minedHeight = pindexMined->nHeight;
-            }
-        }
+    // we ignore failure here as we must be able to propagate the lock even if we don't have the TX locally
+    std::optional<int> minedHeight;
+    if (!hashBlock.IsNull()) {
+        minedHeight = GetBlockHeight(hashBlock);
+        if (found_transaction && !minedHeight.has_value()) {
+            const CBlockIndex* pindexMined =
+                WITH_LOCK(::cs_main, return m_chainstate.m_blockman.LookupBlockIndex(hashBlock));
+            if (pindexMined != nullptr) {
+                CacheBlockHeight(pindexMined);
+                minedHeight = pindexMined->nHeight;
+            }
+        }
+    }
     if (found_transaction) {
         // Let's see if the TX that was locked by this islock is already mined in a ChainLocked block. If yes,
         // we can simply ignore the islock, as the ChainLock implies locking of all TXs in that chain
         if (minedHeight.has_value() && clhandler.HasChainLock(*minedHeight, hashBlock)) {
             ...
         }
     }

This keeps behavior the same for mined transactions while eliminating unnecessary cs_main traffic for mempool ones.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 617ed07 and 186c81d.

📒 Files selected for processing (5)
  • src/instantsend/instantsend.cpp (6 hunks)
  • src/instantsend/instantsend.h (5 hunks)
  • src/instantsend/net_instantsend.cpp (2 hunks)
  • src/instantsend/signing.cpp (1 hunks)
  • src/instantsend/signing.h (2 hunks)
🚧 Files skipped from review as they are similar to previous changes (3)
  • src/instantsend/signing.cpp
  • src/instantsend/net_instantsend.cpp
  • src/instantsend/signing.h
🧰 Additional context used
🧠 Learnings (8)
📓 Common learnings
Learnt from: kwvg
Repo: dashpay/dash PR: 6543
File: src/wallet/receive.cpp:240-251
Timestamp: 2025-02-06T14:34:30.466Z
Learning: Pull request #6543 is focused on move-only changes and refactoring, specifically backporting from Bitcoin. Behavior changes should be proposed in separate PRs.
Learnt from: knst
Repo: dashpay/dash PR: 6692
File: src/llmq/blockprocessor.cpp:217-224
Timestamp: 2025-08-19T14:57:31.801Z
Learning: In PR #6692, knst acknowledged a null pointer dereference issue in ProcessBlock() method where LookupBlockIndex may return nullptr but is passed to gsl::not_null, and created follow-up PR #6789 to address it, consistent with avoiding scope creep in performance-focused PRs.
Learnt from: kwvg
Repo: dashpay/dash PR: 6849
File: src/governance/governance.cpp:1339-1343
Timestamp: 2025-10-21T11:09:34.688Z
Learning: In the Dash Core codebase, `CacheMap` (defined in src/cachemap.h) is internally thread-safe and uses its own `mutable CCriticalSection cs` to protect access to its members. Methods like `GetSize()`, `Insert()`, `Get()`, `HasKey()`, etc., can be called without holding external locks.
Learnt from: knst
Repo: dashpay/dash PR: 6658
File: src/evo/creditpool.cpp:177-185
Timestamp: 2025-05-05T12:45:44.781Z
Learning: The GetAncestor() function in CBlockIndex safely handles negative heights by returning nullptr rather than asserting, making it safe to call with potentially negative values.
📚 Learning: 2025-02-14T15:15:58.165Z
Learnt from: kwvg
Repo: dashpay/dash PR: 6529
File: src/rpc/governance.cpp:1074-1089
Timestamp: 2025-02-14T15:15:58.165Z
Learning: Code blocks marked with `// clang-format off` and `// clang-format on` directives should be excluded from clang-format suggestions as they are intentionally formatted manually for better readability.

Applied to files:

  • src/instantsend/instantsend.cpp
📚 Learning: 2025-07-09T15:05:36.250Z
Learnt from: kwvg
Repo: dashpay/dash PR: 6729
File: src/rpc/evo.cpp:1273-1273
Timestamp: 2025-07-09T15:05:36.250Z
Learning: When clang-format suggestions significantly harm readability (like splitting logical parameter groups across multiple lines), it's acceptable to use `// clang-format off` and `// clang-format on` directives to exclude the problematic section from automatic formatting, prioritizing code readability over strict line length compliance.

Applied to files:

  • src/instantsend/instantsend.cpp
📚 Learning: 2025-05-05T12:45:44.781Z
Learnt from: knst
Repo: dashpay/dash PR: 6658
File: src/evo/creditpool.cpp:177-185
Timestamp: 2025-05-05T12:45:44.781Z
Learning: The GetAncestor() function in CBlockIndex safely handles negative heights by returning nullptr rather than asserting, making it safe to call with potentially negative values.

Applied to files:

  • src/instantsend/instantsend.cpp
📚 Learning: 2025-10-21T11:09:34.688Z
Learnt from: kwvg
Repo: dashpay/dash PR: 6849
File: src/governance/governance.cpp:1339-1343
Timestamp: 2025-10-21T11:09:34.688Z
Learning: In the Dash Core codebase, `CacheMap` (defined in src/cachemap.h) is internally thread-safe and uses its own `mutable CCriticalSection cs` to protect access to its members. Methods like `GetSize()`, `Insert()`, `Get()`, `HasKey()`, etc., can be called without holding external locks.

Applied to files:

  • src/instantsend/instantsend.h
📚 Learning: 2025-01-14T09:06:19.717Z
Learnt from: kwvg
Repo: dashpay/dash PR: 6532
File: src/test/fuzz/netaddress.cpp:83-84
Timestamp: 2025-01-14T09:06:19.717Z
Learning: In fuzzer harness tests, CServiceHash can be used with both default constructor (CServiceHash()) and parameterized constructor (CServiceHash(salt_k0, salt_k1)) to test different variants. The usage pattern CServiceHash()(service) and CServiceHash(0, 0)(service) is valid and intentional in such tests.

Applied to files:

  • src/instantsend/instantsend.h
📚 Learning: 2025-01-02T21:50:00.967Z
Learnt from: kwvg
Repo: dashpay/dash PR: 6504
File: src/llmq/context.cpp:42-43
Timestamp: 2025-01-02T21:50:00.967Z
Learning: LLMQContext manages concurrency for the `CInstantSendManager`. Previously, this was handled globally; now it's handled as a class member in `LLMQContext`, but the concurrency control remains consistent.

Applied to files:

  • src/instantsend/instantsend.h
📚 Learning: 2025-09-09T21:36:11.833Z
Learnt from: kwvg
Repo: dashpay/dash PR: 6837
File: src/stats/rawsender.cpp:0-0
Timestamp: 2025-09-09T21:36:11.833Z
Learning: In RawSender class (src/stats/rawsender.cpp), cs_net is the appropriate mutex for protecting socket access (m_sock) and network operations, not additional custom locks. The implementation correctly uses cs_net with GUARDED_BY annotations and EXCLUSIVE_LOCKS_REQUIRED to synchronize socket access between SendDirectly() and ReconnectThread().

Applied to files:

  • src/instantsend/instantsend.h
🧬 Code graph analysis (2)
src/instantsend/instantsend.cpp (1)
src/node/interfaces.cpp (15)
  • hash (893-910)
  • hash (893-893)
  • hash (932-936)
  • hash (932-932)
  • hash (958-963)
  • hash (958-958)
  • LOCK (581-585)
  • LOCK (591-598)
  • LOCK (599-606)
  • LOCK (868-877)
  • LOCK (911-915)
  • LOCK (1095-1099)
  • tip (97-105)
  • tip (586-590)
  • tip (607-615)
src/instantsend/instantsend.h (2)
src/saltedhasher.h (1)
  • uint256 (38-44)
src/instantsend/instantsend.cpp (24)
  • CacheBlockHeightInternal (724-728)
  • CacheBlockHeightInternal (724-724)
  • TransactionAddedToMempool (203-236)
  • TransactionAddedToMempool (203-203)
  • TransactionRemovedFromMempool (238-253)
  • TransactionRemovedFromMempool (238-238)
  • BlockConnected (255-285)
  • BlockConnected (255-255)
  • BlockDisconnected (287-298)
  • BlockDisconnected (287-288)
  • RemoveConflictingLock (614-625)
  • RemoveConflictingLock (614-614)
  • TryEmplacePendingLock (419-427)
  • TryEmplacePendingLock (419-420)
  • GetInstantSendLockCount (719-722)
  • GetInstantSendLockCount (719-719)
  • CacheBlockHeight (730-734)
  • CacheBlockHeight (730-730)
  • GetBlockHeight (736-754)
  • GetBlockHeight (736-736)
  • CacheTipHeight (756-765)
  • CacheTipHeight (756-756)
  • GetTipHeight (767-780)
  • GetTipHeight (767-767)
🪛 GitHub Actions: Clang Diff Format Check
src/instantsend/instantsend.cpp

[error] 150-150: Clang format differences found. Run './contrib/devtools/clang-format-diff.py -p1' to fix code style issues in this file.

⏰ 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). (10)
  • GitHub Check: linux64_tsan-build / Build source
  • GitHub Check: mac-build / Build source
  • GitHub Check: linux64_nowallet-build / Build source
  • GitHub Check: linux64_sqlite-build / Build source
  • GitHub Check: linux64_ubsan-build / Build source
  • GitHub Check: linux64_fuzz-build / Build source
  • GitHub Check: linux64-build / Build source
  • GitHub Check: arm-linux-build / Build source
  • GitHub Check: win64-build / Build source
  • GitHub Check: Lint / Run linters
🔇 Additional comments (3)
src/instantsend/instantsend.h (1)

20-26: Height cache state + locking annotations look consistent

The new cs_height_cache mutex, LRU cache members, tip-height field, and the Cache*/Get* helpers form a self-contained height cache. The EXCLUSIVE_LOCKS_REQUIRED(!cs_height_cache) annotations on callers that invoke GetBlockHeight/GetTipHeight are appropriate to avoid re-entering the non-recursive Mutex and should help keep lock ordering sane while reducing direct cs_main usage. I don’t see correctness issues here.

Also applies to: 96-103, 135-136, 155-156, 159-163, 173-176, 182-185

src/instantsend/instantsend.cpp (2)

255-297: Tip-height cache wiring in block callbacks looks correct

Using CacheTipHeight(pindex) in BlockConnected, erasing the disconnected block and then CacheTipHeight(pindexDisconnected->pprev) in BlockDisconnected, and calling CacheTipHeight(pindexNew) at the start of UpdatedBlockTip, plus consuming GetTipHeight() in RemoveConflictingLock, gives a coherent picture: the height cache is kept up to date on connects, disconnects, and tip updates, and consumers no longer need to touch cs_main directly. The calls are idempotent and don’t hold cs_height_cache across any other heavy work. This looks good.

Also applies to: 434-451, 614-621


724-780: Height cache helper implementations and lock ordering look safe

CacheBlockHeightInternal, CacheBlockHeight, GetBlockHeight, CacheTipHeight, and GetTipHeight:

  • Always guard m_cached_block_heights / m_cached_tip_height with cs_height_cache.
  • Use short inner scopes so cs_height_cache is released before any WITH_LOCK(::cs_main, ...) call.
  • Reacquire cs_height_cache only after ::cs_main has been fully released (via CacheBlockHeight / CacheTipHeight), avoiding overlapping locks and deadlock risk.
  • Treat unknown block hashes and missing tips gracefully (std::nullopt / -1).

This matches the intent of the new EXCLUSIVE_LOCKS_REQUIRED(!cs_height_cache) annotations and aligns with the PR’s goal of reducing cs_main contention.

knst
knst previously approved these changes Nov 19, 2025
Copy link
Collaborator

@knst knst left a comment

Choose a reason for hiding this comment

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

LGTM 186c81d

islock->txid.ToString(), hash.ToString(), hashBlock.ToString(), from);
if (minedHeight.has_value() && clhandler.HasChainLock(*minedHeight, hashBlock)) {
LogPrint(BCLog::INSTANTSEND, /* Continued */
"CInstantSendManager::%s -- txlock=%s, islock=%s: dropping islock as it already got a " /* Continued */
Copy link
Collaborator

Choose a reason for hiding this comment

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

up

PastaPastaPasta and others added 3 commits November 19, 2025 13:38
- Add unordered_lru_cache for block heights in CInstantSendManager
- Provide GetTipHeight() (non-optional) and GetBlockHeight() helpers
- Use cache in InstantSendSigner::CheckCanLock
- Cache-first for cycleHash height in ISLOCK message path and batch verify
- Cache-first mined-height for HasChainLock and WriteInstantSendLockMined

Only InstantSend files are included in this commit.
Add CacheTipHeight() helper to eliminate code duplication when updating
tip height cache. Refactor cache methods to accept CBlockIndex* instead
of hash/height pairs for type safety. Handle nullptr cases properly.
@PastaPastaPasta
Copy link
Member Author

Resolved

Copy link
Collaborator

@knst knst left a comment

Choose a reason for hiding this comment

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

LGTM e918729

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 (1)
src/instantsend/instantsend.cpp (1)

150-173: Tidy minedHeight computation and avoid unnecessary cache lookup for mempool-only txes

The reworked minedHeight handling is nice: you centralize height lookup via GetBlockHeight(hashBlock), reuse it for both the ChainLock short-circuit and WriteInstantSendLockMined, and only fall back to LookupBlockIndex when the cache/helper couldn’t provide a height.

One small simplification: GetBlockHeight(hashBlock) is invoked even when hashBlock is still null (typical for mempool-only txes) and even when found_transaction is false. That just grabs cs_height_cache, sees hash.IsNull(), and returns. You could avoid that entirely by gating the lookup on found_transaction and !hashBlock.IsNull():

-    const bool found_transaction{tx != nullptr};
-    // we ignore failure here as we must be able to propagate the lock even if we don't have the TX locally
-    std::optional<int> minedHeight = GetBlockHeight(hashBlock);
-    if (found_transaction) {
+    const bool found_transaction{tx != nullptr};
+    // we ignore failure here as we must be able to propagate the lock even if we don't have the TX locally
+    std::optional<int> minedHeight;
+    if (found_transaction && !hashBlock.IsNull()) {
+        minedHeight = GetBlockHeight(hashBlock);
+    }
+    if (found_transaction) {
         if (!minedHeight.has_value()) {
             const CBlockIndex* pindexMined = WITH_LOCK(::cs_main, return m_chainstate.m_blockman.LookupBlockIndex(hashBlock));
             if (pindexMined != nullptr) {
                 CacheBlockHeight(pindexMined);
                 minedHeight = pindexMined->nHeight;
             }
         }
         // Let's see if the TX that was locked by this islock is already mined in a ChainLocked block. If yes,
         // we can simply ignore the islock, as the ChainLock implies locking of all TXs in that chain
         if (minedHeight.has_value() && clhandler.HasChainLock(*minedHeight, hashBlock)) {
             ...
         }
     }

Also, the CI reports a clang-format diff for this file around this region; after adjusting the logic, please run the project’s clang-format to clear that.

🧹 Nitpick comments (3)
src/instantsend/net_instantsend.cpp (2)

37-47: Avoid redundant cs_main lookup after GetBlockHeight in ISDLOCK path

GetBlockHeight(islock->cycleHash) already falls back to LookupBlockIndex under ::cs_main and caches the height. The subsequent WITH_LOCK(::cs_main, LookupBlockIndex) + CacheBlockHeight + manual assignment repeats that work and reintroduces cs_main contention on a path you just cache-ified.

You can simplify to rely solely on the cache helper, e.g.:

-    auto cycleHeightOpt = m_is_manager.GetBlockHeight(islock->cycleHash);
-    if (!cycleHeightOpt) {
-        const auto blockIndex = WITH_LOCK(::cs_main, return m_chainstate.m_blockman.LookupBlockIndex(islock->cycleHash));
-        if (blockIndex == nullptr) {
-            // Maybe we don't have the block yet or maybe some peer spams invalid values for cycleHash
-            m_peer_manager->PeerMisbehaving(pfrom.GetId(), 1);
-            return;
-        }
-        m_is_manager.CacheBlockHeight(blockIndex);
-        cycleHeightOpt = blockIndex->nHeight;
-    }
-    const int block_height = *cycleHeightOpt;
+    auto cycleHeightOpt = m_is_manager.GetBlockHeight(islock->cycleHash);
+    if (!cycleHeightOpt) {
+        // Maybe we don't have the block yet or maybe some peer spams invalid values for cycleHash
+        m_peer_manager->PeerMisbehaving(pfrom.GetId(), 1);
+        return;
+    }
+    const int block_height = *cycleHeightOpt;

128-139: Batch path height logic is correct; optionally cache tipHeight once per batch

Using GetBlockHeight(islock->cycleHash) and GetTipHeight() here matches the new cached height flow and avoids direct cs_main usage. If you want to shave a bit more overhead in this hot loop, you could hoist const int tipHeight = m_is_manager.GetTipHeight(); out of the for and reuse it for all pending locks in this batch, since a small drift in tip during verification is acceptable.

src/instantsend/instantsend.h (1)

19-26: Ensure header self-contains <unordered_map> and briefly document height-cache intent

The new height-cache members (cs_height_cache, MAX_BLOCK_HEIGHT_CACHE, m_cached_block_heights, m_cached_tip_height) and use of unordered_lru_cache<uint256, int, StaticSaltedHasher, MAX_BLOCK_HEIGHT_CACHE> look fine.

This header still declares std::unordered_map<COutPoint, uint256, SaltedOutpointHasher> nonLockedTxsByOutpoints, but no longer explicitly includes <unordered_map>. If that include was removed here and isn’t guaranteed via another header in this file’s includes, it’s safer to restore it so the header is self-contained.

It could also help future maintainers if you add a short comment near cs_height_cache / the cache members explaining that these caches are best-effort helpers to reduce cs_main contention and should not be accessed while holding ::cs_main (to keep lock ordering clear, matching the .cpp helpers).

Also applies to: 96-103

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 186c81d and e918729.

📒 Files selected for processing (5)
  • src/instantsend/instantsend.cpp (6 hunks)
  • src/instantsend/instantsend.h (5 hunks)
  • src/instantsend/net_instantsend.cpp (2 hunks)
  • src/instantsend/signing.cpp (1 hunks)
  • src/instantsend/signing.h (2 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • src/instantsend/signing.cpp
  • src/instantsend/signing.h
🧰 Additional context used
🧠 Learnings (12)
📓 Common learnings
Learnt from: kwvg
Repo: dashpay/dash PR: 6543
File: src/wallet/receive.cpp:240-251
Timestamp: 2025-02-06T14:34:30.466Z
Learning: Pull request #6543 is focused on move-only changes and refactoring, specifically backporting from Bitcoin. Behavior changes should be proposed in separate PRs.
Learnt from: knst
Repo: dashpay/dash PR: 6658
File: src/evo/creditpool.cpp:177-185
Timestamp: 2025-05-05T12:45:44.781Z
Learning: The GetAncestor() function in CBlockIndex safely handles negative heights by returning nullptr rather than asserting, making it safe to call with potentially negative values.
Learnt from: kwvg
Repo: dashpay/dash PR: 6849
File: src/governance/governance.cpp:1339-1343
Timestamp: 2025-10-21T11:09:34.688Z
Learning: In the Dash Core codebase, `CacheMap` (defined in src/cachemap.h) is internally thread-safe and uses its own `mutable CCriticalSection cs` to protect access to its members. Methods like `GetSize()`, `Insert()`, `Get()`, `HasKey()`, etc., can be called without holding external locks.
📚 Learning: 2025-08-19T14:57:31.801Z
Learnt from: knst
Repo: dashpay/dash PR: 6692
File: src/llmq/blockprocessor.cpp:217-224
Timestamp: 2025-08-19T14:57:31.801Z
Learning: In PR #6692, knst acknowledged a null pointer dereference issue in ProcessBlock() method where LookupBlockIndex may return nullptr but is passed to gsl::not_null, and created follow-up PR #6789 to address it, consistent with avoiding scope creep in performance-focused PRs.

Applied to files:

  • src/instantsend/net_instantsend.cpp
📚 Learning: 2025-11-04T18:23:28.175Z
Learnt from: UdjinM6
Repo: dashpay/dash PR: 6933
File: src/governance/classes.cpp:147-154
Timestamp: 2025-11-04T18:23:28.175Z
Learning: In src/governance/classes.cpp, CSuperblock::GetPaymentsLimit intentionally uses synthetic difficulty constants (nBits = 1 for mainnet, powLimit for networks with min difficulty) and simple height-based V20 activation checks instead of actual chain block data. This is by design because superblocks themselves are "synthetic" governance payment blocks, not regular mined blocks.

Applied to files:

  • src/instantsend/net_instantsend.cpp
📚 Learning: 2025-07-15T14:53:04.819Z
Learnt from: knst
Repo: dashpay/dash PR: 6691
File: src/test/llmq_params_tests.cpp:148-151
Timestamp: 2025-07-15T14:53:04.819Z
Learning: In the Dash Core LLMQ implementation, signingActiveQuorumCount is never 0 in the actual parameters defined in params.h, making division by zero scenarios unrealistic in the max_cycles() function.

Applied to files:

  • src/instantsend/net_instantsend.cpp
📚 Learning: 2025-02-14T15:15:58.165Z
Learnt from: kwvg
Repo: dashpay/dash PR: 6529
File: src/rpc/governance.cpp:1074-1089
Timestamp: 2025-02-14T15:15:58.165Z
Learning: Code blocks marked with `// clang-format off` and `// clang-format on` directives should be excluded from clang-format suggestions as they are intentionally formatted manually for better readability.

Applied to files:

  • src/instantsend/instantsend.cpp
📚 Learning: 2025-07-09T15:05:36.250Z
Learnt from: kwvg
Repo: dashpay/dash PR: 6729
File: src/rpc/evo.cpp:1273-1273
Timestamp: 2025-07-09T15:05:36.250Z
Learning: When clang-format suggestions significantly harm readability (like splitting logical parameter groups across multiple lines), it's acceptable to use `// clang-format off` and `// clang-format on` directives to exclude the problematic section from automatic formatting, prioritizing code readability over strict line length compliance.

Applied to files:

  • src/instantsend/instantsend.cpp
📚 Learning: 2025-10-05T20:38:28.457Z
Learnt from: knst
Repo: dashpay/dash PR: 6871
File: contrib/guix/libexec/build.sh:358-360
Timestamp: 2025-10-05T20:38:28.457Z
Learning: In the Dash repository, when backporting code from Bitcoin Core, typos and minor issues in comments should be kept as-is to reduce merge conflicts in future backports, even if they remain unfixed in Bitcoin Core's master branch.

Applied to files:

  • src/instantsend/instantsend.cpp
📚 Learning: 2025-05-05T12:45:44.781Z
Learnt from: knst
Repo: dashpay/dash PR: 6658
File: src/evo/creditpool.cpp:177-185
Timestamp: 2025-05-05T12:45:44.781Z
Learning: The GetAncestor() function in CBlockIndex safely handles negative heights by returning nullptr rather than asserting, making it safe to call with potentially negative values.

Applied to files:

  • src/instantsend/instantsend.cpp
📚 Learning: 2025-10-21T11:09:34.688Z
Learnt from: kwvg
Repo: dashpay/dash PR: 6849
File: src/governance/governance.cpp:1339-1343
Timestamp: 2025-10-21T11:09:34.688Z
Learning: In the Dash Core codebase, `CacheMap` (defined in src/cachemap.h) is internally thread-safe and uses its own `mutable CCriticalSection cs` to protect access to its members. Methods like `GetSize()`, `Insert()`, `Get()`, `HasKey()`, etc., can be called without holding external locks.

Applied to files:

  • src/instantsend/instantsend.h
📚 Learning: 2025-01-14T09:06:19.717Z
Learnt from: kwvg
Repo: dashpay/dash PR: 6532
File: src/test/fuzz/netaddress.cpp:83-84
Timestamp: 2025-01-14T09:06:19.717Z
Learning: In fuzzer harness tests, CServiceHash can be used with both default constructor (CServiceHash()) and parameterized constructor (CServiceHash(salt_k0, salt_k1)) to test different variants. The usage pattern CServiceHash()(service) and CServiceHash(0, 0)(service) is valid and intentional in such tests.

Applied to files:

  • src/instantsend/instantsend.h
📚 Learning: 2025-01-02T21:50:00.967Z
Learnt from: kwvg
Repo: dashpay/dash PR: 6504
File: src/llmq/context.cpp:42-43
Timestamp: 2025-01-02T21:50:00.967Z
Learning: LLMQContext manages concurrency for the `CInstantSendManager`. Previously, this was handled globally; now it's handled as a class member in `LLMQContext`, but the concurrency control remains consistent.

Applied to files:

  • src/instantsend/instantsend.h
📚 Learning: 2025-09-09T21:36:11.833Z
Learnt from: kwvg
Repo: dashpay/dash PR: 6837
File: src/stats/rawsender.cpp:0-0
Timestamp: 2025-09-09T21:36:11.833Z
Learning: In RawSender class (src/stats/rawsender.cpp), cs_net is the appropriate mutex for protecting socket access (m_sock) and network operations, not additional custom locks. The implementation correctly uses cs_net with GUARDED_BY annotations and EXCLUSIVE_LOCKS_REQUIRED to synchronize socket access between SendDirectly() and ReconnectThread().

Applied to files:

  • src/instantsend/instantsend.h
🧬 Code graph analysis (3)
src/instantsend/net_instantsend.cpp (1)
src/net_processing.cpp (3)
  • WITH_LOCK (331-334)
  • WITH_LOCK (3230-3230)
  • WITH_LOCK (3252-3252)
src/instantsend/instantsend.cpp (1)
src/node/interfaces.cpp (15)
  • hash (893-910)
  • hash (893-893)
  • hash (932-936)
  • hash (932-932)
  • hash (958-963)
  • hash (958-958)
  • LOCK (581-585)
  • LOCK (591-598)
  • LOCK (599-606)
  • LOCK (868-877)
  • LOCK (911-915)
  • LOCK (1095-1099)
  • tip (97-105)
  • tip (586-590)
  • tip (607-615)
src/instantsend/instantsend.h (2)
src/saltedhasher.h (1)
  • uint256 (38-44)
src/instantsend/instantsend.cpp (22)
  • CacheBlockHeightInternal (724-728)
  • CacheBlockHeightInternal (724-724)
  • TransactionAddedToMempool (203-236)
  • TransactionAddedToMempool (203-203)
  • TransactionRemovedFromMempool (238-253)
  • TransactionRemovedFromMempool (238-238)
  • BlockConnected (255-285)
  • BlockConnected (255-255)
  • BlockDisconnected (287-298)
  • BlockDisconnected (287-288)
  • RemoveConflictingLock (614-625)
  • RemoveConflictingLock (614-614)
  • GetInstantSendLockCount (719-722)
  • GetInstantSendLockCount (719-719)
  • CacheBlockHeight (730-734)
  • CacheBlockHeight (730-730)
  • GetBlockHeight (736-754)
  • GetBlockHeight (736-736)
  • CacheTipHeight (756-765)
  • CacheTipHeight (756-756)
  • GetTipHeight (767-780)
  • GetTipHeight (767-767)
🪛 GitHub Actions: Clang Diff Format Check
src/instantsend/instantsend.cpp

[error] 150-150: clang-format-diff differences detected between pre- and post-formatting for this file.

⏰ 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). (10)
  • GitHub Check: mac-build / Build source
  • GitHub Check: win64-build / Build source
  • GitHub Check: linux64_nowallet-build / Build source
  • GitHub Check: linux64-build / Build source
  • GitHub Check: linux64_sqlite-build / Build source
  • GitHub Check: linux64_ubsan-build / Build source
  • GitHub Check: linux64_fuzz-build / Build source
  • GitHub Check: arm-linux-build / Build source
  • GitHub Check: linux64_tsan-build / Build source
  • GitHub Check: Lint / Run linters
🔇 Additional comments (3)
src/instantsend/instantsend.cpp (2)

261-285: Height-cache wiring in block/tip callbacks and conflict removal looks consistent

Hooking CacheTipHeight(pindex) into BlockConnected and UpdatedBlockTip, and cleaning up m_cached_block_heights plus recaching the previous tip in BlockDisconnected, keeps the tip/height cache in sync with chainstate without exposing new cs_main callsites. Likewise, using GetTipHeight() in RemoveConflictingLock preserves the previous behavior (passing current tip height into RemoveChainedInstantSendLocks) while benefiting from the cached value. I don’t see functional issues here.

Also applies to: 287-297, 436-437, 618-620


724-780: Based on my inspection of the code snippets:

Current call sites appear safe from lock ordering violations. All visible invocations of GetBlockHeight() and GetTipHeight() (in signing.cpp, net_instantsend.cpp, and instantsend.cpp) do not hold ::cs_main at the call site. Both functions internally acquire ::cs_main via WITH_LOCK, which is safe when called without pre-existing ::cs_main lock.

However, the review concern is fundamentally valid: Future call sites in validation or other contexts that already hold ::cs_main could introduce a deadlock. The code lacks explicit documentation of this constraint.

Recommendation: Add a clarifying comment near the function declarations to document that GetBlockHeight() and GetTipHeight() must not be called while holding ::cs_main. The existing EXCLUSIVE_LOCKS_REQUIRED(!cs_height_cache) annotation should be complemented with this guidance to prevent future misuse.

The current visible code appears correct, but runtime verification of all potential call paths (including indirect calls through other modules) would require deeper call-graph analysis beyond what can be confirmed from code inspection alone.

src/instantsend/instantsend.h (1)

135-137: Lock annotations and public cache API match implementation

Extending the thread-safety annotations to include !cs_height_cache on ResolveBlockConflicts, ProcessInstantSendLock, TransactionRemovedFromMempool, BlockConnected/BlockDisconnected, UpdatedBlockTip, and RemoveConflictingLock matches how these methods now call into the height-cache helpers.

The public helpers (CacheBlockHeight, GetBlockHeight, CacheTipHeight, GetTipHeight) are correctly marked as requiring !cs_height_cache, aligning with the internal locking in the .cpp. From an interface/TSAN perspective this all looks consistent.

Also applies to: 155-156, 159-163, 173-176, 182-185

@DashCoreAutoGuix
Copy link

Guix Automation has completed; a release should be present here: https://github.com/dashpay/dash-dev-branches/releases/tag/v23.0.0-devpr6953.617ed07e. The image should be on dockerhub soon.

@DashCoreAutoGuix
Copy link

Guix Automation has began to build this PR tagged as v23.0.0-devpr6953.e918729c. A new comment will be made when the image is pushed.

@DashCoreAutoGuix
Copy link

Guix Automation has completed; a release should be present here: https://github.com/dashpay/dash-dev-branches/releases/tag/v23.0.0-devpr6953.e918729c. The image should be on dockerhub soon.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants