Skip to content

refactor: drop circular dependency over validation <-> masternode/payment#7339

Open
knst wants to merge 6 commits into
dashpay:developfrom
knst:refactor-validation-payments
Open

refactor: drop circular dependency over validation <-> masternode/payment#7339
knst wants to merge 6 commits into
dashpay:developfrom
knst:refactor-validation-payments

Conversation

@knst

@knst knst commented Jun 4, 2026

Copy link
Copy Markdown
Collaborator

Issue being fixed or feature implemented

There's an exception for existing circular dependency in test/lint/lint-circular-dependencies.py:

  • "masternode/payments -> validation -> masternode/payments",

What was done?

  • introduced new enum MnRewardEra to use in masternode/payment helpers
  • removed dependency of masternode/payment on CChainManager (validation.h)
  • removed dependency of masternode/payment on global Params() (chainparams.h)

How Has This Been Tested?

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

Please note, that 2 other circular dependencies always had existed but had been hidden behind masternode/payment <-> validation:

+    "evo/deterministicmns -> evo/providertx -> validation -> masternode/payments -> evo/deterministicmns",
+    "governance/superblock -> validation -> masternode/payments -> governance/superblock",

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 (for repository code-owners and collaborators only)

@knst knst added this to the 24 milestone Jun 4, 2026
@github-actions

github-actions Bot commented Jun 4, 2026

Copy link
Copy Markdown

✅ No Merge Conflicts Detected

This PR currently has no conflicts with other open PRs.

@thepastaclaw

thepastaclaw commented Jun 4, 2026

Copy link
Copy Markdown

✅ Review complete (commit c75e64a)

@coderabbitai

coderabbitai Bot commented Jun 4, 2026

Copy link
Copy Markdown

Review Change Stack

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: e0bc48bf-12a0-48b5-a6e5-810772bd16f1

📥 Commits

Reviewing files that changed from the base of the PR and between dc2e5ea and c75e64a.

📒 Files selected for processing (10)
  • src/evo/chainhelper.cpp
  • src/evo/creditpool.cpp
  • src/masternode/payments.cpp
  • src/masternode/payments.h
  • src/node/miner.cpp
  • src/rpc/masternode.cpp
  • src/test/block_reward_reallocation_tests.cpp
  • src/validation.cpp
  • src/validation.h
  • test/lint/lint-circular-dependencies.py
🚧 Files skipped from review as they are similar to previous changes (5)
  • src/evo/creditpool.cpp
  • src/validation.h
  • src/test/block_reward_reallocation_tests.cpp
  • src/node/miner.cpp
  • src/masternode/payments.h

Walkthrough

This PR introduces a MnRewardEra enum to abstract masternode reward computation and decouples the CMNPaymentsProcessor class from ChainstateManager dependency. The reward era (Classic, CreditPool, or EvoReward) is now computed explicitly at call sites based on deployment activation status and threaded through the entire payments validation pipeline. The era replaces the previous boolean v20 activation flag. GetMasternodePayment is moved from validation.cpp to payments.cpp with updated signature, and a new GetMnRewardEraAfter helper is added to determine era based on deployment states. All payment validation methods now accept CChain& active_chain instead of accessing m_chainman.ActiveChain() internally. A new SuperBlockCheckType enum replaces boolean superblock check parameters, and circular dependency documentation is updated to reflect the new module relationships.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

  • dashpay/dash#7212: Both PRs modify the masternode payments pipeline wiring around CMNPaymentsProcessor (notably its constructor usage in src/evo/chainhelper.cpp and behavior in src/masternode/payments.{cpp,h}), so the changes are directly related at the constructor/initialization level.
  • dashpay/dash#7330: Both PRs update CMNPaymentsProcessor wiring in evo/chainhelper—this PR threads era through the validation pipeline while the related PR also refactors governance dependencies, creating overlapping code-level changes.
  • dashpay/dash#7362: Both PRs modify masternode/superblock payment validation for V24 duplicate-disallowance behavior by updating how SuperblockManager::IsValidSuperblock is called with active chain context.

Suggested reviewers

  • UdjinM6
  • PastaPastaPasta
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 23.08% 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 clearly and specifically describes the main objective of the PR: removing a circular dependency between validation and masternode/payments modules through refactoring.
Description check ✅ Passed The description thoroughly explains the issue being fixed, what was done, how it was tested, and notes the newly exposed circular dependencies, directly relating to the changeset.
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.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Caution

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

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

2571-2581: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Pass this chainstate’s chain into payment validation.

