Skip to content

Conversation

@knst
Copy link
Collaborator

@knst knst commented Nov 19, 2025

Issue being fixed or feature implemented

Separation of consensus & chain code and network & node in Dash Core is blocked by tight connection of network and consensus code; in components such as llmq::CSigningManager, llmq::CSigSharesManager, coinjoin::client, coinjoin::server, governance/, llmq::CInstantSendManager, etc.
It blocks backport's of bitcoin related to 'kernel' project.

What was done?

This PR addresses a dependency of llmq::CSigningManager on PeerManager (llmq::CSigSharesManager is out of scope; will be done in the PR 2b/N).

It is a split from proof-of-concept PR #6934

Prior work: #6959

How Has This Been Tested?

Run unit & functional tests.
Run a linter test/lint/lint-circular-dependencies.py

Overall conception is tested in #6934 as potentially successful to finish separation of network & consensus code.

Breaking Changes

N/A

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
  • I have assigned this pull request to a milestone

@knst knst added this to the 23.1 milestone Nov 19, 2025
@github-actions
Copy link

github-actions bot commented Nov 19, 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.

@coderabbitai
Copy link

coderabbitai bot commented Nov 19, 2025

Walkthrough

This PR adds a NetSigning NetHandler (header + implementation) and wires it into PeerManager; moves recovered-signature network handling and a worker thread into NetSigning. CSigningManager constructor no longer requires chainstate and removes worker-thread lifecycle methods while gaining VerifyAndProcessRecoveredSig, FetchPendingReconstructed, GetListeners, and modified CollectPendingRecoveredSigsToVerify. CSigSharesManager::TryRecoverSig now returns std::shared_ptr. PeerManagerInternal gains PeerPostProcessMessage. Build/test/lint entries updated to include the new files and adjusted dependencies.

Sequence Diagram(s)

sequenceDiagram
    participant PM as PeerManager
    participant NS as NetSigning
    participant SM as CSigningManager
    participant QM as CQuorumManager

    Note over PM,NS: Incoming network recovered-sig (QSIGREC)
    PM->>NS: ProcessMessage(QSIGREC, vRecv)
    NS->>NS: Deserialize CRecoveredSig\nErase pending requests
    NS->>SM: VerifyAndProcessRecoveredSig(from, recoveredSig)
    SM->>QM: Lookup quorum / pubkey
    alt quorum found & valid
        SM->>SM: ProcessRecoveredSig(recoveredSig)
        SM->>NS: Notify listeners / emit signals
        NS->>PM: Post processed result via PeerPostProcessMessage
    else missing/invalid quorum
        SM-->>NS: return / ignore
    end

    Note over NS: Background worker thread
    NS->>NS: WorkThreadMain()
    loop periodic
        NS->>SM: FetchPendingReconstructed()
        NS->>SM: CollectPendingRecoveredSigsToVerify(...)
        SM-->>NS: pubkeys + sig shares
        NS->>NS: Batch verify sig shares, handle misbehavior
        NS->>SM: ProcessRecoveredSig(validRecoveredSig)
    end
Loading
sequenceDiagram
    participant SS as SigSharesManager
    participant SM as CSigningManager
    participant NS as NetSigning

    Note over SS,SM: Local signature recovery attempt
    SS->>SS: TryRecoverSig(...)
    alt recovery succeeded
        SS-->>SM: returns shared_ptr<CRecoveredSig>
        SM->>SM: ProcessRecoveredSig(shared_ptr)
        SM->>NS: Notify listeners / emit signals
    else recovery failed
        SS-->>SS: returns nullptr
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

  • Inspect NetSigning thread lifecycle, synchronization, and interruption/shutdown behavior.
  • Verify interactions between NetSigning and CSigningManager (VerifyAndProcessRecoveredSig, FetchPendingReconstructed, CollectPendingRecoveredSigsToVerify).
  • Ensure TryRecoverSig call sites handle the new std::shared_ptr return (nullptr checks).
  • Review changes to CSigningManager constructor usages across the codebase.
  • Validate PeerPostProcessMessage integration and PeerManagerInternal implementations.
  • Confirm Makefile.am and test/lint updates correctly expose and reference the new files.

Suggested reviewers

  • UdjinM6
  • PastaPastaPasta
  • kwvg

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 9.76% 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 refactoring effort to separate network and consensus logic, specifically for the signing component.
Description check ✅ Passed The description is comprehensive and directly related to the changeset, explaining the issue, solution, and testing performed.
✨ 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.

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

🧹 Nitpick comments (5)
src/net_processing.cpp (2)

660-661: Document semantics of PeerPostProcessMessage for external callers

The new API is straightforward, but its contract isn’t obvious: calling it currently uses a sentinel node id (-1 in the implementation), which means:

  • any m_error will not actually punish a specific peer, and
  • any m_to_erase will not clear per‑peer object download state.

It would help future users to add a brief comment here (and/or in the class header) stating that this entry point is intended only for generic post‑processing (relays, DSQs, etc.) where a concrete NodeId is not available, and that peer‑scoped effects are effectively ignored.


6556-6559: Avoid magic -1 node id literal in PeerPostProcessMessage

Using -1 directly as the “no peer” NodeId works (callers like Misbehaving/EraseObjectRequest safely no‑op), but it’s a magic value and already appears elsewhere in this file for the same purpose.

Consider introducing a named sentinel (e.g. constexpr NodeId NO_PEER_ID{-1};) or reusing an existing one if present, and calling:

-    PostProcessMessage(std::move(ret), -1);
+    PostProcessMessage(std::move(ret), NO_PEER_ID);

This keeps the intent clear and avoids repeating the raw literal.

src/llmq/net_signing.h (1)

15-45: Make NetSigning header self-contained (forward declare CRecoveredSig, include <memory>)

The NetSigning interface looks aligned with the new design (a dedicated net handler around CSigningManager, with its own worker thread and interrupt), but the header currently depends on transitive declarations:

  • ProcessRecoveredSig uses std::shared_ptr<const llmq::CRecoveredSig> without a visible declaration of llmq::CRecoveredSig.
  • <memory> is not included even though std::shared_ptr appears in the public API.

To avoid brittle include-order dependencies and keep the header self-contained, consider:

 #include <net_processing.h>
 
 #include <util/threadinterrupt.h>
 #include <util/time.h>
 
+#include <memory>
 #include <thread>
 
-namespace llmq {
-class CSigningManager;
-} // namespace llmq
+namespace llmq {
+class CSigningManager;
+class CRecoveredSig;
+} // namespace llmq

This should not change behavior but will make future usage of llmq/net_signing.h safer regardless of inclusion order.

src/llmq/signing_shares.cpp (2)

783-795: Code duplication acknowledged in TODO.

The recovered signature processing logic (lines 785-793) duplicates code from NetSigning::ProcessRecoveredSig (net_signing.cpp lines 62-67). The TODO comment on line 786 already acknowledges this.

Consider extracting this common logic to a shared helper method in a follow-up to reduce duplication.


798-879: Verify single-member recovery logic flow.

The single-member quorum recovery path has a two-stage check:

  1. Lines 817-832: Prepares singleMemberRecoveredSig if is_single_member()
  2. Lines 846-848: Returns singleMemberRecoveredSig if is_single_member()

While logically correct (both checks should match), this pattern is fragile because singleMemberRecoveredSig is only initialized inside the first if block. If the checks ever diverge due to future changes, line 847 could return an uninitialized shared_ptr.

Consider restructuring for clarity, such as an early return after line 831 for single-member quorums.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 86e84d7 and ebf650e.

📒 Files selected for processing (15)
  • src/Makefile.am (2 hunks)
  • src/evo/mnhftx.cpp (0 hunks)
  • src/init.cpp (2 hunks)
  • src/llmq/context.cpp (1 hunks)
  • src/llmq/net_signing.cpp (1 hunks)
  • src/llmq/net_signing.h (1 hunks)
  • src/llmq/signing.cpp (9 hunks)
  • src/llmq/signing.h (7 hunks)
  • src/llmq/signing_shares.cpp (4 hunks)
  • src/llmq/signing_shares.h (2 hunks)
  • src/net_processing.cpp (2 hunks)
  • src/net_processing.h (1 hunks)
  • src/test/evo_islock_tests.cpp (0 hunks)
  • src/test/fuzz/process_message.cpp (0 hunks)
  • test/lint/lint-circular-dependencies.py (2 hunks)
💤 Files with no reviewable changes (3)
  • src/test/fuzz/process_message.cpp
  • src/evo/mnhftx.cpp
  • src/test/evo_islock_tests.cpp
