Skip to content

test: adds QSENDRECSIGS functional tests for watchquorum nodes#7301

Open
knst wants to merge 1 commit intodashpay:developfrom
knst:tests-for-7293
Open

test: adds QSENDRECSIGS functional tests for watchquorum nodes#7301
knst wants to merge 1 commit intodashpay:developfrom
knst:tests-for-7293

Conversation

@knst
Copy link
Copy Markdown
Collaborator

@knst knst commented Apr 30, 2026

Issue being fixed or feature implemented

Addresses #7293 (comment)

What was done?

Adds functional tests for QSENDRECSIGS for watch-quorum nodes.

How Has This Been Tested?

Run on the top of #7293 and on develop with #7293 reverted.

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 24 milestone Apr 30, 2026
@github-actions
Copy link
Copy Markdown

✅ No Merge Conflicts Detected

This PR currently has no conflicts with other open PRs.

@thepastaclaw
Copy link
Copy Markdown

thepastaclaw commented Apr 30, 2026

✅ Review complete (commit 3a4fea7)

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: 3a4fea7360

ℹ️ 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".

b"qpcommit": None,
b"qrinfo": None,
b"qsendrecsigs": None,
b"qsendrecsigs": msg_qsendrecsigs,
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Keep qsendrecsigs ignored until callback exists

Changing MESSAGEMAP to decode qsendrecsigs makes P2PInterface.on_message() dispatch to on_qsendrecsigs, but P2PInterface has no such callback method, so receiving this message raises AttributeError and tears down the test peer. This is reachable when nodes send QSENDRECSIGS to authenticated quorum-relay peers (see the send paths in CMNAuth::ProcessMessage / CConnman::SetMasternodeQuorumRelayMembers), so this mapping change can break functional tests that use default P2PInterface on those connections.

Useful? React with 👍 / 👎.

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 30, 2026

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: 140315e6-2d3e-4d50-8796-25527dcab93d

📥 Commits

Reviewing files that changed from the base of the PR and between 7e524b1 and 3a4fea7.

📒 Files selected for processing (3)
  • test/functional/p2p_instantsend.py
  • test/functional/test_framework/messages.py
  • test/functional/test_framework/p2p.py

Walkthrough

These changes add test infrastructure for verifying InstantSend lock propagation to configured masternodes. A new wire-message type qsendrecsigs is defined in the test framework to enable observers. A new RecSigsObserver class extends P2PInterface to watch for incoming MSG_ISDLOCK inventory messages, and a corresponding functional test attaches observers to masternode connections, triggers an InstantSend transaction, and asserts that at least one observer received the lock announcement.

Sequence Diagram

sequenceDiagram
    participant Test as Test Case
    participant MN as Masternode
    participant Obs as RecSigsObserver
    participant Net as P2P Network

    Test->>Test: Create RecSigsObserver instances
    Test->>Obs: send_qsendrecsigs(on=True)
    Obs->>MN: Enable qsendrecsigs message
    Test->>Test: Trigger InstantSend transaction
    MN->>Net: Broadcast MSG_ISDLOCK inv
    Net->>Obs: on_inv(MSG_ISDLOCK message)
    Obs->>Obs: Record MSG_ISDLOCK received
    Test->>Test: Sync P2P observers
    Test->>Obs: Assert lock observed
    Obs-->>Test: Confirmation received
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 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 accurately summarizes the main change: adding functional tests for QSENDRECSIGS message handling in watch-quorum nodes, which is directly reflected in the changeset.
Description check ✅ Passed The description clearly relates to the changeset by explaining that functional tests for QSENDRECSIGS were added for watch-quorum nodes, and references the related issue.
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
Review rate limit: 7/8 reviews remaining, refill in 7 minutes and 30 seconds.

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

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

The new functional test adds useful coverage for the QSENDRECSIGS regression fixed in #7293, but has a few notable issues: (1) mapping qsendrecsigs to a deserializer in MESSAGEMAP without adding a default on_qsendrecsigs callback creates a latent AttributeError for any P2PInterface mock that becomes a verified quorum-relay MN peer; (2) the assertion uses any() so the test passes even if 3 of 4 MNs still suppress the inv, which is exactly the partial regression it should catch; (3) the test uses raw QSENDRECSIGS rather than starting a node with -watchquorums, despite the log line claiming otherwise.

Reviewed commit: 3a4fea7

🟡 2 suggestion(s) | 💬 2 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 `test/functional/p2p_instantsend.py`:
- [SUGGESTION] lines 169-170: `any()` makes the regression check too weak — use `all()`
  PR #7293 fixed every masternode peer to stop suppressing `MSG_ISDLOCK` for non-MN recsig consumers. The server-side filter at `net_processing.cpp:1201-1204` (`PeerReconstructsISLockFromRecsig`) only suppresses the inv when the peer is a verified MN, so every one of the 4 MNs is expected to relay the inv to its attached non-MN observer. Asserting `any(...)` lets the test pass even if 3 out of 4 MNs are still broken — i.e., a partial regression of #7293 would slip through, which is precisely what this test should catch. Tighten to `all(...)`, ideally wrapped in a `wait_until` to give propagation room.

In `test/functional/test_framework/p2p.py`:
- [SUGGESTION] line 181: Add a default `on_qsendrecsigs` callback to P2PInterface
  Previously `MESSAGEMAP[b"qsendrecsigs"] = None` caused incoming QSENDRECSIGS messages to be silently dropped at p2p.py:414. After this change, the message is deserialized and dispatched via `on_message` → `getattr(self, 'on_qsendrecsigs')(message)` (p2p.py:562). No default `on_qsendrecsigs` is defined alongside the other Dash defaults at lines 617–625. Today this is latent: `p2p_quorum_data.py` calls the `mnauth` RPC on P2P mocks, but its fake proRegTxHashes don't match real relay quorum members so `mnauth.cpp:161-167` doesn't push QSENDRECSIGS back. Any future test that performs MNAUTH with a real MN proRegTxHash that is also in a relay quorum will receive QSENDRECSIGS and crash with AttributeError. The fix is one line.

Comment on lines +169 to +170
assert any(o.isdlock_inv_seen for o in observers), \
"non-MN peer with QSENDRECSIGS got no MSG_ISDLOCK inv"
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: any() makes the regression check too weak — use all()

PR #7293 fixed every masternode peer to stop suppressing MSG_ISDLOCK for non-MN recsig consumers. The server-side filter at net_processing.cpp:1201-1204 (PeerReconstructsISLockFromRecsig) only suppresses the inv when the peer is a verified MN, so every one of the 4 MNs is expected to relay the inv to its attached non-MN observer. Asserting any(...) lets the test pass even if 3 out of 4 MNs are still broken — i.e., a partial regression of #7293 would slip through, which is precisely what this test should catch. Tighten to all(...), ideally wrapped in a wait_until to give propagation room.

💡 Suggested change
Suggested change
assert any(o.isdlock_inv_seen for o in observers), \
"non-MN peer with QSENDRECSIGS got no MSG_ISDLOCK inv"
self.wait_until(lambda: all(o.isdlock_inv_seen for o in observers),
timeout=10)
assert all(o.isdlock_inv_seen for o in observers), \
"some non-MN peers with QSENDRECSIGS got no MSG_ISDLOCK inv"

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 `test/functional/p2p_instantsend.py`:
- [SUGGESTION] lines 169-170: `any()` makes the regression check too weak — use `all()`
  PR #7293 fixed every masternode peer to stop suppressing `MSG_ISDLOCK` for non-MN recsig consumers. The server-side filter at `net_processing.cpp:1201-1204` (`PeerReconstructsISLockFromRecsig`) only suppresses the inv when the peer is a verified MN, so every one of the 4 MNs is expected to relay the inv to its attached non-MN observer. Asserting `any(...)` lets the test pass even if 3 out of 4 MNs are still broken — i.e., a partial regression of #7293 would slip through, which is precisely what this test should catch. Tighten to `all(...)`, ideally wrapped in a `wait_until` to give propagation room.

b"qpcommit": None,
b"qrinfo": None,
b"qsendrecsigs": None,
b"qsendrecsigs": msg_qsendrecsigs,
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: Add a default on_qsendrecsigs callback to P2PInterface

Previously MESSAGEMAP[b"qsendrecsigs"] = None caused incoming QSENDRECSIGS messages to be silently dropped at p2p.py:414. After this change, the message is deserialized and dispatched via on_messagegetattr(self, 'on_qsendrecsigs')(message) (p2p.py:562). No default on_qsendrecsigs is defined alongside the other Dash defaults at lines 617–625. Today this is latent: p2p_quorum_data.py calls the mnauth RPC on P2P mocks, but its fake proRegTxHashes don't match real relay quorum members so mnauth.cpp:161-167 doesn't push QSENDRECSIGS back. Any future test that performs MNAUTH with a real MN proRegTxHash that is also in a relay quorum will receive QSENDRECSIGS and crash with AttributeError. The fix is one line.

💡 Suggested change
Suggested change
b"qsendrecsigs": msg_qsendrecsigs,
def on_qsendrecsigs(self, message): pass
def on_mnlistdiff(self, message): pass
def on_clsig(self, message): pass
def on_islock(self, message): pass
def on_isdlock(self, message): pass

source: ['claude']

🤖 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 `test/functional/test_framework/p2p.py`:
- [SUGGESTION] line 181: Add a default `on_qsendrecsigs` callback to P2PInterface
  Previously `MESSAGEMAP[b"qsendrecsigs"] = None` caused incoming QSENDRECSIGS messages to be silently dropped at p2p.py:414. After this change, the message is deserialized and dispatched via `on_message` → `getattr(self, 'on_qsendrecsigs')(message)` (p2p.py:562). No default `on_qsendrecsigs` is defined alongside the other Dash defaults at lines 617–625. Today this is latent: `p2p_quorum_data.py` calls the `mnauth` RPC on P2P mocks, but its fake proRegTxHashes don't match real relay quorum members so `mnauth.cpp:161-167` doesn't push QSENDRECSIGS back. Any future test that performs MNAUTH with a real MN proRegTxHash that is also in a relay quorum will receive QSENDRECSIGS and crash with AttributeError. The fix is one line.

self.generate(self.nodes[0], 2)

def test_isdlock_relayed_to_recsigs_observer(self):
self.log.info("Non-MN peer started with -watchquorums must still get ISDLOCK invs")
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: Test mentions -watchquorums but never sets the flag — either fix the log or actually exercise the watcher path

The log line claims "Non-MN peer started with -watchquorums must still get ISDLOCK invs", but set_dash_test_params(8, 4) doesn't pass extra_args=["-watchquorums"] to any node — the test instead attaches P2PInterface mocks and sends QSENDRECSIGS manually. That does cover the generic m_wants_recsigs && !verified_masternode server branch, which is the same code path #7293 fixed, but the production scenario reported in the bug was a full node running with -watchquorums. Either (a) rephrase the log to match what the test actually does ("Non-MN peer that opted in via QSENDRECSIGS must still get ISDLOCK invs"), or (b) start a real node with -watchquorums to exercise the watcher init path end-to-end.

💡 Suggested change
Suggested change
self.log.info("Non-MN peer started with -watchquorums must still get ISDLOCK invs")
self.log.info("Non-MN peer that opted in via QSENDRECSIGS must still get ISDLOCK invs")

source: ['claude', 'codex']

Comment on lines +29 to +31
# MSG_ISDLOCK inv type, see src/protocol.h
if inv.type == 31:
self.isdlock_inv_seen = True
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: Prefer a named constant for MSG_ISDLOCK type id

MSG_TX (1) and MSG_BLOCK (2) are defined as named constants in test_framework/messages.py and used elsewhere in the framework. The bare literal 31 with an explanatory comment works, but adding MSG_ISDLOCK = 31 to messages.py and importing it would match the rest of the framework and survive a future renumber gracefully. The corresponding C++ enum value lives in src/protocol.h.

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.

2 participants