ConnectBlock() runs on whichever CChainState is being advanced, but these calls still thread m_chainman.ActiveChain(). IsBlockValueValid() uses that chain for CSuperblock::GetPaymentsLimit(...), and IsBlockPayeeValid() uses it for superblock/governance checks, so background/snapshot validation can evaluate a block against the wrong chain context. Pass m_chain (or a local reference to this chainstate’s chain) instead.

Suggested fix
+    const CChain& chain = m_chain;
-    if (!m_chain_helper->mn_payments->IsBlockValueValid(m_chainman.ActiveChain(), block, pindex->nHeight, blockSubsidy + feeReward, strError, check_superblock)) {
+    if (!m_chain_helper->mn_payments->IsBlockValueValid(chain, block, pindex->nHeight, blockSubsidy + feeReward, strError, check_superblock)) {
         // NOTE: Do not punish, the node might be missing governance data
         LogPrintf("ERROR: ConnectBlock(DASH): %s\n", strError);
         return state.Invalid(BlockValidationResult::BLOCK_RESULT_UNSET, "bad-cb-amount");
     }
@@
-    if (!m_chain_helper->mn_payments->IsBlockPayeeValid(m_chainman.ActiveChain(), *block.vtx[0], pindex->pprev, blockSubsidy, feeReward, mn_reward_era, check_superblock)) {
+    if (!m_chain_helper->mn_payments->IsBlockPayeeValid(chain, *block.vtx[0], pindex->pprev, blockSubsidy, feeReward, mn_reward_era, check_superblock)) {
         // NOTE: Do not punish, the node might be missing governance data
         LogPrintf("ERROR: ConnectBlock(DASH): couldn't find masternode or superblock payments\n");
         return state.Invalid(BlockValidationResult::BLOCK_RESULT_UNSET, "bad-cb-payee");
     }
🤖 Prompt for 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.

In `@src/validation.cpp` around lines 2571 - 2581, ConnectBlock is calling
m_chain_helper->mn_payments->IsBlockValueValid(...) and IsBlockPayeeValid(...)
with m_chainman.ActiveChain(), which can be the wrong chain during
background/snapshot validation; change those calls to pass this CChainState's
chain (use m_chain or a local reference to m_chain) instead of
m_chainman.ActiveChain() so CSuperblock::GetPaymentsLimit and
superblock/governance checks use the correct chain context (update the two call
sites that reference IsBlockValueValid and IsBlockPayeeValid to pass m_chain).
🤖 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.

Outside diff comments:
In `@src/validation.cpp`:
- Around line 2571-2581: ConnectBlock is calling
m_chain_helper->mn_payments->IsBlockValueValid(...) and IsBlockPayeeValid(...)
with m_chainman.ActiveChain(), which can be the wrong chain during
background/snapshot validation; change those calls to pass this CChainState's
chain (use m_chain or a local reference to m_chain) instead of
m_chainman.ActiveChain() so CSuperblock::GetPaymentsLimit and
superblock/governance checks use the correct chain context (update the two call
sites that reference IsBlockValueValid and IsBlockPayeeValid to pass m_chain).

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: ec84e219-dc6e-4c80-954b-025e317994e1

📥 Commits

Reviewing files that changed from the base of the PR and between 0f3a2fe and dc2e5ea.

📒 Files selected for processing (10)
  • src/evo/chainhelper.cpp
  • src/evo/creditpool.cpp
  • src/masternode/payments.cpp
  • src/masternode/payments.h
  • src/node/miner.cpp
  • src/rpc/masternode.cpp
  • src/test/block_reward_reallocation_tests.cpp
  • src/validation.cpp
  • src/validation.h
  • test/lint/lint-circular-dependencies.py

@thepastaclaw thepastaclaw left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Code Review

Clean refactor PR that decouples CMNPaymentsProcessor from ChainstateManager and replaces a boolean fV20Active flag with an MnRewardEra enum computed by callers via a new GetMnRewardEraAfter helper. Both verifier agents converged on no blocking findings, and the two nitpicks raised were low-signal observations dropped during verification.

@knst knst marked this pull request as draft June 4, 2026 20:38
@github-actions

github-actions Bot commented Jun 5, 2026

Copy link
Copy Markdown

This pull request has conflicts, please rebase.

@knst knst force-pushed the refactor-validation-payments branch from dc2e5ea to 37ad935 Compare June 7, 2026 07:38
@github-actions

Copy link
Copy Markdown

This pull request has conflicts, please rebase.

@knst knst force-pushed the refactor-validation-payments branch from 37ad935 to fc05d3e Compare June 21, 2026 07:45
@github-actions

Copy link
Copy Markdown

This pull request has conflicts, please rebase.

@knst knst force-pushed the refactor-validation-payments branch from fc05d3e to c75e64a Compare June 21, 2026 07:47
@knst knst marked this pull request as ready for review June 21, 2026 08:01

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

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

73-93: 💤 Low value

Consider constexpr std::array for the immutable period table.

Since vecPeriods is never modified after initialization, using static constexpr std::array provides compile-time initialization and clearer const semantics.

♻️ Suggested change
-    // Periods used to reallocate the masternode reward from 50% to 60%
-    static std::vector<int> vecPeriods{
+    // Periods used to reallocate the masternode reward from 50% to 60%
+    static constexpr std::array<int, 19> vecPeriods{
         513, // Period 1:  51.3%
         526, // Period 2:  52.6%
         ...
         600  // Period 19: 60%
     };
🤖 Prompt for 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.

In `@src/masternode/payments.cpp` around lines 73 - 93, Replace the `static
std::vector<int> vecPeriods` declaration with `static constexpr std::array<int,
19> vecPeriods` to provide compile-time initialization and clearer const
semantics for the immutable period table. Change the vector initialization
syntax to array initialization syntax while keeping all 19 period values
unchanged. This change makes the fixed-size, never-modified collection more
efficient and semantically explicit.
🤖 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/rpc/masternode.cpp`:
- Line 320: The dereference of node.chain_helper in the
GetRequiredPaymentsString call lacks a null pointer guard, creating a potential
crash path. Add a CHECK_NONFATAL guard for node.chain_helper before this
dereference, mirroring the pattern used in the earlier loop at line 310 to
ensure consistent safety checks throughout the function.

---

Nitpick comments:
In `@src/masternode/payments.cpp`:
- Around line 73-93: Replace the `static std::vector<int> vecPeriods`
declaration with `static constexpr std::array<int, 19> vecPeriods` to provide
compile-time initialization and clearer const semantics for the immutable period
table. Change the vector initialization syntax to array initialization syntax
while keeping all 19 period values unchanged. This change makes the fixed-size,
never-modified collection more efficient and semantically explicit.
🪄 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: e0bc48bf-12a0-48b5-a6e5-810772bd16f1

📥 Commits

Reviewing files that changed from the base of the PR and between dc2e5ea and c75e64a.

📒 Files selected for processing (10)
  • src/evo/chainhelper.cpp
  • src/evo/creditpool.cpp
  • src/masternode/payments.cpp
  • src/masternode/payments.h
  • src/node/miner.cpp
  • src/rpc/masternode.cpp
  • src/test/block_reward_reallocation_tests.cpp
  • src/validation.cpp
  • src/validation.h
  • test/lint/lint-circular-dependencies.py
🚧 Files skipped from review as they are similar to previous changes (5)
  • src/evo/creditpool.cpp
  • src/validation.h
  • src/test/block_reward_reallocation_tests.cpp
  • src/node/miner.cpp
  • src/masternode/payments.h

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Caution

Inline review comments failed to post. This is likely due to GitHub's internal server error or limits when posting large numbers of comments. If you are seeing this consistently it is likely a permissions issue. Please check "Moderation" -> "Code review limits" under your organization settings.

Actionable comments posted: 1

🧹 Nitpick comments (1)
src/masternode/payments.cpp (1)

73-93: 💤 Low value

Consider constexpr std::array for the immutable period table.

Since vecPeriods is never modified after initialization, using static constexpr std::array provides compile-time initialization and clearer const semantics.

♻️ Suggested change
-    // Periods used to reallocate the masternode reward from 50% to 60%
-    static std::vector<int> vecPeriods{
+    // Periods used to reallocate the masternode reward from 50% to 60%
+    static constexpr std::array<int, 19> vecPeriods{
         513, // Period 1:  51.3%
         526, // Period 2:  52.6%
         ...
         600  // Period 19: 60%
     };
🤖 Prompt for 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.

In `@src/masternode/payments.cpp` around lines 73 - 93, Replace the `static
std::vector<int> vecPeriods` declaration with `static constexpr std::array<int,
19> vecPeriods` to provide compile-time initialization and clearer const
semantics for the immutable period table. Change the vector initialization
syntax to array initialization syntax while keeping all 19 period values
unchanged. This change makes the fixed-size, never-modified collection more
efficient and semantically explicit.
🤖 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/rpc/masternode.cpp`:
- Line 320: The dereference of node.chain_helper in the
GetRequiredPaymentsString call lacks a null pointer guard, creating a potential
crash path. Add a CHECK_NONFATAL guard for node.chain_helper before this
dereference, mirroring the pattern used in the earlier loop at line 310 to
ensure consistent safety checks throughout the function.

---

Nitpick comments:
In `@src/masternode/payments.cpp`:
- Around line 73-93: Replace the `static std::vector<int> vecPeriods`
declaration with `static constexpr std::array<int, 19> vecPeriods` to provide
compile-time initialization and clearer const semantics for the immutable period
table. Change the vector initialization syntax to array initialization syntax
while keeping all 19 period values unchanged. This change makes the fixed-size,
never-modified collection more efficient and semantically explicit.
🪄 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: e0bc48bf-12a0-48b5-a6e5-810772bd16f1

📥 Commits

Reviewing files that changed from the base of the PR and between dc2e5ea and c75e64a.

📒 Files selected for processing (10)
  • src/evo/chainhelper.cpp
  • src/evo/creditpool.cpp
  • src/masternode/payments.cpp
  • src/masternode/payments.h
  • src/node/miner.cpp
  • src/rpc/masternode.cpp
  • src/test/block_reward_reallocation_tests.cpp
  • src/validation.cpp
  • src/validation.h
  • test/lint/lint-circular-dependencies.py
🚧 Files skipped from review as they are similar to previous changes (5)
  • src/evo/creditpool.cpp
  • src/validation.h
  • src/test/block_reward_reallocation_tests.cpp
  • src/node/miner.cpp
  • src/masternode/payments.h
🛑 Comments failed to post (1)
src/rpc/masternode.cpp (1)

320-320: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Guard node.chain_helper before dereference in projected payees path.

Line 320 dereferences node.chain_helper without a check, unlike the earlier loop at Line 310. Reuse CHECK_NONFATAL here to avoid a null-pointer crash path.

Suggested fix
-        std::string strPayments = GetRequiredPaymentsString(*node.chain_helper->superblocks, tip_mn_list, h, projection[i]);
+        std::string strPayments = GetRequiredPaymentsString(*CHECK_NONFATAL(node.chain_helper)->superblocks, tip_mn_list, h, projection[i]);
🤖 Prompt for 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.

In `@src/rpc/masternode.cpp` at line 320, The dereference of node.chain_helper in
the GetRequiredPaymentsString call lacks a null pointer guard, creating a
potential crash path. Add a CHECK_NONFATAL guard for node.chain_helper before
this dereference, mirroring the pattern used in the earlier loop at line 310 to
ensure consistent safety checks throughout the function.

@thepastaclaw thepastaclaw left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Code Review

Clean behavior-preserving refactor that removes the cycle between masternode/payments and validation by threading explicit chain/era/superblock-check context. Two in-scope suggestions: the new explicit chain parameter still uses the global active chain inside a per-chainstate method, and the new is_v24 flag is reconstructed from a superblock-check enum that conflates two orthogonal concepts.

🟡 2 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/validation.cpp`:
- [SUGGESTION] src/validation.cpp:2428-2438: Pass this chainstate's m_chain into payment validation instead of m_chainman.ActiveChain()
  `CChainState::ConnectBlock` is a per-chainstate method (validation.h:563 defines the instance's own `m_chain`), but both `IsBlockValueValid` and `IsBlockPayeeValid` are called with `m_chainman.ActiveChain()`. With background/snapshot validation, `ConnectBlock` can run on a chainstate that is not the active one, in which case the chain passed to payment validation describes a different tip than the block being connected. The previous implementation hid this same coupling by calling `m_chainman.ActiveChain()` internally, so this PR preserves rather than introduces the bug — but now that the chain is an explicit parameter, the natural fix is to thread `m_chain` here so the new abstraction is correct by construction.

In `src/masternode/payments.cpp`:
- [SUGGESTION] src/masternode/payments.cpp:320-365: is_v24 reconstructed from SuperBlockCheckType conflates two orthogonal concepts
  At payments.cpp:320 and :365, `is_v24` is derived as `check_superblock == SuperBlockCheckType::DisallowDuplicates`. This works only because the sole production caller in `ConnectBlock` (validation.cpp:2422-2425) folds two independent signals — `IsSuperblockValidationRequired` and `DEPLOYMENT_V24` active — into the same enum value. Pre-refactor, `is_v24` was computed locally from `DeploymentActiveAfter(..., DEPLOYMENT_V24)` independent of whether the superblock check ran. A future caller (RPC, fuzz harness, or test) that wants V24-active duplicate detection but with a different superblock-check policy has no way to express that, and silently downgrading to `AllowDuplicates` won't trip any assertion. Consider either passing V24 as a separate parameter (keeping `SuperBlockCheckType` as a 2-state), deriving the era/V24 flag from `pindexPrev` inside payments via a small helper next to `GetMnRewardEraAfter`, or at minimum asserting at the caller that `DisallowDuplicates` is only used when V24 is active.

Comment thread src/validation.cpp
Comment thread src/masternode/payments.cpp
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants