Skip to content

refactor: drop dependency of spork manager on network code#7303

Open
knst wants to merge 4 commits intodashpay:developfrom
knst:refactor-spork-net-processing-v2
Open

refactor: drop dependency of spork manager on network code#7303
knst wants to merge 4 commits intodashpay:developfrom
knst:refactor-spork-net-processing-v2

Conversation

@knst
Copy link
Copy Markdown
Collaborator

@knst knst commented May 2, 2026

Issue being fixed or feature implemented

It helps to break circular dependencies over spork <-> net.
It helps to get spork manager ready kernel project.

Apparantly, chainstate implementation uses sporks for validation of blockchain but network dependencies could be avoided there.

This changes is PR-ready changes from #7234 for spork manager.

What was done?

Dropped dependency of spork manager on network code by moving network code to net_processing.

There's no need dedicated NetHandler because imlementation is trivial, short and doesn't have any async worker or threads.

How Has This Been Tested?

Run unit & functional tests.

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

It helps to break circular dependencies over spork <-> net.
It helps to get spork manager ready kernel project.
Apparantly, chainstate implementation uses sporks for validation
of blockchain but network dependencies could be avoided there.
@github-actions
Copy link
Copy Markdown

github-actions Bot commented May 2, 2026

✅ No Merge Conflicts Detected

This PR currently has no conflicts with other open PRs.

@thepastaclaw
Copy link
Copy Markdown

thepastaclaw commented May 2, 2026

✅ Review complete (commit 6a4ee52)

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 81e15cd42f

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread src/spork.cpp Outdated
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 2, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 8b13f284-5601-40a9-9260-953a75a3887f

📥 Commits

Reviewing files that changed from the base of the PR and between 69f09d1 and 6a4ee52.

📒 Files selected for processing (1)
  • src/spork.cpp

Walkthrough

This PR moves spork handling into explicit SPORK and GETSPORKS branches in PeerManagerImpl::ProcessMessage(). SPORK messages are deserialized to CSporkMessage, the peer's object-download request cleared, and the spork validated via CSporkManager::IsSporkValid() (misbehavior recorded for invalid). Valid sporks are passed to CSporkManager::ProcessSpork(const CSporkMessage&) and, only on success, the SPORK inv is relayed. GETSPORKS requests are answered by iterating CSporkManager::ActiveSporks() and pushing each active SPORK to the peer. The generic post-process routing for sporks was removed. The lint expected circular-dependencies list dropped one entry.

Sequence Diagram(s)

sequenceDiagram
    participant Peer as Remote Peer
    participant PM as PeerManagerImpl
    participant SM as SporkManager
    participant Inv as Inventory/Relay

    rect rgba(100, 150, 200, 0.5)
    Note over Peer,Inv: SPORK Message Flow (New)
    Peer->>PM: Send SPORK
    PM->>PM: Deserialize to CSporkMessage
    PM->>SM: IsSporkValid(spork)
    SM-->>PM: bool (valid/invalid)
    alt Validation passes
        PM->>SM: ProcessSpork(spork)
        SM-->>PM: bool (processed)
        alt Process succeeds
            PM->>Inv: Relay SPORK inv
        end
    else Invalid
        PM->>Peer: Record misbehavior / drop
    end
    end

    rect rgba(150, 100, 200, 0.5)
    Note over Peer,PM: GETSPORKS Message Flow (New)
    Peer->>PM: Send GETSPORKS
    PM->>SM: ActiveSporks()
    SM-->>PM: Map of active sporks
    PM->>Peer: Push SPORK messages for each active spork
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ 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%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title 'refactor: drop dependency of spork manager on network code' directly and clearly summarizes the main change: removing network dependencies from the spork manager by moving network-related code to net_processing.
Description check ✅ Passed The description is related to the changeset, explaining the motivation (breaking circular dependencies, preparing for kernel project), what was done (moving network code to net_processing), and testing performed (unit & functional tests).
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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
Copy Markdown

@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: 2

🧹 Nitpick comments (1)
src/spork.h (1)

262-264: ⚡ Quick win

Prefer a message-oriented return type for ActiveSporks().

Returning std::unordered_map<SporkId, std::map<CKeyID, CSporkMessage>> leaks CSporkManager’s storage layout into the new public API. The network-facing caller only needs the active CSporkMessages, so a flat collection or visitor would keep this boundary aligned with the decoupling goal of the refactor.

♻️ Suggested API shape
-    std::unordered_map<SporkId, std::map<CKeyID, CSporkMessage>> ActiveSporks() const EXCLUSIVE_LOCKS_REQUIRED(!cs);
+    std::vector<CSporkMessage> ActiveSporks() const EXCLUSIVE_LOCKS_REQUIRED(!cs);
-std::unordered_map<SporkId, std::map<CKeyID, CSporkMessage>> CSporkManager::ActiveSporks() const
+std::vector<CSporkMessage> CSporkManager::ActiveSporks() const
 {
     LOCK(cs); // make sure to not lock this together with cs_main
-    return mapSporksActive;
+    std::vector<CSporkMessage> out;
+    for (const auto& [_, by_signer] : mapSporksActive) {
+        for (const auto& [_, spork] : by_signer) {
+            out.push_back(spork);
+        }
+    }
+    return out;
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/spork.h` around lines 262 - 264, ActiveSporks() currently exposes the
internal nested container type (std::unordered_map<SporkId, std::map<CKeyID,
CSporkMessage>>) — change it to return a message-oriented collection like
std::map<SporkId, CSporkMessage> or std::vector<CSporkMessage> (or implement a
visitor callback signature) so callers only receive the active CSporkMessage
objects; update the declaration of ActiveSporks in src/spork.h and its
implementation in CSporkManager to build and return the flattened collection (or
invoke the visitor) from the existing internal storage, and adjust all callers
to use the new return type (or visitor) rather than relying on CKeyID or the
nested storage layout.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/net_processing.cpp`:
- Around line 5482-5489: The GETSPORKS handler currently streams every entry
from m_sporkman.ActiveSporks() back to the requester via m_connman.PushMessage
which can produce an unbounded large response; modify the GETSPORKS branch to
enforce a strict response budget (either a max message count and/or max byte
count) before calling m_connman.PushMessage for each signerSporkPair: iterate
ActiveSporks() and accumulate a counter and estimated byte size for messages
created with CNetMsgMaker(pfrom.GetCommonVersion()).Make(NetMsgType::SPORK,
...), stop sending when the budget is reached, and if there are remaining
entries fall back to sending an inventory-style response (e.g., advertise
available spork IDs or a single compact summary) rather than streaming the rest;
reference GETSPORKS, m_sporkman.ActiveSporks(), signerSporkPair,
m_connman.PushMessage and pfrom when making the changes.

In `@src/spork.cpp`:
- Around line 142-149: ProcessSpork currently unconditionally dereferences the
optional signer (assert(spork.GetSignerKeyID().has_value()) / value()) which can
abort on malformed input; change CSporkManager::ProcessSpork to validate the
spork signer before use by calling IsSporkValid() or checking
GetSignerKeyID().has_value() and returning false (or rejecting the message) if
missing/invalid, and only then extract keyIDSigner via GetSignerKeyID().value();
update any logging to reflect rejection reason and avoid using assert() for
input validation.

---

Nitpick comments:
In `@src/spork.h`:
- Around line 262-264: ActiveSporks() currently exposes the internal nested
container type (std::unordered_map<SporkId, std::map<CKeyID, CSporkMessage>>) —
change it to return a message-oriented collection like std::map<SporkId,
CSporkMessage> or std::vector<CSporkMessage> (or implement a visitor callback
signature) so callers only receive the active CSporkMessage objects; update the
declaration of ActiveSporks in src/spork.h and its implementation in
CSporkManager to build and return the flattened collection (or invoke the
visitor) from the existing internal storage, and adjust all callers to use the
new return type (or visitor) rather than relying on CKeyID or the nested storage
layout.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 4da6e749-a439-4668-bc4d-d3f07d77b34c

📥 Commits

Reviewing files that changed from the base of the PR and between 4a0fd85 and 81e15cd.

📒 Files selected for processing (4)
  • src/net_processing.cpp
  • src/spork.cpp
  • src/spork.h
  • test/lint/lint-circular-dependencies.py
💤 Files with no reviewable changes (1)
  • test/lint/lint-circular-dependencies.py

Comment thread src/net_processing.cpp
Comment thread src/spork.cpp
@knst knst force-pushed the refactor-spork-net-processing-v2 branch from 81e15cd to 7c899a2 Compare May 2, 2026 12:17
Copy link
Copy Markdown

@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.

♻️ Duplicate comments (1)
src/spork.cpp (1)

142-149: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Keep ProcessSpork() self-validating.

This entry point still assumes prior validation and dereferences signer data via assert()/.value(). If any non-network caller skips IsSporkValid(), malformed input can still abort the node or get stored here. Please harden ProcessSpork() itself before extracting keyIDSigner.

Suggested hardening
 bool CSporkManager::ProcessSpork(const CSporkMessage& spork)
 {
+    if (!IsSporkValid(spork)) return false;
+
     uint256 hash = spork.GetHash();
     std::string strLogMsg{strprintf("SPORK -- hash: %s id: %d value: %10d", hash.ToString(), spork.nSporkID,
                                     spork.nValue)};
-    assert(spork.GetSignerKeyID().has_value());
-    auto keyIDSigner = spork.GetSignerKeyID().value();
+    const auto opt_keyIDSigner = spork.GetSignerKeyID();
+    assert(opt_keyIDSigner.has_value());
+    const CKeyID& keyIDSigner = *opt_keyIDSigner;

Based on learnings, this repository keeps assert() enabled in all build types, so this assertion is still production-impacting.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/spork.cpp` around lines 142 - 149, ProcessSpork currently unconditionally
dereferences the optional returned by CSporkMessage::GetSignerKeyID() via
assert()/.value(), which can abort the node if a caller skips IsSporkValid();
make ProcessSpork self-validating by checking the optional before use: call
spork.GetSignerKeyID() and if it has no value, log an error and return false (or
otherwise reject the spork) instead of asserting, and only assign keyIDSigner
when the optional is present; also ensure any subsequent code that uses
keyIDSigner (e.g., comparisons, map lookups, or calls that assume a valid
signer) is guarded by that same presence check so ProcessSpork never
dereferences a missing signer even if IsSporkValid() was skipped.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In `@src/spork.cpp`:
- Around line 142-149: ProcessSpork currently unconditionally dereferences the
optional returned by CSporkMessage::GetSignerKeyID() via assert()/.value(),
which can abort the node if a caller skips IsSporkValid(); make ProcessSpork
self-validating by checking the optional before use: call spork.GetSignerKeyID()
and if it has no value, log an error and return false (or otherwise reject the
spork) instead of asserting, and only assign keyIDSigner when the optional is
present; also ensure any subsequent code that uses keyIDSigner (e.g.,
comparisons, map lookups, or calls that assume a valid signer) is guarded by
that same presence check so ProcessSpork never dereferences a missing signer
even if IsSporkValid() was skipped.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 02c45efc-2758-49c4-b9c5-1811d627cf4f

📥 Commits

Reviewing files that changed from the base of the PR and between 81e15cd and 7c899a2.

📒 Files selected for processing (4)
  • src/net_processing.cpp
  • src/spork.cpp
  • src/spork.h
  • test/lint/lint-circular-dependencies.py
💤 Files with no reviewable changes (1)
  • test/lint/lint-circular-dependencies.py
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/spork.h

@thepastaclaw
Copy link
Copy Markdown

Followed up on this CodeRabbit finding by opening a small PR against the source branch:

knst#2

It makes CSporkManager::ProcessSpork() self-validate before mutating spork state and removes the assert() / .value() signer path.

Validation:

  • git diff --check
  • Confirmed there are no remaining GetSignerKeyID().value() / direct assert patterns in the touched spork path
  • Could not run src/test/test_dash --run_test=spork_tests in the follow-up worktree because no test binary is present

@knst knst requested review from PastaPastaPasta and UdjinM6 May 2, 2026 13:43
@thepastaclaw
Copy link
Copy Markdown

Prepared the requested follow-up branch to disable legacy P2P spork handling on mainnet conservatively (no punishment; empty GETSPORKS response; no mainnet MSG_SPORK request/response/relay paths): https://github.com/thepastaclaw/dash/tree/tracker-1145-disable-mainnet-p2p-sporks\n\nCommit: thepastaclaw@3633209d38eaacf0ca42e72ffde5dc201e050ad8\n\nVerification run: git diff --check. I did not create a PR from here.

@thepastaclaw
Copy link
Copy Markdown

Opened a draft follow-up PR for the mainnet spork P2P review feedback: knst#3

Copy link
Copy Markdown

@thepastaclaw thepastaclaw left a comment

Choose a reason for hiding this comment

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

Code Review

Refactor cleanly extracts spork P2P handling from CSporkManager into net_processing. One real behavioral regression: already-seen sporks are now relayed where previously they were not. Codex's quorum getdata finding is invalid — this PR does not touch any quorum/RPC code.

Reviewed commit: 81e15cd

🟡 1 suggestion(s) | 💬 1 nitpick(s)

🤖 Prompt for all review comments with AI agents
These findings are from an automated code review. Verify each finding against the current code and only fix it if needed.

In `src/spork.cpp`:
- [SUGGESTION] lines 154-156: Already-seen sporks are now relayed; previously they were not
  Before this refactor, the old `CSporkManager::ProcessSpork` only populated `ret.m_inventory` after the `mapSporksByHash[hash] = spork` write, so the 'seen' branch (`mapSporksActive[id][signer].nTimeSigned >= spork.nTimeSigned`) returned with empty inventory and the caller did not relay anything. The new `ProcessSpork` returns `true` on that same seen path (src/spork.cpp:156), and the new dispatch in `PeerManagerImpl::ProcessMessage` calls `RelayInv(spork_inv)` whenever `ProcessSpork` returns true (src/net_processing.cpp:5476-5478). Net effect: every duplicate spork received from a peer is now re-announced via INV, which previously was treated as a no-op. Inventory tracking on the receiving side limits the practical fallout, but this is an unintended propagation change. Either return `false` for the seen branch or split the API into 'stored vs. should-relay' so the prior semantics are preserved.

Comment thread src/spork.cpp Outdated
Comment thread src/spork.cpp
Comment on lines +142 to +148
bool CSporkManager::ProcessSpork(const CSporkMessage& spork)
{
uint256 hash = spork.GetHash();
std::string strLogMsg{strprintf("SPORK -- hash: %s id: %d value: %10d", hash.ToString(), spork.nSporkID,
spork.nValue)};
assert(spork.GetSignerKeyID().has_value());
auto keyIDSigner = spork.GetSignerKeyID().value();
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💬 Nitpick: Undocumented precondition that IsSporkValid was called first

ProcessSpork asserts spork.GetSignerKeyID().has_value() and immediately dereferences the optional. The only thing guaranteeing the optional is engaged is that callers must run IsSporkValid first (it rejects empty signers). This precondition is not stated in the spork.h doc-comment for ProcessSpork, which makes the API easy to misuse from future call sites (e.g. RPC paths or tests). Either document the requirement on the header declaration or fold the validity check into ProcessSpork itself.

source: ['claude']

Copy link
Copy Markdown

@thepastaclaw thepastaclaw left a comment

Choose a reason for hiding this comment

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

Code Review

Re-reviewed commit 7c899a2. The duplicate-spork relay regression from the prior review is fixed: the already-seen path now returns false again, so duplicate sporks are no longer re-announced. I did not find any new blocking issues; the remaining ProcessSpork/IsSporkValid API precondition is the same minor thread already left on the previous review, so I am not restating it as a duplicate inline comment here.

Reviewed commit: 7c899a2

Copy link
Copy Markdown

@thepastaclaw thepastaclaw left a comment

Choose a reason for hiding this comment

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

No blockers from me on the current head; remaining comments are advisory suggestions/nits.

@UdjinM6
Copy link
Copy Markdown

UdjinM6 commented May 3, 2026

pls see be5c18a

- Use __func__ in IsSporkValid log prefixes (was stale "ProcessSpork")
- Mark IsSporkValid as const
- Pass reason string to Misbehaving on invalid spork

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Copy link
Copy Markdown

@thepastaclaw thepastaclaw left a comment

Choose a reason for hiding this comment

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

Reviewed the current head (7460211; same patch as be5c18a). The new changes look good to me: the stale log prefix is fixed via func, IsSporkValid() can safely be const with the existing mutable lock, and passing a concrete Misbehaving reason preserves the existing rejection path. No new blockers from me.

Copy link
Copy Markdown

@thepastaclaw thepastaclaw left a comment

Choose a reason for hiding this comment

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

Code Review

Refactor cleanly extracts SPORK/GETSPORKS dispatch into PeerManagerImpl, removing spork->net coupling. Behavior is preserved across the validation/erase/relay paths and duplicate relay is correctly suppressed. One suggestion remains around the new split API: ProcessSpork() now relies on a caller-side precondition (IsSporkValid first) that the public interface and header comment do not communicate.

Reviewed commit: 7460211

🟡 1 suggestion(s)

🤖 Prompt for all review comments with AI agents
These findings are from an automated code review. Verify each finding against the current code and only fix it if needed.

In `src/spork.h`:
- [SUGGESTION] lines 252-259: ProcessSpork() has an undocumented validity precondition after the split
  The refactor split spork handling into IsSporkValid() and ProcessSpork(), but ProcessSpork() now asserts spork.GetSignerKeyID().has_value() at src/spork.cpp:147 and unconditionally dereferences the optional on the next line. That precondition is only satisfied if IsSporkValid() ran first and returned true. The single current caller in net_processing.cpp does this correctly, but two things make the new API a footgun for future use: (1) the Doxygen comment at src/spork.h:255 still claims "it validates the spork and adds it to the internal spork storage", which is no longer true; (2) since the stated goal of the refactor is to make spork management reusable outside the networking layer, a future caller is likely to copy this pattern incorrectly and either crash in debug builds or throw std::bad_optional_access in release. Either fold the validity check back into ProcessSpork(), update the comment to state the precondition explicitly, or change the signature to take a validated wrapper type so the unsafe call is unrepresentable.

Comment thread src/spork.h
Comment on lines 252 to 259
/**
* ProcessSpork is used to handle the 'spork' p2p message.
*
* For 'spork', it validates the spork and adds it to the internal spork storage and
* performs any necessary processing.
*/
[[nodiscard]] MessageProcessingResult ProcessSpork(NodeId from, CDataStream& vRecv)
[[nodiscard]] bool ProcessSpork(const CSporkMessage& spork)
EXCLUSIVE_LOCKS_REQUIRED(!cs, !cs_cache);
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🟡 Suggestion: ProcessSpork() has an undocumented validity precondition after the split

The refactor split spork handling into IsSporkValid() and ProcessSpork(), but ProcessSpork() now asserts spork.GetSignerKeyID().has_value() at src/spork.cpp:147 and unconditionally dereferences the optional on the next line. That precondition is only satisfied if IsSporkValid() ran first and returned true. The single current caller in net_processing.cpp does this correctly, but two things make the new API a footgun for future use: (1) the Doxygen comment at src/spork.h:255 still claims "it validates the spork and adds it to the internal spork storage", which is no longer true; (2) since the stated goal of the refactor is to make spork management reusable outside the networking layer, a future caller is likely to copy this pattern incorrectly and either crash in debug builds or throw std::bad_optional_access in release. Either fold the validity check back into ProcessSpork(), update the comment to state the precondition explicitly, or change the signature to take a validated wrapper type so the unsafe call is unrepresentable.

source: ['codex']

🤖 Fix this with AI agents
These findings are from an automated code review. Verify each finding against the current code and only fix it if needed.

In `src/spork.h`:
- [SUGGESTION] lines 252-259: ProcessSpork() has an undocumented validity precondition after the split
  The refactor split spork handling into IsSporkValid() and ProcessSpork(), but ProcessSpork() now asserts spork.GetSignerKeyID().has_value() at src/spork.cpp:147 and unconditionally dereferences the optional on the next line. That precondition is only satisfied if IsSporkValid() ran first and returned true. The single current caller in net_processing.cpp does this correctly, but two things make the new API a footgun for future use: (1) the Doxygen comment at src/spork.h:255 still claims "it validates the spork and adds it to the internal spork storage", which is no longer true; (2) since the stated goal of the refactor is to make spork management reusable outside the networking layer, a future caller is likely to copy this pattern incorrectly and either crash in debug builds or throw std::bad_optional_access in release. Either fold the validity check back into ProcessSpork(), update the comment to state the precondition explicitly, or change the signature to take a validated wrapper type so the unsafe call is unrepresentable.

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 69f09d1a20

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread src/spork.cpp Outdated
Copy link
Copy Markdown

@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

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/spork.cpp`:
- Around line 148-150: The early-return has the wrong polarity and calls value()
on an empty optional; change the guard in spork.cpp so it returns false when
GetSignerKeyID() is not present (if (!spork.GetSignerKeyID().has_value()) return
false;) and then safely obtain the signer ID (auto keyIDSigner =
spork.GetSignerKeyID().value();) — this restores the original defensive check
around spork.GetSignerKeyID() and prevents std::bad_optional_access.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: fe3db1d8-bfe3-4237-af95-f26408e84f2d

📥 Commits

Reviewing files that changed from the base of the PR and between 7460211 and 69f09d1.

📒 Files selected for processing (1)
  • src/spork.cpp

Comment thread src/spork.cpp Outdated
@thepastaclaw
Copy link
Copy Markdown

Confirmed the new Codex/CodeRabbit signer-guard report: current head 69f09d1a has the polarity inverted, so valid signer keys return false and an empty optional would fall through to .value().

I pushed a minimal one-line fix branch here (no PR opened from this worker): https://github.com/thepastaclaw/dash/tree/tracker-1145-fix-spork-signer-guard

Commit: thepastaclaw@182a8d6

Validation: git diff --check.

@thepastaclaw
Copy link
Copy Markdown

Opened a draft fix PR for the signer guard issue here: knst#4

@knst knst force-pushed the refactor-spork-net-processing-v2 branch from 69f09d1 to 6a4ee52 Compare May 5, 2026 13:30
Copy link
Copy Markdown

@thepastaclaw thepastaclaw left a comment

Choose a reason for hiding this comment

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

Code Review

Refactor cleanly extracts spork P2P handling out of CSporkManager into PeerManagerImpl. Two minor issues remain: the ProcessSpork() Doxygen comment is stale (it no longer validates) and ProcessSpork() recomputes GetSignerKeyID() unnecessarily. No blocking issues.

Reviewed commit: 6a4ee52

🟡 1 suggestion(s) | 💬 1 nitpick(s)

🤖 Prompt for all review comments with AI agents
These findings are from an automated code review. Verify each finding against the current code and only fix it if needed.

In `src/spork.h`:
- [SUGGESTION] lines 252-259: ProcessSpork() doc comment is stale and hides an unsafe precondition
  The Doxygen comment claims ProcessSpork() "validates the spork and adds it to the internal spork storage," but after the refactor validation (timestamp window + signer-pubkey membership in setSporkPubKeyIDs) lives entirely in IsSporkValid(). ProcessSpork() now only checks `GetSignerKeyID().has_value()` (src/spork.cpp:148) before storing. The single current caller in net_processing.cpp:5472 invokes IsSporkValid() first, but since the stated purpose of this PR is to make CSporkManager reusable outside the networking layer, leaving this implicit precondition undocumented turns the new public API into a footgun: a future caller following the doc could store a spork with a future nTimeSigned or one signed by a non-spork key. Either fold the IsSporkValid() check back into ProcessSpork(), or update the comment to state explicitly that callers must call IsSporkValid() first.

Comment thread src/spork.h
Comment on lines 252 to 259
/**
* ProcessSpork is used to handle the 'spork' p2p message.
*
* For 'spork', it validates the spork and adds it to the internal spork storage and
* performs any necessary processing.
*/
[[nodiscard]] MessageProcessingResult ProcessSpork(NodeId from, CDataStream& vRecv)
[[nodiscard]] bool ProcessSpork(const CSporkMessage& spork)
EXCLUSIVE_LOCKS_REQUIRED(!cs, !cs_cache);
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🟡 Suggestion: ProcessSpork() doc comment is stale and hides an unsafe precondition

The Doxygen comment claims ProcessSpork() "validates the spork and adds it to the internal spork storage," but after the refactor validation (timestamp window + signer-pubkey membership in setSporkPubKeyIDs) lives entirely in IsSporkValid(). ProcessSpork() now only checks GetSignerKeyID().has_value() (src/spork.cpp:148) before storing. The single current caller in net_processing.cpp:5472 invokes IsSporkValid() first, but since the stated purpose of this PR is to make CSporkManager reusable outside the networking layer, leaving this implicit precondition undocumented turns the new public API into a footgun: a future caller following the doc could store a spork with a future nTimeSigned or one signed by a non-spork key. Either fold the IsSporkValid() check back into ProcessSpork(), or update the comment to state explicitly that callers must call IsSporkValid() first.

💡 Suggested change
Suggested change
/**
* ProcessSpork is used to handle the 'spork' p2p message.
*
* For 'spork', it validates the spork and adds it to the internal spork storage and
* performs any necessary processing.
*/
[[nodiscard]] MessageProcessingResult ProcessSpork(NodeId from, CDataStream& vRecv)
[[nodiscard]] bool ProcessSpork(const CSporkMessage& spork)
EXCLUSIVE_LOCKS_REQUIRED(!cs, !cs_cache);
/**
* ProcessSpork stores a spork that has already been validated by IsSporkValid().
*
* Callers MUST invoke IsSporkValid(spork) first; ProcessSpork() trusts its
* input apart from a defensive check that the signer key can be recovered.
* Returns false for duplicate/older sporks and for messages whose signer
* cannot be recovered.
*/
[[nodiscard]] bool ProcessSpork(const CSporkMessage& spork)
EXCLUSIVE_LOCKS_REQUIRED(!cs, !cs_cache);

source: ['claude', 'codex']

🤖 Fix this with AI agents
These findings are from an automated code review. Verify each finding against the current code and only fix it if needed.

In `src/spork.h`:
- [SUGGESTION] lines 252-259: ProcessSpork() doc comment is stale and hides an unsafe precondition
  The Doxygen comment claims ProcessSpork() "validates the spork and adds it to the internal spork storage," but after the refactor validation (timestamp window + signer-pubkey membership in setSporkPubKeyIDs) lives entirely in IsSporkValid(). ProcessSpork() now only checks `GetSignerKeyID().has_value()` (src/spork.cpp:148) before storing. The single current caller in net_processing.cpp:5472 invokes IsSporkValid() first, but since the stated purpose of this PR is to make CSporkManager reusable outside the networking layer, leaving this implicit precondition undocumented turns the new public API into a footgun: a future caller following the doc could store a spork with a future nTimeSigned or one signed by a non-spork key. Either fold the IsSporkValid() check back into ProcessSpork(), or update the comment to state explicitly that callers must call IsSporkValid() first.

Comment thread src/spork.cpp
Comment on lines +148 to +150
if (!spork.GetSignerKeyID().has_value()) return false;

auto keyIDSigner = spork.GetSignerKeyID().value();
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💬 Nitpick: ProcessSpork() invokes GetSignerKeyID() twice

CSporkMessage::GetSignerKeyID() performs a CPubKey::RecoverCompact on the signature each call and is not cached on the message. ProcessSpork() now calls it twice in succession (line 148 for the has_value() check, line 150 for value()), and the prior IsSporkValid() in the caller has already done it once — three recoveries per SPORK message on the happy path. Sporks are infrequent so the practical impact is negligible, but caching the optional locally avoids the redundant ECDSA recovery.

💡 Suggested change
Suggested change
if (!spork.GetSignerKeyID().has_value()) return false;
auto keyIDSigner = spork.GetSignerKeyID().value();
auto opt_keyIDSigner = spork.GetSignerKeyID();
if (!opt_keyIDSigner.has_value()) return false;
auto keyIDSigner = *opt_keyIDSigner;

source: ['claude']

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