🧰 Additional context used
🧠 Learnings (6)
📓 Common learnings
Learnt from: kwvg
Repo: dashpay/dash PR: 6761
File: src/chainlock/signing.cpp:247-250
Timestamp: 2025-07-29T14:32:48.369Z
Learning: In PR #6761, kwvg acknowledged a null pointer check issue in ChainLockSigner::Cleanup() method but deferred it to follow-up, consistent with the pattern of avoiding scope creep in refactoring PRs.
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: UdjinM6
Repo: dashpay/dash PR: 6933
File: src/llmq/utils.cpp:284-298
Timestamp: 2025-11-04T18:24:27.241Z
Learning: In consensus-critical code (such as quorum formation, block validation, or deployment activation logic), do not suggest changes to the logic itself even if the implementation appears theoretically incorrect or off-by-one. Consensus rules, once deployed on the Dash network, must be preserved exactly to avoid network forks. Refactoring PRs should maintain perfect behavioral equivalence. Only suggest logic changes if explicitly accompanied by a DIP (Dash Improvement Proposal) or if the maintainer indicates the consensus rule needs to be changed with appropriate activation logic.
Learnt from: kwvg
Repo: dashpay/dash PR: 6718
File: test/functional/test_framework/test_framework.py:2102-2102
Timestamp: 2025-06-09T16:43:20.996Z
Learning: In the test framework consolidation PR (#6718), user kwvg prefers to limit functional changes to those directly related to MasternodeInfo, avoiding scope creep even for minor improvements like error handling consistency.
📚 Learning: 2024-12-29T17:43:41.755Z
Learnt from: kwvg
Repo: dashpay/dash PR: 6504
File: src/llmq/quorums.cpp:224-224
Timestamp: 2024-12-29T17:43:41.755Z
Learning: The `CQuorumManager` is fully initialized by `LLMQContext`, addressing any concerns about the manager’s initialization sequence.

Applied to files:

  • src/llmq/context.cpp
📚 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/llmq/context.cpp
📚 Learning: 2025-10-02T18:29:54.756Z
Learnt from: kwvg
Repo: dashpay/dash PR: 6840
File: src/net_processing.cpp:2882-2886
Timestamp: 2025-10-02T18:29:54.756Z
Learning: Across net_processing.cpp, once LLMQContext (m_llmq_ctx) is asserted non-null, its subcomponents (e.g., isman, qdkgsman, quorum_block_processor) are treated as initialized and used without extra null checks.

Applied to files:

  • src/llmq/context.cpp
  • src/init.cpp
  • src/llmq/net_signing.h
  • src/llmq/net_signing.cpp
  • src/net_processing.cpp
  • src/Makefile.am
  • src/llmq/signing.cpp
  • src/llmq/signing.h
📚 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/llmq/context.cpp
  • src/llmq/signing.h
📚 Learning: 2025-07-29T14:32:48.369Z
Learnt from: kwvg
Repo: dashpay/dash PR: 6761
File: src/chainlock/signing.cpp:247-250
Timestamp: 2025-07-29T14:32:48.369Z
Learning: In PR #6761, kwvg acknowledged a null pointer check issue in ChainLockSigner::Cleanup() method but deferred it to follow-up, consistent with the pattern of avoiding scope creep in refactoring PRs.

Applied to files:

  • src/llmq/signing.cpp
🧬 Code graph analysis (7)
src/net_processing.h (1)
src/net_processing.cpp (4)
  • PeerPostProcessMessage (6556-6559)
  • PeerPostProcessMessage (6556-6556)
  • ret (660-660)
  • ret (683-683)
src/llmq/signing_shares.h (2)
test/functional/test_framework/messages.py (1)
  • CRecoveredSig (1520-1544)
src/llmq/signing_shares.cpp (2)
  • TryRecoverSig (798-879)
  • TryRecoverSig (798-799)
src/llmq/net_signing.h (4)
src/llmq/signing.cpp (4)
  • CSigningManager (327-331)
  • CSigningManager (333-333)
  • ProcessRecoveredSig (475-514)
  • ProcessRecoveredSig (475-475)
src/llmq/signing.h (2)
  • CSigningManager (158-233)
  • nodiscard (69-91)
src/net_processing.h (3)
  • NetHandler (70-85)
  • NetHandler (74-77)
  • PeerManagerInternal (58-68)
src/llmq/net_signing.cpp (12)
  • ProcessMessage (19-34)
  • ProcessMessage (19-19)
  • ProcessPendingRecoveredSigs (70-131)
  • ProcessPendingRecoveredSigs (70-70)
  • ProcessRecoveredSig (58-68)
  • ProcessRecoveredSig (58-58)
  • Start (36-44)
  • Start (36-36)
  • Stop (46-56)
  • Stop (46-46)
  • WorkThreadMain (133-148)
  • WorkThreadMain (133-133)
src/llmq/net_signing.cpp (3)
src/net_processing.cpp (15)
  • ProcessMessage (3667-5457)
  • ProcessMessage (3667-3672)
  • pfrom (618-618)
  • pfrom (639-640)
  • pfrom (751-753)
  • pfrom (762-762)
  • pfrom (770-770)
  • pfrom (773-773)
  • pfrom (775-775)
  • pfrom (777-777)
  • pfrom (873-873)
  • pfrom (1055-1055)
  • WITH_LOCK (331-334)
  • WITH_LOCK (3232-3232)
  • WITH_LOCK (3254-3254)
src/llmq/net_signing.h (1)
  • Start (33-35)
src/llmq/signing.cpp (2)
  • ProcessRecoveredSig (475-514)
  • ProcessRecoveredSig (475-475)
src/llmq/signing_shares.cpp (2)
src/llmq/signhash.cpp (1)
  • SignHash (14-22)
src/llmq/signhash.h (1)
  • SignHash (24-46)
src/llmq/signing.cpp (2)
src/llmq/signing.h (1)
  • CSigningManager (158-233)
src/llmq/net_signing.cpp (2)
  • ProcessRecoveredSig (58-68)
  • ProcessRecoveredSig (58-58)
src/llmq/signing.h (3)
src/llmq/signhash.cpp (1)
  • SignHash (14-22)
src/llmq/signhash.h (2)
  • SignHash (24-46)
  • hash (64-73)
src/llmq/signing.cpp (18)
  • CSigningManager (327-331)
  • CSigningManager (333-333)
  • AlreadyHave (335-348)
  • AlreadyHave (335-335)
  • GetRecoveredSigForGetData (350-360)
  • GetRecoveredSigForGetData (350-350)
  • VerifyAndProcessRecoveredSig (362-394)
  • VerifyAndProcessRecoveredSig (362-362)
  • FetchPendingReconstructed (467-472)
  • FetchPendingReconstructed (467-467)
  • CollectPendingRecoveredSigsToVerify (396-465)
  • CollectPendingRecoveredSigsToVerify (396-398)
  • GetListeners (516-520)
  • GetListeners (516-516)
  • ProcessRecoveredSig (475-514)
  • ProcessRecoveredSig (475-475)
  • Cleanup (533-539)
  • Cleanup (533-533)
⏰ 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: linux64_ubsan-build / Build source
  • GitHub Check: linux64_sqlite-build / Build source
  • GitHub Check: linux64_fuzz-build / Build source
  • GitHub Check: linux64-build / Build source
  • GitHub Check: linux64_tsan-build / Build source
  • GitHub Check: win64-build / Build source
  • GitHub Check: arm-linux-build / Build source
  • GitHub Check: linux64_nowallet-build / Build source
  • GitHub Check: Lint / Run linters
🔇 Additional comments (21)
src/net_processing.h (1)

67-67: LGTM! Clean interface extension for post-processing.

The addition of PeerPostProcessMessage to the PeerManagerInternal interface is well-structured. The use of an rvalue reference for MessageProcessingResult enables efficient move semantics for post-processing results. This aligns with the broader refactoring to decouple network and consensus logic.

src/init.cpp (2)

94-94: LGTM! Header change reflects architectural separation.

The switch from llmq/signing.h to llmq/net_signing.h aligns with the PR objective to separate network-handling code from core signing logic. This supports the broader refactoring to decouple consensus/chain code from network/node code.


2203-2203: LGTM! NetSigning handler follows established patterns.

The addition of the NetSigning extra handler is consistent with the existing NetInstantSend handler pattern (line 2202). Both dereference node.llmq_ctx subcomponents (sigman and isman respectively) without explicit null checks, which is the established pattern for LLMQContext usage in this codebase. Based on learnings, this is acceptable as LLMQContext subcomponents are treated as initialized once the context is asserted non-null.

src/Makefile.am (1)

274-275: NetSigning build integration looks consistent

Adding llmq/net_signing.h to BITCOIN_CORE_H and llmq/net_signing.cpp to libbitcoin_node_a_SOURCES matches how other LLMQ components are wired into libbitcoin_node and should build cleanly across targets.

Also applies to: 539-539

src/llmq/context.cpp (1)

18-40: CSigningManager wiring matches new API; consider marking peerman unused

Constructing sigman from *qman and db_params aligns with the refactored CSigningManager interface and cleanly removes chainstate/peer dependencies from LLMQContext. With signing’s worker-thread handling moved out, keeping Interrupt()/Start()/Stop() focused on qman and clhandler looks appropriate.

The PeerManager& peerman argument in Start is currently unused, which can trigger -Wunused-parameter under stricter builds; consider (void)peerman; or [[maybe_unused]] to keep this future-proof.

src/llmq/signing_shares.h (1)

10-11: SignHash include and TryRecoverSig return type are consistent with new flow

Pulling in llmq/signhash.h is appropriate given the direct SignHash members in CSigSharesNodeState, and having TryRecoverSig return std::shared_ptr<CRecoveredSig> (possibly nullptr) cleanly exposes whether recovery succeeded to callers without altering locking annotations.

Also applies to: 480-482

test/lint/lint-circular-dependencies.py (1)

24-64: Circular-dependency expectations updated in line with new LLMQ wiring

The updated EXPECTED_CIRCULAR_DEPENDENCIES entries that now traverse llmq/signing_shares -> net_processing -> … instead of llmq/signing match the refactored dependency graph and keep the lint test aligned with the new architecture.

src/llmq/net_signing.cpp (5)

36-44: LGTM!

The thread lifecycle checks are appropriate. The assert on line 40 ensures Start() is not called multiple times, which is a programming error rather than a runtime condition.


46-56: LGTM!

The shutdown sequence check is appropriate, ensuring Interrupt() is called before Stop().


58-68: LGTM!

The recovered signature processing flow is correct. Early return when ProcessRecoveredSig returns false (indicating the signature was already processed) prevents redundant listener notifications.


70-131: LGTM!

The batched verification flow is well-structured:

  1. Fetches pending reconstructed signatures and processes them immediately
  2. Collects pending signatures to verify (up to 32 unique sessions)
  3. Performs batched verification using quorum public keys
  4. Bans peers with invalid signatures
  5. Processes valid signatures

The use of .at() on line 100 is safe because CollectPendingRecoveredSigsToVerify ensures only signatures with valid quorums are included in recSigsByNode.


133-148: LGTM!

The worker thread main loop follows the expected pattern: process pending work, perform periodic cleanup, and sleep when idle. The interrupt handling on line 144 ensures the thread can be cleanly shut down.

src/llmq/signing.cpp (6)

327-330: LGTM!

The constructor signature change successfully removes the CChainState dependency, aligning with the PR objective to separate consensus/chain code from network/node code.


362-394: LGTM!

The new entry point properly validates recovered signatures before queueing them for verification. The early returns for missing/inactive quorums and duplicate signatures are appropriate, and the cs_pending lock protects the pending map correctly.


396-465: LGTM!

The refactored method now populates a map of BLS public keys (ret_pubkeys) instead of full quorum objects. This is a cleaner separation of concerns, as the verification logic only needs the public keys, not the entire quorum state.

The structured binding on line 436 and the key-based lookup pattern are appropriate for the new data flow.


467-472: LGTM!

The method efficiently fetches and clears pending reconstructed signatures using atomic swap under lock. Clean implementation.


475-514: LGTM!

The boolean return type appropriately indicates whether the recovered signature was newly processed (true) or already existed (false). This enables callers to skip redundant listener notifications, as seen in NetSigning::ProcessRecoveredSig.


516-520: LGTM!

The method returns a copy of the listeners vector under lock, allowing callers to iterate without holding cs_listeners. This prevents potential deadlocks.

src/llmq/signing.h (3)

37-37: LGTM!

The forward declaration of SignHash is appropriate for use in the buildSignHash() return type, avoiding unnecessary header inclusion.


55-55: LGTM!

Making the return type explicit improves API clarity and prevents potential issues with auto deduction.


179-209: LGTM!

The API changes are well-documented and consistent with the implementations:

  • Constructor removes CChainState dependency
  • New VerifyAndProcessRecoveredSig entry point for network-based processing
  • New methods (FetchPendingReconstructed, CollectPendingRecoveredSigsToVerify, GetListeners) clearly documented as used by NetSigning

The lock annotations are appropriate for all methods.

@UdjinM6
Copy link

UdjinM6 commented Dec 2, 2025

LGTM overall, a few small suggestions and should also maybe rebase to make sure all new lints are happy

@knst knst force-pushed the refactor-peermanager-handlers-signing branch from 069797f to 933602e Compare December 2, 2025 14:07
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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/llmq/signing.cpp (1)

437-463: Potential issue: Erased sigs may leave dangling entries in retSigShares.

When a quorum is not found or inactive (lines 445-456), the signature is erased from v but the outer loop continues. If all sigs for a node are erased, the node entry in retSigShares remains with an empty list. The caller (ProcessPendingRecoveredSigs in net_signing.cpp) iterates over retSigShares and calls pubkeys.at() for each sig, which could throw if the corresponding pubkey was never added.

This is related to the concern raised in net_signing.cpp. Consider either:

  1. Cleaning up empty entries in retSigShares, or
  2. Using pubkeys.find() in the caller as suggested there.
     for (auto& [nodeId, v] : retSigShares) {
         for (auto it = v.begin(); it != v.end();) {
             // ... existing logic ...
         }
+        // Clean up empty entries
+        if (v.empty()) {
+            // Note: Can't erase during range-for; consider restructuring
+        }
     }
♻️ Duplicate comments (2)
src/llmq/net_signing.cpp (2)

29-32: LGTM - Previous issue addressed.

The early return after PeerMisbehaving is now correctly implemented, preventing invalid recovered signatures from being processed.


139-139: LGTM - Reviewer suggestion addressed.

The CLEANUP_INTERVAL is now defined as constexpr as suggested in the past review.

🧹 Nitpick comments (2)
src/llmq/net_signing.cpp (1)

37-57: Consider using Assume() instead of bare assert(false) for clearer intent.

The assert(false) statements at lines 41 and 51 could be replaced with Assume(condition) macros which provide better diagnostics and follow the codebase pattern for detecting programming errors. However, this is a minor stylistic preference and the current implementation is functionally correct.

 void NetSigning::Start()
 {
     // can't start new thread if we have one running already
-    if (workThread.joinable()) {
-        assert(false);
-    }
+    Assume(!workThread.joinable());
 
     workThread = std::thread(&util::TraceThread, "recsigs", [this] { WorkThreadMain(); });
 }
 
 void NetSigning::Stop()
 {
     // make sure to call InterruptWorkerThread() first
-    if (!workInterrupt) {
-        assert(false);
-    }
+    Assume(workInterrupt);
 
     if (workThread.joinable()) {
         workThread.join();
     }
 }
src/llmq/signing.h (1)

185-187: Consider making VerifyAndProcessRecoveredSig const-correct on the shared_ptr

The rest of the class stores recovered sigs as std::shared_ptr<const CRecoveredSig>, while this new API takes std::shared_ptr<CRecoveredSig>. Since the implementation only reads from the object and then enqueues it, you could tighten the signature to be const as well:

-    void VerifyAndProcessRecoveredSig(NodeId from, std::shared_ptr<CRecoveredSig> recovered_sig)
+    void VerifyAndProcessRecoveredSig(NodeId from, std::shared_ptr<const CRecoveredSig> recovered_sig)
         EXCLUSIVE_LOCKS_REQUIRED(!cs_pending);

Callers with std::shared_ptr<CRecoveredSig> will still compile thanks to the standard shared_ptr converting constructor, but the interface will better reflect immutability.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 069797f25cac30b3bf08be03e48fdd74b01bf38e and fc1ffdec81261cb28bda2752b4a7cc99535216ba.

📒 Files selected for processing (15)
  • src/Makefile.am (2 hunks)
  • src/evo/mnhftx.cpp (0 hunks)
  • src/init.cpp (2 hunks)
  • src/llmq/context.cpp (1 hunks)
  • src/llmq/net_signing.cpp (1 hunks)
  • src/llmq/net_signing.h (1 hunks)
  • src/llmq/signing.cpp (9 hunks)
  • src/llmq/signing.h (7 hunks)
  • src/llmq/signing_shares.cpp (4 hunks)
  • src/llmq/signing_shares.h (2 hunks)
  • src/net_processing.cpp (2 hunks)
  • src/net_processing.h (1 hunks)
  • src/test/evo_islock_tests.cpp (0 hunks)
  • src/test/fuzz/process_message.cpp (0 hunks)
  • test/lint/lint-circular-dependencies.py (2 hunks)
💤 Files with no reviewable changes (3)
  • src/evo/mnhftx.cpp
  • src/test/evo_islock_tests.cpp
  • src/test/fuzz/process_message.cpp
🚧 Files skipped from review as they are similar to previous changes (2)
  • src/init.cpp
  • src/llmq/signing_shares.cpp
🧰 Additional context used
📓 Path-based instructions (5)
src/**/*.{cpp,h,hpp,cc}

📄 CodeRabbit inference engine (CLAUDE.md)

Dash Core implementation must be written in C++20, requiring at least Clang 16 or GCC 11.1

Files:

  • src/net_processing.h
  • src/llmq/net_signing.cpp
  • src/llmq/net_signing.h
  • src/llmq/signing_shares.h
  • src/net_processing.cpp
  • src/llmq/signing.cpp
  • src/llmq/context.cpp
  • src/llmq/signing.h
src/{masternode,evo,llmq,governance,coinjoin}/**/*.{cpp,h}

📄 CodeRabbit inference engine (CLAUDE.md)

Use Dash-specific database implementations: CFlatDB for persistent storage (MasternodeMetaStore, GovernanceStore, SporkStore, NetFulfilledRequestStore) and CDBWrapper extensions for Evolution/DKG/InstantSend/Quorum/RecoveredSigs data

Files:

  • src/llmq/net_signing.cpp
  • src/llmq/net_signing.h
  • src/llmq/signing_shares.h
  • src/llmq/signing.cpp
  • src/llmq/context.cpp
  • src/llmq/signing.h
src/{masternode,llmq}/**/*.{cpp,h}

📄 CodeRabbit inference engine (CLAUDE.md)

BLS integration must be used for cryptographic foundation of advanced masternode features

Files:

  • src/llmq/net_signing.cpp
  • src/llmq/net_signing.h
  • src/llmq/signing_shares.h
  • src/llmq/signing.cpp
  • src/llmq/context.cpp
  • src/llmq/signing.h
src/llmq/**/*.{cpp,h}

📄 CodeRabbit inference engine (CLAUDE.md)

src/llmq/**/*.{cpp,h}: LLMQ quorums must support multiple configurations (50/60, 400/60, 400/85) for different services (ChainLocks, InstantSend, governance voting)
InstantSend implementation must provide distributed key generation for secure transaction locking with quorum consensus
ChainLocks implementation must prevent reorganizations and provide block finality through 51% attack prevention

Files:

  • src/llmq/net_signing.cpp
  • src/llmq/net_signing.h
  • src/llmq/signing_shares.h
  • src/llmq/signing.cpp
  • src/llmq/context.cpp
  • src/llmq/signing.h
src/{masternode,llmq,evo,coinjoin,governance}/**/*.{cpp,h}

📄 CodeRabbit inference engine (CLAUDE.md)

Use unordered_lru_cache for efficient caching with LRU eviction in Dash-specific data structures

Files:

  • src/llmq/net_signing.cpp
  • src/llmq/net_signing.h
  • src/llmq/signing_shares.h
  • src/llmq/signing.cpp
  • src/llmq/context.cpp
  • src/llmq/signing.h
🧠 Learnings (16)
📓 Common learnings
Learnt from: kwvg
Repo: dashpay/dash PR: 6761
File: src/chainlock/signing.cpp:247-250
Timestamp: 2025-07-29T14:32:48.369Z
Learning: In PR #6761, kwvg acknowledged a null pointer check issue in ChainLockSigner::Cleanup() method but deferred it to follow-up, consistent with the pattern of avoiding scope creep in refactoring PRs.
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: UdjinM6
Repo: dashpay/dash PR: 6933
File: src/llmq/utils.cpp:284-298
Timestamp: 2025-11-04T18:24:27.241Z
Learning: In consensus-critical code (such as quorum formation, block validation, or deployment activation logic), do not suggest changes to the logic itself even if the implementation appears theoretically incorrect or off-by-one. Consensus rules, once deployed on the Dash network, must be preserved exactly to avoid network forks. Refactoring PRs should maintain perfect behavioral equivalence. Only suggest logic changes if explicitly accompanied by a DIP (Dash Improvement Proposal) or if the maintainer indicates the consensus rule needs to be changed with appropriate activation logic.
📚 Learning: 2025-11-24T16:41:22.457Z
Learnt from: CR
Repo: dashpay/dash PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-24T16:41:22.457Z
Learning: Applies to src/{masternode,llmq}/**/*.{cpp,h} : BLS integration must be used for cryptographic foundation of advanced masternode features

Applied to files:

  • src/Makefile.am
  • src/llmq/signing_shares.h
  • src/llmq/signing.cpp
  • src/llmq/signing.h
📚 Learning: 2025-11-24T16:41:22.457Z
Learnt from: CR
Repo: dashpay/dash PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-24T16:41:22.457Z
Learning: Applies to src/llmq/**/*.{cpp,h} : InstantSend implementation must provide distributed key generation for secure transaction locking with quorum consensus

Applied to files:

  • src/Makefile.am
  • src/llmq/net_signing.cpp
  • src/llmq/signing_shares.h
  • src/llmq/signing.cpp
  • src/llmq/context.cpp
  • src/llmq/signing.h
📚 Learning: 2025-11-24T16:41:22.457Z
Learnt from: CR
Repo: dashpay/dash PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-24T16:41:22.457Z
Learning: Applies to src/{validation,txmempool}/**/*.{cpp,h} : Block validation and mempool handling must use extensions to Bitcoin Core mechanisms for special transaction validation and enhanced transaction relay

Applied to files:

  • src/Makefile.am
  • src/llmq/signing.cpp
  • src/llmq/signing.h
📚 Learning: 2025-11-24T16:41:22.457Z
Learnt from: CR
Repo: dashpay/dash PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-24T16:41:22.457Z
Learning: Applies to src/llmq/**/*.{cpp,h} : LLMQ quorums must support multiple configurations (50/60, 400/60, 400/85) for different services (ChainLocks, InstantSend, governance voting)

Applied to files:

  • src/Makefile.am
  • src/llmq/net_signing.cpp
  • src/llmq/signing_shares.h
  • src/llmq/signing.cpp
  • src/llmq/context.cpp
  • src/llmq/signing.h
📚 Learning: 2025-11-24T16:41:22.457Z
Learnt from: CR
Repo: dashpay/dash PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-24T16:41:22.457Z
Learning: Applies to src/llmq/**/*.{cpp,h} : ChainLocks implementation must prevent reorganizations and provide block finality through 51% attack prevention

Applied to files:

  • src/Makefile.am
  • test/lint/lint-circular-dependencies.py
  • src/llmq/signing_shares.h
  • src/llmq/signing.cpp
  • src/llmq/signing.h
📚 Learning: 2025-10-02T18:29:54.756Z
Learnt from: kwvg
Repo: dashpay/dash PR: 6840
File: src/net_processing.cpp:2882-2886
Timestamp: 2025-10-02T18:29:54.756Z
Learning: Across net_processing.cpp, once LLMQContext (m_llmq_ctx) is asserted non-null, its subcomponents (e.g., isman, qdkgsman, quorum_block_processor) are treated as initialized and used without extra null checks.

Applied to files:

  • src/Makefile.am
  • src/llmq/net_signing.cpp
  • src/net_processing.cpp
  • src/llmq/signing.cpp
  • src/llmq/context.cpp
  • src/llmq/signing.h
📚 Learning: 2025-07-29T14:32:48.369Z
Learnt from: kwvg
Repo: dashpay/dash PR: 6761
File: src/chainlock/signing.cpp:247-250
Timestamp: 2025-07-29T14:32:48.369Z
Learning: In PR #6761, kwvg acknowledged a null pointer check issue in ChainLockSigner::Cleanup() method but deferred it to follow-up, consistent with the pattern of avoiding scope creep in refactoring PRs.

Applied to files:

  • test/lint/lint-circular-dependencies.py
  • src/llmq/net_signing.cpp
  • src/llmq/signing.cpp
  • src/llmq/signing.h
📚 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/llmq/signing.cpp
  • src/llmq/context.cpp
  • src/llmq/signing.h
📚 Learning: 2025-11-24T16:41:22.457Z
Learnt from: CR
Repo: dashpay/dash PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-24T16:41:22.457Z
Learning: Applies to src/coinjoin/**/*.{cpp,h} : CoinJoin implementation must use masternode-coordinated mixing sessions with uniform denomination outputs

Applied to files:

  • src/llmq/signing.cpp
📚 Learning: 2025-11-24T16:41:22.457Z
Learnt from: CR
Repo: dashpay/dash PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-24T16:41:22.457Z
Learning: Applies to src/{validation,consensus,net_processing}/**/*.{cpp,h} : ValidationInterface callbacks must be used for event-driven architecture to coordinate subsystems during block/transaction processing

Applied to files:

  • src/llmq/signing.cpp
📚 Learning: 2024-12-29T17:43:41.755Z
Learnt from: kwvg
Repo: dashpay/dash PR: 6504
File: src/llmq/quorums.cpp:224-224
Timestamp: 2024-12-29T17:43:41.755Z
Learning: The `CQuorumManager` is fully initialized by `LLMQContext`, addressing any concerns about the manager’s initialization sequence.

Applied to files:

  • src/llmq/context.cpp
📚 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/llmq/context.cpp
📚 Learning: 2025-11-24T16:41:22.457Z
Learnt from: CR
Repo: dashpay/dash PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-24T16:41:22.457Z
Learning: Applies to src/node/chainstate.{cpp,h} : Chainstate initialization must be separated into dedicated src/node/chainstate.* files

Applied to files:

  • src/llmq/signing.h
📚 Learning: 2025-11-24T16:41:22.457Z
Learnt from: CR
Repo: dashpay/dash PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-24T16:41:22.457Z
Learning: Applies to src/{masternode,evo,llmq,governance,coinjoin}/**/*.{cpp,h} : Use Dash-specific database implementations: CFlatDB for persistent storage (MasternodeMetaStore, GovernanceStore, SporkStore, NetFulfilledRequestStore) and CDBWrapper extensions for Evolution/DKG/InstantSend/Quorum/RecoveredSigs data

Applied to files:

  • src/llmq/signing.h
📚 Learning: 2025-11-24T16:41:22.457Z
Learnt from: CR
Repo: dashpay/dash PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-24T16:41:22.457Z
Learning: Applies to src/{masternode,llmq,evo,coinjoin,governance}/**/*.{cpp,h} : Use unordered_lru_cache for efficient caching with LRU eviction in Dash-specific data structures

Applied to files:

  • src/llmq/signing.h
🧬 Code graph analysis (6)
src/net_processing.h (1)
src/net_processing.cpp (4)
  • PeerPostProcessMessage (6561-6564)
  • PeerPostProcessMessage (6561-6561)
  • ret (655-655)
  • ret (678-678)
src/llmq/net_signing.cpp (2)
src/llmq/net_signing.h (1)
  • Start (33-35)
src/llmq/signing.cpp (2)
  • ProcessRecoveredSig (476-515)
  • ProcessRecoveredSig (476-476)
src/llmq/net_signing.h (2)
src/llmq/signing.h (2)
  • CSigningManager (158-233)
  • nodiscard (69-91)
src/net_processing.h (3)
  • NetHandler (70-85)
  • NetHandler (74-77)
  • PeerManagerInternal (58-68)
src/llmq/signing_shares.h (2)
test/functional/test_framework/messages.py (1)
  • CRecoveredSig (1520-1544)
src/llmq/signing_shares.cpp (2)
  • TryRecoverSig (807-888)
  • TryRecoverSig (807-808)
src/llmq/signing.cpp (2)
src/llmq/signing.h (1)
  • CSigningManager (158-233)
src/llmq/net_signing.cpp (2)
  • ProcessRecoveredSig (59-69)
  • ProcessRecoveredSig (59-59)
src/llmq/signing.h (3)
src/llmq/signhash.h (2)
  • SignHash (24-46)
  • hash (64-73)
src/llmq/signing.cpp (10)
  • CSigningManager (328-332)
  • CSigningManager (334-334)
  • AlreadyHave (336-349)
  • AlreadyHave (336-336)
  • VerifyAndProcessRecoveredSig (363-395)
  • VerifyAndProcessRecoveredSig (363-363)
  • GetListeners (517-521)
  • GetListeners (517-517)
  • ProcessRecoveredSig (476-515)
  • ProcessRecoveredSig (476-476)
src/llmq/net_signing.cpp (2)
  • ProcessRecoveredSig (59-69)
  • ProcessRecoveredSig (59-59)
🔇 Additional comments (25)
test/lint/lint-circular-dependencies.py (1)

26-63: Verify that the circular dependency lint passes and check for obsolete entries.

The updated dependency paths consistently introduce llmq/signing_shares as an intermediate component between signing logic and net_processing, which aligns with the PR's goal of separating network and consensus code. Ensure that:

  1. The lint script passes with these changes
  2. Any old dependency paths that are now replaced by the new llmq/signing_shares paths are removed from the expected list (e.g., paths previously routing through llmq/signing directly)
src/net_processing.h (1)

67-67: LGTM! Clean interface extension for post-processing.

The new pure virtual method provides a clean callback mechanism for external handlers (like NetSigning) to post-process message results. The signature correctly uses move semantics with MessageProcessingResult&&, and the implementation appropriately forwards to the existing PostProcessMessage infrastructure.

src/Makefile.am (1)

276-276: LGTM! Standard build integration for new NetSigning component.

The header and source files are correctly added to their respective lists (BITCOIN_CORE_H and libbitcoin_node_a_SOURCES), properly placed within the llmq section, and formatted consistently with existing entries.

Also applies to: 539-539

src/net_processing.cpp (1)

655-656: Clarify semantics of NodeId = -1 in PeerPostProcessMessage

Forwarding through PostProcessMessage(std::move(ret), /*NodeId=*/-1) preserves relay behavior (txs, invs, DSQ) but effectively drops m_error and m_to_erase handling, since Misbehaving() / EraseObjectRequest() no-op for non-existent peers (e.g. NodeId = -1). If all current PeerPostProcessMessage call sites are truly peer-agnostic and either never set these fields or handle misbehavior directly, this is fine and consistent with existing NodeId = -1 usage (e.g. orphan processing). Otherwise, you may eventually want a variant that also threads the originating NodeId.

src/llmq/signing_shares.h (2)

10-10: LGTM!

The signhash.h include is appropriately added since SignHash is used in the SessionInfo and Session structs within this file.


482-483: LGTM!

The return type change from void to std::shared_ptr<CRecoveredSig> aligns with the implementation in signing_shares.cpp and enables callers to receive and propagate the recovered signature to listeners and main signals.

src/llmq/context.cpp (2)

33-33: LGTM!

The simplified CSigningManager construction removing the ChainstateManager dependency aligns with the refactor that decouples consensus/chain code from network/node code.


46-58: LGTM!

The removal of worker thread lifecycle calls (InterruptWorkerThread, StartWorkerThread, StopWorkerThread) from LLMQContext is correct since this responsibility has been moved to the new NetSigning handler which manages its own worker thread lifecycle.

src/llmq/net_signing.h (1)

19-45: LGTM!

The NetSigning class is well-designed:

  • Properly extends NetHandler with appropriate lifecycle methods (Start, Stop, Interrupt)
  • Constructor correctly initializes the thread interrupt and stores the signing manager reference
  • Worker thread pattern follows existing conventions in the codebase
src/llmq/net_signing.cpp (3)

59-69: LGTM!

The ProcessRecoveredSig flow correctly:

  1. Delegates to the signing manager for persistence
  2. Notifies all registered listeners
  3. Emits main signals for broader observers

71-132: LGTM - Well-structured batch verification.

The ProcessPendingRecoveredSigs implementation correctly:

  • Fetches pending reconstructed sigs first
  • Uses insecure batch verification (appropriate since verifying against quorum public keys)
  • Properly handles bad sources by banning misbehaving peers
  • Uses a processed set to avoid duplicate processing

100-102: Verify pubkeys.at() safety - defensive check recommended for missing quorum keys

The pubkeys.at() call at line 101 will throw std::out_of_range if the key doesn't exist. While CollectPendingRecoveredSigsToVerify populates pubkeys for valid quorums, a defensive check using find() should be added to handle edge cases where a recovered signature references a quorum that may not have a corresponding public key in the map:

-            const auto& pubkey = pubkeys.at(std::make_pair(recSig->getLlmqType(), recSig->getQuorumHash()));
+            auto it = pubkeys.find(std::make_pair(recSig->getLlmqType(), recSig->getQuorumHash()));
+            if (it == pubkeys.end()) {
+                batchVerifier.badSources.emplace(nodeId);
+                break;
+            }
+            const auto& pubkey = it->second;
src/llmq/signing.cpp (5)

328-332: LGTM!

The simplified constructor removing ChainstateManager dependency aligns with the refactor goal of decoupling consensus/chain code from network/node code.


363-395: LGTM!

The VerifyAndProcessRecoveredSig method correctly:

  • Centralizes quorum lookup and active-quorum validation
  • Returns early for already-seen signatures
  • Stores pending sigs per-node for later batch verification

468-473: LGTM!

The FetchPendingReconstructed method atomically swaps out the pending reconstructed sigs under lock, which is a clean pattern for transferring ownership.


476-515: LGTM!

The ProcessRecoveredSig method now returns a boolean indicating whether the sig was newly processed, enabling callers to determine if listeners should be notified. The duplicate/conflict detection logic is preserved correctly.


517-521: LGTM!

The GetListeners accessor properly acquires the lock before returning a copy of the listeners vector, ensuring thread safety.

src/llmq/signing.h (8)

16-16: unordered_lru_cache include placement is appropriate

Including <unordered_lru_cache.h> here matches the usage in CRecoveredSigsDb and keeps the LLMQ-specific caching self-contained in this header, in line with the project guidelines for llmq DB/caching code.


37-37: Forward declaration of SignHash cleanly breaks the header dependency

Forward-declaring llmq::SignHash here is a good way to avoid pulling in signhash.h and helps with circular-dependency reduction, as long as the definition of CSigBase::buildSignHash() lives in a .cpp that includes signhash.h (which it appears to do from the provided snippet).


55-55: Explicit return type for getLlmqType() improves clarity

Switching from auto to Consensus::LLMQType for getLlmqType() makes the API clearer and avoids potential template/ABI surprises without changing behavior.


154-155: TODO on simplifying HandleNewRecoveredSig return type is fine to leave for later

The new TODO pointing to a std::variant-based result is a reasonable future refactor note; keeping it out of this structural PR avoids scope creep in consensus-adjacent code.


179-179: Constructor decoupling from CChainState aligns with PR goal

Adjusting CSigningManager’s ctor to depend only on CQuorumManager and DbWrapperParams removes the chainstate dependency and cleanly pushes node/network concerns out of this component, which is exactly what this PR is aiming for.


199-209: NetSigning-facing helpers look well-factored and respect locking patterns

The new trio of methods

  • FetchPendingReconstructed(),
  • CollectPendingRecoveredSigsToVerify(...) (now exposing CBLSPublicKey instead of CQuorumCPtr), and
  • GetListeners(),

plus the clarified comment and bool-returning ProcessRecoveredSig(...), provide a clean separation between the network handler and the signing manager while keeping all internal state guarded by cs_pending/cs_listeners. Returning copies by value avoids holding locks during downstream processing, which is a good trade-off here.


211-211: Private section for CSigSharesManager helpers keeps surface minimal

Reintroducing a private: section before GetDb() and the CSigSharesManager friendship keeps these helpers out of the public API while still exposing what the share manager needs, which is a sensible encapsulation boundary.


232-232: Public Cleanup() hook remains small and focused

Keeping void Cleanup(); as a standalone public hook fits well with the refactor that removed internal worker-thread management; it gives external orchestrators a clear, single entry point for db/cache cleanup without re-introducing node/peer dependencies.

@knst knst force-pushed the refactor-peermanager-handlers-signing branch from fc1ffde to dc00dce Compare December 2, 2025 15:35
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 (3)
src/llmq/net_signing.cpp (2)

37-57: Consider replacing assert(false) with Assume(false) or throwing an exception.

Using assert(false) for error conditions (lines 41 and 51) may be silently ignored in release builds compiled with NDEBUG. If these are invariants that must hold, consider using Assume(false) which logs in release builds, or throw an exception to ensure the error is always caught.

 void NetSigning::Start()
 {
     // can't start new thread if we have one running already
     if (workThread.joinable()) {
-        assert(false);
+        Assume(false && "NetSigning::Start called with running thread");
+        return;
     }
 
     workThread = std::thread(&util::TraceThread, "recsigs", [this] { WorkThreadMain(); });
 }
 
 void NetSigning::Stop()
 {
     // make sure to call InterruptWorkerThread() first
     if (!workInterrupt) {
-        assert(false);
+        Assume(false && "NetSigning::Stop called without Interrupt");
     }
 
     if (workThread.joinable()) {
         workThread.join();
     }
 }

114-120: Consider using C++20 contains() instead of count().

Since Dash Core requires C++20 (per coding guidelines), you can use the more expressive contains() method:

-        if (batchVerifier.badSources.count(nodeId)) {
+        if (batchVerifier.badSources.contains(nodeId)) {
src/llmq/signing.cpp (1)

437-463: Consider using C++20 contains() for consistency.

Similar to the suggestion in net_signing.cpp, line 443 could use contains():

-            if (!ret_pubkeys.count(quorumKey)) {
+            if (!ret_pubkeys.contains(quorumKey)) {
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between fc1ffdec81261cb28bda2752b4a7cc99535216ba and dc00dce6b3b6166b25f0f0d2479227db536ae753.

📒 Files selected for processing (3)
  • src/llmq/net_signing.cpp (1 hunks)
  • src/llmq/signing.cpp (9 hunks)
  • src/net_processing.cpp (2 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/net_processing.cpp
🧰 Additional context used
📓 Path-based instructions (5)
src/**/*.{cpp,h,hpp,cc}

📄 CodeRabbit inference engine (CLAUDE.md)

Dash Core implementation must be written in C++20, requiring at least Clang 16 or GCC 11.1

Files:

  • src/llmq/net_signing.cpp
  • src/llmq/signing.cpp
src/{masternode,evo,llmq,governance,coinjoin}/**/*.{cpp,h}

📄 CodeRabbit inference engine (CLAUDE.md)

Use Dash-specific database implementations: CFlatDB for persistent storage (MasternodeMetaStore, GovernanceStore, SporkStore, NetFulfilledRequestStore) and CDBWrapper extensions for Evolution/DKG/InstantSend/Quorum/RecoveredSigs data

Files:

  • src/llmq/net_signing.cpp
  • src/llmq/signing.cpp
src/{masternode,llmq}/**/*.{cpp,h}

📄 CodeRabbit inference engine (CLAUDE.md)

BLS integration must be used for cryptographic foundation of advanced masternode features

Files:

  • src/llmq/net_signing.cpp
  • src/llmq/signing.cpp
src/llmq/**/*.{cpp,h}

📄 CodeRabbit inference engine (CLAUDE.md)

src/llmq/**/*.{cpp,h}: LLMQ quorums must support multiple configurations (50/60, 400/60, 400/85) for different services (ChainLocks, InstantSend, governance voting)
InstantSend implementation must provide distributed key generation for secure transaction locking with quorum consensus
ChainLocks implementation must prevent reorganizations and provide block finality through 51% attack prevention

Files:

  • src/llmq/net_signing.cpp
  • src/llmq/signing.cpp
src/{masternode,llmq,evo,coinjoin,governance}/**/*.{cpp,h}

📄 CodeRabbit inference engine (CLAUDE.md)

Use unordered_lru_cache for efficient caching with LRU eviction in Dash-specific data structures

Files:

  • src/llmq/net_signing.cpp
  • src/llmq/signing.cpp
🧠 Learnings (11)
📓 Common learnings
Learnt from: kwvg
Repo: dashpay/dash PR: 6761
File: src/chainlock/signing.cpp:247-250
Timestamp: 2025-07-29T14:32:48.369Z
Learning: In PR #6761, kwvg acknowledged a null pointer check issue in ChainLockSigner::Cleanup() method but deferred it to follow-up, consistent with the pattern of avoiding scope creep in refactoring PRs.
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: UdjinM6
Repo: dashpay/dash PR: 6933
File: src/llmq/utils.cpp:284-298
Timestamp: 2025-11-04T18:24:27.241Z
Learning: In consensus-critical code (such as quorum formation, block validation, or deployment activation logic), do not suggest changes to the logic itself even if the implementation appears theoretically incorrect or off-by-one. Consensus rules, once deployed on the Dash network, must be preserved exactly to avoid network forks. Refactoring PRs should maintain perfect behavioral equivalence. Only suggest logic changes if explicitly accompanied by a DIP (Dash Improvement Proposal) or if the maintainer indicates the consensus rule needs to be changed with appropriate activation logic.
Learnt from: kwvg
Repo: dashpay/dash PR: 6718
File: test/functional/test_framework/test_framework.py:2102-2102
Timestamp: 2025-06-09T16:43:20.996Z
Learning: In the test framework consolidation PR (#6718), user kwvg prefers to limit functional changes to those directly related to MasternodeInfo, avoiding scope creep even for minor improvements like error handling consistency.
📚 Learning: 2025-10-02T18:29:54.756Z
Learnt from: kwvg
Repo: dashpay/dash PR: 6840
File: src/net_processing.cpp:2882-2886
Timestamp: 2025-10-02T18:29:54.756Z
Learning: Across net_processing.cpp, once LLMQContext (m_llmq_ctx) is asserted non-null, its subcomponents (e.g., isman, qdkgsman, quorum_block_processor) are treated as initialized and used without extra null checks.

Applied to files:

  • src/llmq/net_signing.cpp
  • src/llmq/signing.cpp
📚 Learning: 2025-11-24T16:41:22.457Z
Learnt from: CR
Repo: dashpay/dash PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-24T16:41:22.457Z
Learning: Applies to src/llmq/**/*.{cpp,h} : InstantSend implementation must provide distributed key generation for secure transaction locking with quorum consensus

Applied to files:

  • src/llmq/net_signing.cpp
  • src/llmq/signing.cpp
📚 Learning: 2025-07-29T14:32:48.369Z
Learnt from: kwvg
Repo: dashpay/dash PR: 6761
File: src/chainlock/signing.cpp:247-250
Timestamp: 2025-07-29T14:32:48.369Z
Learning: In PR #6761, kwvg acknowledged a null pointer check issue in ChainLockSigner::Cleanup() method but deferred it to follow-up, consistent with the pattern of avoiding scope creep in refactoring PRs.

Applied to files:

  • src/llmq/net_signing.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/llmq/signing.cpp
📚 Learning: 2025-11-24T16:41:22.457Z
Learnt from: CR
Repo: dashpay/dash PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-24T16:41:22.457Z
Learning: Applies to src/llmq/**/*.{cpp,h} : LLMQ quorums must support multiple configurations (50/60, 400/60, 400/85) for different services (ChainLocks, InstantSend, governance voting)

Applied to files:

  • src/llmq/signing.cpp
📚 Learning: 2025-11-24T16:41:22.457Z
Learnt from: CR
Repo: dashpay/dash PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-24T16:41:22.457Z
Learning: Applies to src/{masternode,llmq}/**/*.{cpp,h} : BLS integration must be used for cryptographic foundation of advanced masternode features

Applied to files:

  • src/llmq/signing.cpp
📚 Learning: 2025-11-24T16:41:22.457Z
Learnt from: CR
Repo: dashpay/dash PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-24T16:41:22.457Z
Learning: Applies to src/llmq/**/*.{cpp,h} : ChainLocks implementation must prevent reorganizations and provide block finality through 51% attack prevention

Applied to files:

  • src/llmq/signing.cpp
📚 Learning: 2025-11-24T16:41:22.457Z
Learnt from: CR
Repo: dashpay/dash PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-24T16:41:22.457Z
Learning: Applies to src/coinjoin/**/*.{cpp,h} : CoinJoin implementation must use masternode-coordinated mixing sessions with uniform denomination outputs

Applied to files:

  • src/llmq/signing.cpp
📚 Learning: 2025-11-24T16:41:22.457Z
Learnt from: CR
Repo: dashpay/dash PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-24T16:41:22.457Z
Learning: Applies to src/{validation,txmempool}/**/*.{cpp,h} : Block validation and mempool handling must use extensions to Bitcoin Core mechanisms for special transaction validation and enhanced transaction relay

Applied to files:

  • src/llmq/signing.cpp
📚 Learning: 2025-11-24T16:41:22.457Z
Learnt from: CR
Repo: dashpay/dash PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-24T16:41:22.457Z
Learning: Applies to src/{validation,consensus,net_processing}/**/*.{cpp,h} : ValidationInterface callbacks must be used for event-driven architecture to coordinate subsystems during block/transaction processing

Applied to files:

  • src/llmq/signing.cpp
🧬 Code graph analysis (2)
src/llmq/net_signing.cpp (1)
src/llmq/net_signing.h (1)
  • Start (33-35)
src/llmq/signing.cpp (2)
src/llmq/signing.h (1)
  • CSigningManager (158-233)
src/llmq/net_signing.cpp (2)
  • ProcessRecoveredSig (59-69)
  • ProcessRecoveredSig (59-59)
🔇 Additional comments (9)
src/llmq/net_signing.cpp (4)

19-35: LGTM! ProcessMessage handles QSIGREC correctly.

The message processing flow is well-structured: deserialize, clear pending request, validate LLMQ type with early return on failure, then delegate to signature manager. The early return after PeerMisbehaving (line 31) correctly prevents processing invalid signatures.


59-69: LGTM! ProcessRecoveredSig correctly notifies listeners and main signals.

The function properly chains processing through the signature manager, iterates over listener copies (thread-safe), and emits main signals for observers.


134-149: LGTM! Worker thread main loop follows standard patterns.

The cleanup interval is appropriately declared as constexpr. The loop correctly handles interruption, periodic cleanup, and idle sleeping. The TODO comment on line 144 is noted for future improvement.


100-104: Potential std::out_of_range exception if pubkey lookup fails at line 101.

pubkeys.at() will throw if the key is not found. While CollectPendingRecoveredSigsToVerify should populate the map and synchronously remove both signatures and pubkeys when a quorum is unavailable, consider adding a defensive check using find() instead of at() or verifying the key exists before access for added robustness.

src/llmq/signing.cpp (5)

328-332: LGTM! Constructor simplification aligns with separation of concerns.

The constructor now only takes CQuorumManager and database parameters, removing the previous chainstate dependency as intended by this refactoring PR.


363-395: LGTM! VerifyAndProcessRecoveredSig provides proper validation pipeline.

The function correctly validates quorum existence and activity, checks for duplicates, and queues signatures for batch verification. Proper locking is used for the pending signatures map.


468-473: LGTM! FetchPendingReconstructed uses efficient swap pattern.

The atomic swap under lock ensures thread safety while efficiently transferring ownership of pending reconstructed signatures.


476-515: LGTM! ProcessRecoveredSig correctly returns status for caller coordination.

The function properly handles:

  • Duplicate detection (line 480-482)
  • Conflict detection with appropriate logging (lines 489-509)
  • Atomic DB write followed by pending cleanup (lines 511-512)
  • Returns true only when the signature was newly written, enabling NetSigning to coordinate listener notifications.

517-521: LGTM! GetListeners returns a thread-safe copy.

Returning a copy of the listeners vector allows callers to iterate without holding the lock, preventing potential deadlocks and ensuring thread safety.

UdjinM6
UdjinM6 previously approved these changes Dec 2, 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 dc00dce6b3b6166b25f0f0d2479227db536ae753

@github-actions
Copy link

github-actions bot commented Dec 2, 2025

This pull request has conflicts, please rebase.

Co-authored-by: UdjinM6 <UdjinM6@users.noreply.github.com>
@knst knst force-pushed the refactor-peermanager-handlers-signing branch from dc00dce to 094b5a4 Compare December 3, 2025 14:30
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 (3)
src/llmq/signing_shares.h (1)

10-11: Header updates align with the new SignHash and recovery API

Importing <llmq/signhash.h> and changing TryRecoverSig to return std::shared_ptr<CRecoveredSig> (with unchanged EXCLUSIVE_LOCKS_REQUIRED(!cs) annotation) matches the implementation and lets callers drive post‑recovery handling without altering locking semantics.

If you want to emphasise that recovered sigs aren’t mutated after construction, a future follow‑up could switch the return type to std::shared_ptr<const CRecoveredSig>.

Also applies to: 481-484

src/llmq/signing_shares.cpp (1)

743-807: Recovered-signature flow from ProcessSigShare/TryRecoverSig looks correct; shared post-processing could be factored out

After adding a verified share, ProcessSigShare only attempts recovery once the per-session count reaches the quorum threshold, then calls TryRecoverSig outside cs, which in turn:

  • Re-checks HasRecoveredSigForId to avoid duplicate work,
  • Builds the relevant sign hash and collects only the shares for that session under cs,
  • Handles single‑member quorums by directly wrapping the member’s share in a CRecoveredSig, and
  • For multi‑member quorums, performs BLS Recover and occasional self‑verification outside the lock.

On success, the caller’s sigman.ProcessRecoveredSig(rs) followed by iterating sigman.GetListeners() and invoking m_peerman.PostProcessMessage(...) plus GetMainSignals().NotifyRecoveredSig(rs, …, proactive_relay) matches the NetSigning path and keeps lock hold times short. The dedicated NotifyRecoveredSig helper that wraps RelayRecoveredSig also fits this design.

To avoid divergence between CSigSharesManager and NetSigning over time, you might later extract the common “post‑ProcessRecoveredSig” block (listeners iteration, proactive_relay decision, and NotifyRecoveredSig/GetMainSignals().NotifyRecoveredSig call) into a shared helper in CSigningManager or a small utility.

Also applies to: 810-891, 988-991

src/llmq/net_signing.cpp (1)

143-143: Consider extracting the cleanup interval constant.

As suggested in a previous review, the cleanup interval could be extracted for better readability and maintainability.

Apply this diff:

+    constexpr auto CLEANUP_INTERVAL{5s};
-    constexpr auto CLEANUP_INTERVAL{5s};
-    if (cleanupThrottler.TryCleanup(CLEANUP_INTERVAL)) {
+    if (cleanupThrottler.TryCleanup(CLEANUP_INTERVAL)) {
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between dc00dce6b3b6166b25f0f0d2479227db536ae753 and 094b5a4.

📒 Files selected for processing (15)
  • src/Makefile.am (2 hunks)
  • src/evo/mnhftx.cpp (0 hunks)
  • src/init.cpp (2 hunks)
  • src/llmq/context.cpp (1 hunks)
  • src/llmq/net_signing.cpp (1 hunks)
  • src/llmq/net_signing.h (1 hunks)
  • src/llmq/signing.cpp (9 hunks)
  • src/llmq/signing.h (7 hunks)
  • src/llmq/signing_shares.cpp (4 hunks)
  • src/llmq/signing_shares.h (2 hunks)
  • src/net_processing.cpp (2 hunks)
  • src/net_processing.h (1 hunks)
  • src/test/evo_islock_tests.cpp (0 hunks)
  • src/test/fuzz/process_message.cpp (0 hunks)
  • test/lint/lint-circular-dependencies.py (2 hunks)
💤 Files with no reviewable changes (3)
  • src/test/evo_islock_tests.cpp
  • src/test/fuzz/process_message.cpp
  • src/evo/mnhftx.cpp
🚧 Files skipped from review as they are similar to previous changes (3)
  • src/net_processing.cpp
  • src/net_processing.h
  • src/llmq/net_signing.h
🧰 Additional context used
📓 Path-based instructions (5)
src/**/*.{cpp,h,hpp,cc}

📄 CodeRabbit inference engine (CLAUDE.md)

Dash Core implementation must be written in C++20, requiring at least Clang 16 or GCC 11.1

Files:

  • src/init.cpp
  • src/llmq/signing_shares.h
  • src/llmq/net_signing.cpp
  • src/llmq/context.cpp
  • src/llmq/signing_shares.cpp
  • src/llmq/signing.cpp
  • src/llmq/signing.h
src/{masternode,evo,llmq,governance,coinjoin}/**/*.{cpp,h}

📄 CodeRabbit inference engine (CLAUDE.md)

Use Dash-specific database implementations: CFlatDB for persistent storage (MasternodeMetaStore, GovernanceStore, SporkStore, NetFulfilledRequestStore) and CDBWrapper extensions for Evolution/DKG/InstantSend/Quorum/RecoveredSigs data

Files:

  • src/llmq/signing_shares.h
  • src/llmq/net_signing.cpp
  • src/llmq/context.cpp
  • src/llmq/signing_shares.cpp
  • src/llmq/signing.cpp
  • src/llmq/signing.h
src/{masternode,llmq}/**/*.{cpp,h}

📄 CodeRabbit inference engine (CLAUDE.md)

BLS integration must be used for cryptographic foundation of advanced masternode features

Files:

  • src/llmq/signing_shares.h
  • src/llmq/net_signing.cpp
  • src/llmq/context.cpp
  • src/llmq/signing_shares.cpp
  • src/llmq/signing.cpp
  • src/llmq/signing.h
src/llmq/**/*.{cpp,h}

📄 CodeRabbit inference engine (CLAUDE.md)

src/llmq/**/*.{cpp,h}: LLMQ quorums must support multiple configurations (50/60, 400/60, 400/85) for different services (ChainLocks, InstantSend, governance voting)
InstantSend implementation must provide distributed key generation for secure transaction locking with quorum consensus
ChainLocks implementation must prevent reorganizations and provide block finality through 51% attack prevention

Files:

  • src/llmq/signing_shares.h
  • src/llmq/net_signing.cpp
  • src/llmq/context.cpp
  • src/llmq/signing_shares.cpp
  • src/llmq/signing.cpp
  • src/llmq/signing.h
src/{masternode,llmq,evo,coinjoin,governance}/**/*.{cpp,h}

📄 CodeRabbit inference engine (CLAUDE.md)

Use unordered_lru_cache for efficient caching with LRU eviction in Dash-specific data structures

Files:

  • src/llmq/signing_shares.h
  • src/llmq/net_signing.cpp
  • src/llmq/context.cpp
  • src/llmq/signing_shares.cpp
  • src/llmq/signing.cpp
  • src/llmq/signing.h
🧠 Learnings (19)
📓 Common learnings
Learnt from: kwvg
Repo: dashpay/dash PR: 6761
File: src/chainlock/signing.cpp:247-250
Timestamp: 2025-07-29T14:32:48.369Z
Learning: In PR #6761, kwvg acknowledged a null pointer check issue in ChainLockSigner::Cleanup() method but deferred it to follow-up, consistent with the pattern of avoiding scope creep in refactoring PRs.
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: UdjinM6
Repo: dashpay/dash PR: 6933
File: src/llmq/utils.cpp:284-298
Timestamp: 2025-11-04T18:24:27.241Z
Learning: In consensus-critical code (such as quorum formation, block validation, or deployment activation logic), do not suggest changes to the logic itself even if the implementation appears theoretically incorrect or off-by-one. Consensus rules, once deployed on the Dash network, must be preserved exactly to avoid network forks. Refactoring PRs should maintain perfect behavioral equivalence. Only suggest logic changes if explicitly accompanied by a DIP (Dash Improvement Proposal) or if the maintainer indicates the consensus rule needs to be changed with appropriate activation logic.
Learnt from: kwvg
Repo: dashpay/dash PR: 6718
File: test/functional/test_framework/test_framework.py:2102-2102
Timestamp: 2025-06-09T16:43:20.996Z
Learning: In the test framework consolidation PR (#6718), user kwvg prefers to limit functional changes to those directly related to MasternodeInfo, avoiding scope creep even for minor improvements like error handling consistency.
📚 Learning: 2025-11-24T16:41:22.457Z
Learnt from: CR
Repo: dashpay/dash PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-24T16:41:22.457Z
Learning: Applies to src/llmq/**/*.{cpp,h} : InstantSend implementation must provide distributed key generation for secure transaction locking with quorum consensus

Applied to files:

  • src/init.cpp
  • src/llmq/signing_shares.h
  • src/llmq/net_signing.cpp
  • src/Makefile.am
  • src/llmq/context.cpp
  • src/llmq/signing.cpp
  • src/llmq/signing.h
📚 Learning: 2025-11-24T16:41:22.457Z
Learnt from: CR
Repo: dashpay/dash PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-24T16:41:22.457Z
Learning: Applies to src/{masternode,llmq}/**/*.{cpp,h} : BLS integration must be used for cryptographic foundation of advanced masternode features

Applied to files:

  • src/init.cpp
  • src/llmq/signing_shares.h
  • src/Makefile.am
  • src/llmq/signing.cpp
  • src/llmq/signing.h
📚 Learning: 2025-10-02T18:29:54.756Z
Learnt from: kwvg
Repo: dashpay/dash PR: 6840
File: src/net_processing.cpp:2882-2886
Timestamp: 2025-10-02T18:29:54.756Z
Learning: Across net_processing.cpp, once LLMQContext (m_llmq_ctx) is asserted non-null, its subcomponents (e.g., isman, qdkgsman, quorum_block_processor) are treated as initialized and used without extra null checks.

Applied to files:

  • src/init.cpp
  • src/llmq/net_signing.cpp
  • src/Makefile.am
  • src/llmq/context.cpp
  • src/llmq/signing.cpp
  • src/llmq/signing.h
📚 Learning: 2025-11-24T16:41:22.457Z
Learnt from: CR
Repo: dashpay/dash PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-24T16:41:22.457Z
Learning: Applies to src/llmq/**/*.{cpp,h} : LLMQ quorums must support multiple configurations (50/60, 400/60, 400/85) for different services (ChainLocks, InstantSend, governance voting)

Applied to files:

  • src/init.cpp
  • src/Makefile.am
  • src/llmq/context.cpp
  • src/llmq/signing.cpp
  • src/llmq/signing.h
📚 Learning: 2025-11-24T16:41:22.457Z
Learnt from: CR
Repo: dashpay/dash PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-24T16:41:22.457Z
Learning: Applies to src/node/**/*.{cpp,h} : NodeContext must be extended with Dash-specific managers for each subsystem during initialization

Applied to files:

  • src/init.cpp
📚 Learning: 2025-07-29T14:32:48.369Z
Learnt from: kwvg
Repo: dashpay/dash PR: 6761
File: src/chainlock/signing.cpp:247-250
Timestamp: 2025-07-29T14:32:48.369Z
Learning: In PR #6761, kwvg acknowledged a null pointer check issue in ChainLockSigner::Cleanup() method but deferred it to follow-up, consistent with the pattern of avoiding scope creep in refactoring PRs.

Applied to files:

  • src/llmq/net_signing.cpp
  • src/llmq/signing.h
📚 Learning: 2025-11-24T16:41:22.457Z
Learnt from: CR
Repo: dashpay/dash PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-24T16:41:22.457Z
Learning: Applies to src/{validation,txmempool}/**/*.{cpp,h} : Block validation and mempool handling must use extensions to Bitcoin Core mechanisms for special transaction validation and enhanced transaction relay

Applied to files:

  • src/Makefile.am
  • src/llmq/signing.cpp
  • src/llmq/signing.h
📚 Learning: 2025-11-24T16:41:22.457Z
Learnt from: CR
Repo: dashpay/dash PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-24T16:41:22.457Z
Learning: Applies to src/llmq/**/*.{cpp,h} : ChainLocks implementation must prevent reorganizations and provide block finality through 51% attack prevention

Applied to files:

  • src/Makefile.am
  • src/llmq/context.cpp
  • src/llmq/signing.cpp
  • src/llmq/signing.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/llmq/context.cpp
📚 Learning: 2024-12-29T17:43:41.755Z
Learnt from: kwvg
Repo: dashpay/dash PR: 6504
File: src/llmq/quorums.cpp:224-224
Timestamp: 2024-12-29T17:43:41.755Z
Learning: The `CQuorumManager` is fully initialized by `LLMQContext`, addressing any concerns about the manager’s initialization sequence.

Applied to files:

  • src/llmq/context.cpp
📚 Learning: 2025-06-09T16:43:20.996Z
Learnt from: kwvg
Repo: dashpay/dash PR: 6718
File: test/functional/test_framework/test_framework.py:2102-2102
Timestamp: 2025-06-09T16:43:20.996Z
Learning: In the test framework consolidation PR (#6718), user kwvg prefers to limit functional changes to those directly related to MasternodeInfo, avoiding scope creep even for minor improvements like error handling consistency.

Applied to files:

  • test/lint/lint-circular-dependencies.py
📚 Learning: 2025-11-24T16:41:22.457Z
Learnt from: CR
Repo: dashpay/dash PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-24T16:41:22.457Z
Learning: Applies to src/{validation,consensus,net_processing}/**/*.{cpp,h} : ValidationInterface callbacks must be used for event-driven architecture to coordinate subsystems during block/transaction processing

Applied to files:

  • src/llmq/signing.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/llmq/signing.cpp
  • src/llmq/signing.h
📚 Learning: 2025-11-24T16:41:22.457Z
Learnt from: CR
Repo: dashpay/dash PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-24T16:41:22.457Z
Learning: Applies to src/coinjoin/**/*.{cpp,h} : CoinJoin implementation must use masternode-coordinated mixing sessions with uniform denomination outputs

Applied to files:

  • src/llmq/signing.cpp
📚 Learning: 2025-08-19T15:08:00.835Z
Learnt from: knst
Repo: dashpay/dash PR: 6692
File: src/llmq/commitment.cpp:54-60
Timestamp: 2025-08-19T15:08:00.835Z
Learning: In Dash Core, llmq_params.size == 1 is used only for regtest environments, not on public networks, which significantly reduces the risk profile of code paths specific to single-member quorums.

Applied to files:

  • src/llmq/signing.cpp
📚 Learning: 2025-11-24T16:41:22.457Z
Learnt from: CR
Repo: dashpay/dash PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-24T16:41:22.457Z
Learning: Applies to src/node/chainstate.{cpp,h} : Chainstate initialization must be separated into dedicated src/node/chainstate.* files

Applied to files:

  • src/llmq/signing.h
📚 Learning: 2025-11-24T16:41:22.457Z
Learnt from: CR
Repo: dashpay/dash PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-24T16:41:22.457Z
Learning: Applies to src/{masternode,evo,llmq,governance,coinjoin}/**/*.{cpp,h} : Use Dash-specific database implementations: CFlatDB for persistent storage (MasternodeMetaStore, GovernanceStore, SporkStore, NetFulfilledRequestStore) and CDBWrapper extensions for Evolution/DKG/InstantSend/Quorum/RecoveredSigs data

Applied to files:

  • src/llmq/signing.h
📚 Learning: 2025-11-24T16:41:22.457Z
Learnt from: CR
Repo: dashpay/dash PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-24T16:41:22.457Z
Learning: Applies to src/{masternode,llmq,evo,coinjoin,governance}/**/*.{cpp,h} : Use unordered_lru_cache for efficient caching with LRU eviction in Dash-specific data structures

Applied to files:

  • src/llmq/signing.h
🧬 Code graph analysis (5)
src/llmq/signing_shares.h (1)
src/llmq/signing_shares.cpp (2)
  • TryRecoverSig (810-891)
  • TryRecoverSig (810-811)
src/llmq/net_signing.cpp (2)
src/llmq/net_signing.h (1)
  • Start (33-35)
src/llmq/signing.cpp (2)
  • ProcessRecoveredSig (477-516)
  • ProcessRecoveredSig (477-477)
src/llmq/signing_shares.cpp (3)
src/validationinterface.cpp (2)
  • GetMainSignals (121-124)
  • GetMainSignals (121-121)
src/llmq/signhash.cpp (1)
  • SignHash (14-22)
src/llmq/signhash.h (1)
  • SignHash (24-46)
src/llmq/signing.cpp (2)
src/llmq/signing.h (1)
  • CSigningManager (158-233)
src/llmq/net_signing.cpp (2)
  • ProcessRecoveredSig (59-73)
  • ProcessRecoveredSig (59-59)
src/llmq/signing.h (2)
src/llmq/signhash.h (2)
  • SignHash (24-46)
  • hash (64-73)
src/llmq/signing.cpp (8)
  • CSigningManager (329-333)
  • CSigningManager (335-335)
  • GetRecoveredSigForGetData (352-362)
  • GetRecoveredSigForGetData (352-352)
  • VerifyAndProcessRecoveredSig (364-396)
  • VerifyAndProcessRecoveredSig (364-364)
  • ProcessRecoveredSig (477-516)
  • ProcessRecoveredSig (477-477)
⏰ 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_multiprocess-build / Build source
  • GitHub Check: arm-linux-build / Build source
  • GitHub Check: win64-build / Build source
  • GitHub Check: linux64_ubsan-build / Build source
  • GitHub Check: linux64_sqlite-build / Build source
  • GitHub Check: linux64_fuzz-build / Build source
  • GitHub Check: linux64-build / Build source
  • GitHub Check: mac-build / Build source
  • GitHub Check: linux64_nowallet-build / Build source
  • GitHub Check: linux64_tsan-build / Build source
🔇 Additional comments (20)
test/lint/lint-circular-dependencies.py (1)

26-28: Updated circular-dependency expectations match the new signing_shares/NetSigning topology

The new cycles routed through llmq/signing_shares and the added llmq/context / masternode/active/context nodes look internally consistent with moving network concerns out of llmq/signing; no unexpected new patterns stand out.

Also applies to: 33-35, 54-56, 58-58, 63-63

src/init.cpp (1)

96-96: NetSigning integration with PeerManager mirrors existing NetInstantSend wiring

Including <llmq/net_signing.h> and adding NetSigning as an extra handler using node.llmq_ctx->sigman cleanly plugs recovered-signature networking into the existing handler mechanism without introducing new lifetime or ownership issues in AppInitMain.

Also applies to: 2205-2207

src/Makefile.am (1)

276-276: Build rules correctly expose and compile llmq/net_signing

Adding llmq/net_signing.h to BITCOIN_CORE_H and llmq/net_signing.cpp to libbitcoin_node_a_SOURCES is consistent with other llmq components and ensures NetSigning is available wherever core headers and the node library are used.

Also applies to: 539-539

src/llmq/context.cpp (1)

18-37: CSigningManager construction decoupled from ChainstateManager without changing LLMQContext semantics

Constructing sigman with *qman and db_params only is in line with moving recovered‑sig handling toward a pure consensus component; ChainstateManager remains wired into quorum_block_processor, qman, clhandler, and isman, so LLMQContext’s external behaviour stays intact while removing the direct signing↔chainstate dependency.

src/llmq/signing_shares.cpp (1)

102-133: SignHash-based session tracking is consistent and type-safe

Using llmq::SignHash in InitSession, Session/SessionInfo, and the various QSIG* message handlers centralizes sign-hash computation and avoids ad‑hoc uint256 recomputations; conversions via .Get() and logging via ToString() are applied consistently where a raw hash or string is needed, so the change is behaviourally neutral while tightening types around session identity.

Also applies to: 155-169, 353-371, 390-408, 420-472

src/llmq/net_signing.cpp (4)

19-35: LGTM - Missing return after misbehavior now addressed.

The early return after PeerMisbehaving has been correctly added on line 31, preventing invalid signatures from being processed further.


37-57: Thread lifecycle management looks correct.

The Start() and Stop() methods properly enforce the lifecycle contract: Start() prevents multiple worker threads, and Stop() requires Interrupt() to be called first. The use of assertions is appropriate for catching programming errors during development.


59-73: ProcessRecoveredSig implementation is correct.

The method properly handles the processing flow: validate → notify listeners → emit main signals. The proactive relay logic excludes specific LLMQ types as intended.


75-136: Batch verification and misbehavior handling implemented correctly.

The method properly:

  • Fetches and processes reconstructed sigs
  • Batches signature verification with appropriate security settings
  • Handles invalid signatures by marking sources as bad
  • Bans misbehaving peers
  • Processes valid sigs with proper deduplication

The nodeId == -1 check on line 131 correctly identifies locally reconstructed signatures (as opposed to those received from peers) for proactive relay determination.

src/llmq/signing.cpp (6)

329-332: Constructor refactoring aligns with separation goals.

Removing the CChainState dependency successfully decouples the signing manager from chainstate, advancing the consensus/network separation objective.


364-396: Verification and queueing logic is sound.

The method correctly:

  • Validates quorum existence and activity
  • Prevents duplicate processing via HasRecoveredSigForHash check
  • Handles pending reconstructed signatures appropriately
  • Queues validated signatures per node for batch verification

Thread safety is maintained with proper locking on line 387.


398-467: Refactored to return public keys instead of quorum objects.

The updated signature returning ret_pubkeys (map of CBLSPublicKey) instead of quorum pointers is more efficient and reduces coupling. The validation logic correctly filters out missing or inactive quorums.


469-474: Efficient extraction of pending reconstructed signatures.

Using swap within the lock is an efficient pattern for extracting the entire map while minimizing lock contention.


477-516: Return value now indicates processing outcome.

Changing the return type to bool allows callers to determine whether the signature was newly processed (true) or already known/conflicting (false). The conflict detection and logging logic remains sound.


518-522: Thread-safe accessor for listeners.

The method correctly returns a copy of the listener vector under lock, ensuring thread safety for callers.

src/llmq/signing.h (5)

16-16: Header dependency reduction through forward declaration.

Adding the SignHash forward declaration (line 37) instead of including the full header reduces compilation dependencies, improving build times.

Also applies to: 37-37


55-55: Explicit return types improve clarity.

Making getLlmqType() return an explicit Consensus::LLMQType (line 55) improves code clarity and works well with the forward-declared SignHash return type on line 69.

Also applies to: 69-69


154-155: Listener interface preserved with future improvement noted.

The TODO comment documents a potential future simplification of the return type. The current interface remains stable for this refactoring PR.


179-209: API surface updated for NetSigning integration.

The new public methods are well-documented and properly annotated:

  • VerifyAndProcessRecoveredSig replaces the old message processing interface
  • FetchPendingReconstructed, CollectPendingRecoveredSigsToVerify, GetListeners, and ProcessRecoveredSig support the new NetSigning workflow
  • [[nodiscard]] and lock annotations improve safety and maintainability

211-216: Proper encapsulation with controlled access.

Moving GetDb() to the private section (line 213) with a friend declaration for CSigSharesManager appropriately restricts database access while maintaining necessary functionality. Cleanup() correctly remains public for lifecycle management.

Also applies to: 232-232

Copy link
Member

@PastaPastaPasta PastaPastaPasta left a comment

Choose a reason for hiding this comment

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

utACK 094b5a4

@knst knst requested a review from UdjinM6 December 3, 2025 15:59
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 094b5a4

@PastaPastaPasta PastaPastaPasta merged commit 09d533a into dashpay:develop Dec 3, 2025
57 of 65 checks passed
PastaPastaPasta added a commit that referenced this pull request Dec 16, 2025
…rnance]

a67300c fix: multiple CI failures (Konstantin Akimov)
be62366 refactor: pass connman in constructor of NetHandler's child which need it (Konstantin Akimov)
dc08ef4 refactor: break circular dependency of CMasternodeManager over net.h (Konstantin Akimov)
9ac269c refactor: move ProcessMessage from CMasternodeSync to SyncManager (Konstantin Akimov)
b9aec50 refactor: split NetGovernance to SyncManager and NetGovernance (Konstantin Akimov)
106827c fmt: clang-format (Konstantin Akimov)
fc21e51 refactor: move ProcessMessage from CGovernanceManager to NetGovernance (Konstantin Akimov)
fa0e173 refactor: add CConnman to NetHandler's ProcessMessage (Konstantin Akimov)
7f3d16d chore: apply clang-format and adjust logs (Konstantin Akimov)
461392e refactor: move RequestGovernanceObjectVotes to NetGovernance to drop dependency governance on net_processing (Konstantin Akimov)
8425ca1 fix: reduce scope of ::cs_main during PeerGetRequestedObjectCount (Konstantin Akimov)
77faacc refactor: split FetchGovernanceObjectVotes to own member of CGovernanceManager (Konstantin Akimov)
e7f9218 refactor: drop a wrapper RequestGovernanceObjectVotes for a single node (Konstantin Akimov)
eb9607e refactor: move DEFAULT_SYNC_MEMPOOL form validation.h to masternode/sync.h (Konstantin Akimov)
be0be6b chore: fix clang-format and adjust log messages (Konstantin Akimov)
e878592 refactor: drop dependency of masternode/sync on governance by moving relevant code to NetGovernance (Konstantin Akimov)
7e4849a refactor: inline method CleanOrphanObjects to CGovernanceManager::RequestOrphanObjects (Konstantin Akimov)
285def6 refactor: use NetHandler to move scheduler & partially network related code from governance (Konstantin Akimov)
78c6062 refactor: drop unused includes and forward declarations from masternode/sync and governance/governance (Konstantin Akimov)
91129d4 refactor: drop dead code with ERROR_TXINDEX_DISABLED_WHEN_GOV_ENABLED (Konstantin Akimov)

Pull request description:

  ## Issue being fixed or feature implemented
  Separation of consensus & chain code and network & node in Dash Core is blocked by tight connection of network and consensus code; in components such as `llmq::CSigningManager`, `llmq::CSigSharesManager`, `coinjoin::client`, `coinjoin::server`, `governance/`, `llmq::CInstantSendManager`, etc.
  It blocks backport's of bitcoin related to 'kernel' project.

  ## What was done?
  This PR addresses a dependency of `CGovernanceManager` on `PeerManager`.

  `CMasternodeSync` is refactored to pure state-machine without dependencies on net.h.

  It is a split from proof-of-concept PR #6934
  Prior work: #6959 #6992

  ## How Has This Been Tested?
  Run unit & functional tests.
  Run a linter `test/lint/lint-circular-dependencies.py`

  Overall conception is tested in #6934 as potentially successful to finish separation of network & consensus code.

  ## Breaking Changes
  N/A

  ## Checklist:
  - [x] I have performed a self-review of my own code
  - [x] I have commented my code, particularly in hard-to-understand areas
  - [ ] I have added or updated relevant unit/integration/functional/e2e tests
  - [x] I have made corresponding changes to the documentation
  - [x] I have assigned this pull request to a milestone

ACKs for top commit:
  UdjinM6:
    light ACK a67300c

Tree-SHA512: dc8548268cd3156848e50fd338776be676c63d966fd6b576fb1f0f3b5416afd6d9b4a4d17f6df7cd1cf3b0c4f8908b4848e196dad808f430207d75e2adf6bbe0
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