Skip to content

refactor: remove dependencies of CChainState on CMasternodeSync#7212

Open
knst wants to merge 7 commits intodashpay:developfrom
knst:kernel-zero-is-mn-sync
Open

refactor: remove dependencies of CChainState on CMasternodeSync#7212
knst wants to merge 7 commits intodashpay:developfrom
knst:kernel-zero-is-mn-sync

Conversation

@knst
Copy link
Copy Markdown
Collaborator

@knst knst commented Mar 12, 2026

Issue being fixed or feature implemented

The class CMasternodeSync is mostly used in network context. It should not be a direct dependency of CChainState.

The changes are split-off from #7178 to reduce scope of 7178 tidy as CInstantSendManager related only.

What was done?

  • fixed Regression tests of miner to be compatible with Instant Send
  • RejectConflictBlocks is removed from ChainLockSigner::TrySignChainTip. The call IsBlockchainSynced is duplicated; no spork validation is required since feat: remove SPORK_3_INSTANTSEND_BLOCK_FILTERING and SPORK_9_SUPERBLOCKS_ENABLED #7278
  • RejectConflictBlocks is removed from BlockAssembler::TestPackageTransactions but it should not affect anything beside regression tests [which are fixed], because blocks are not expected to be mined when sync is not done yet.
  • RejectConflictBlocks is replaced by IsInitialBlockDownload for CChainState::ConnectBlock which makes more sense
  • CMNPaymentsProcessor uses a new helper CGovernanceManager::IsValidAndSynced instead direct usages of CMasternodeSync

How Has This Been Tested?

Run unit & functional tests.

Breaking Changes

N/A

Checklist:

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

  • I have performed a self-review of my own code
  • I have commented my code, particularly in hard-to-understand areas
  • I have added or updated relevant unit/integration/functional/e2e tests
  • I have made corresponding changes to the documentation
  • I have assigned this pull request to a milestone (for repository code-owners and collaborators only)

@github-actions
Copy link
Copy Markdown

This pull request has conflicts, please rebase.

@github-actions
Copy link
Copy Markdown

github-actions Bot commented Mar 12, 2026

✅ No Merge Conflicts Detected

This PR currently has no conflicts with other open PRs.

@knst knst mentioned this pull request Mar 12, 2026
5 tasks
@knst knst force-pushed the kernel-zero-is-mn-sync branch from d65c9b9 to 521f1fc Compare April 2, 2026 06:53
@knst knst force-pushed the kernel-zero-is-mn-sync branch 2 times, most recently from f552476 to 24f2705 Compare April 2, 2026 08:31
@knst knst requested review from PastaPastaPasta, UdjinM6 and kwvg April 6, 2026 07:37
@knst knst marked this pull request as ready for review April 6, 2026 07:38
@thepastaclaw
Copy link
Copy Markdown

thepastaclaw commented Apr 6, 2026

✅ Review complete (commit 94eeabd)

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 6, 2026

Walkthrough

This change removes CMasternodeSync dependencies from several components (CChainstateHelper, CMNPaymentsProcessor, llmq::CInstantSendManager, LLMQContext) and from node chainstate loading APIs. The InstantSend public API RejectConflictingBlocks() and the CChainstateHelper passthrough were removed; InstantSend-related checks now run based on node sync/IBD state. CGovernanceManager gains IsValidAndSynced() and masternode payment validation now gates on that. A NullNodeSyncNotifier with assert-based callbacks was added. Tests and initialization call sites were updated to match the new signatures.

Sequence Diagram(s)

sequenceDiagram
    participant BlockConnect as CChainState::ConnectBlock
    participant ISManager as llmq::CInstantSendManager
    participant ChainHelper as CChainstateHelper
    participant Chain as Chain/Node (IBD state)

    Note over BlockConnect,Chain: Block connection flow with InstantSend checks
    BlockConnect->>Chain: Check IsInitialBlockDownload()
    alt Node synced (!IBD)
        BlockConnect->>ISManager: ConflictingISLockIfAny(txids)
        ISManager-->>BlockConnect: return conflicts (if any)
        alt Conflict
            BlockConnect->>BlockConnect: state.Invalid("conflict-tx-lock")
        else No conflict
            BlockConnect->>BlockConnect: continue connect
        end
    else Node in IBD
        BlockConnect->>BlockConnect: skip InstantSend conflict loop
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Suggested reviewers

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

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 18.75% 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 accurately summarizes the main refactoring objective: removing CMasternodeSync dependencies from CChainState.
Description check ✅ Passed The description is well-related to the changeset, explaining the issue being fixed, concrete changes made, and testing performed.
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

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

🧹 Nitpick comments (1)
src/test/miner_tests.cpp (1)

583-584: Consider extracting a helper to reduce repetition.

The UpdateTxFirstSeenMap call with the time calculation is repeated many times throughout these tests. A small helper function would improve readability.

🔧 Optional helper function
// At the top of the test file or within MinerTestingSetup
void AgeTxForInstantSend(const Uint256HashSet& txids, int64_t base_time = -1) {
    if (base_time < 0) {
        base_time = std::chrono::duration_cast<std::chrono::seconds>(
            Now<NodeSeconds>().time_since_epoch()).count();
    }
    m_node.clhandler->UpdateTxFirstSeenMap(txids, base_time - WAIT_FOR_ISLOCK_TIMEOUT);
}

Then calls become:

AgeTxForInstantSend({hashMediumFeeTx, hashPrioritsedChild, ...});
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/test/miner_tests.cpp` around lines 583 - 584, Extract a small helper
(e.g. AgeTxForInstantSend) near the top of the test file or inside
MinerTestingSetup that wraps the repeated call to
m_node.clhandler->UpdateTxFirstSeenMap by computing the epoch seconds via
std::chrono::duration_cast<std::chrono::seconds>(Now<NodeSeconds>().time_since_epoch()).count()
and passing (base_time - WAIT_FOR_ISLOCK_TIMEOUT), provide an optional base_time
parameter defaulting to the current epoch seconds, and replace all repeated
direct UpdateTxFirstSeenMap(..., Now<NodeSeconds>()... -
WAIT_FOR_ISLOCK_TIMEOUT) calls with AgeTxForInstantSend(txid_set) to reduce
duplication and improve readability while keeping existing semantics.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@src/test/miner_tests.cpp`:
- Around line 583-584: Extract a small helper (e.g. AgeTxForInstantSend) near
the top of the test file or inside MinerTestingSetup that wraps the repeated
call to m_node.clhandler->UpdateTxFirstSeenMap by computing the epoch seconds
via
std::chrono::duration_cast<std::chrono::seconds>(Now<NodeSeconds>().time_since_epoch()).count()
and passing (base_time - WAIT_FOR_ISLOCK_TIMEOUT), provide an optional base_time
parameter defaulting to the current epoch seconds, and replace all repeated
direct UpdateTxFirstSeenMap(..., Now<NodeSeconds>()... -
WAIT_FOR_ISLOCK_TIMEOUT) calls with AgeTxForInstantSend(txid_set) to reduce
duplication and improve readability while keeping existing semantics.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 632b3c82-edc1-4141-81be-782473d695b8

📥 Commits

Reviewing files that changed from the base of the PR and between 2ca9da3 and 24f2705.

📒 Files selected for processing (17)
  • src/evo/chainhelper.cpp
  • src/evo/chainhelper.h
  • src/governance/governance.cpp
  • src/governance/governance.h
  • src/init.cpp
  • src/instantsend/instantsend.cpp
  • src/instantsend/instantsend.h
  • src/llmq/context.cpp
  • src/llmq/context.h
  • src/masternode/payments.cpp
  • src/masternode/payments.h
  • src/masternode/sync.cpp
  • src/masternode/sync.h
  • src/node/chainstate.cpp
  • src/node/chainstate.h
  • src/test/miner_tests.cpp
  • src/test/util/setup_common.cpp
💤 Files with no reviewable changes (2)
  • src/node/chainstate.h
  • src/init.cpp

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 PR successfully removes several direct CMasternodeSync dependencies, but it also changes validation behavior in a way that is not safe during initial sync. ConnectBlock now enforces InstantSend conflicts whenever spork 3 is active, even though the network layer still accepts and stores ISLOCKs before IsBlockchainSynced() is true.

Reviewed commit: 24f2705

🔴 1 blocking

1 additional finding

🔴 blocking: Removing the blockchain-sync gate makes block acceptance depend on pre-sync ISLOCK state

src/instantsend/instantsend.cpp (lines 469-475)

This method used to disable conflict rejection until masternode sync had reached the blockchain-synced stage, which kept transient InstantSend state out of ConnectBlock. After this change, validation.cpp calls ShouldInstantSendRejectConflicts() and returns conflict-tx-lock for any locally known conflicting lock, while NetInstantSend::ProcessMessage() still accepts and queues incoming ISLOCKs without checking IsBlockchainSynced(). A node that is still catching up can therefore learn a conflicting ISLOCK from the network and reject an otherwise valid block before it has finished syncing, which is a behavioral regression in the block-validation path.

🤖 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/instantsend/instantsend.cpp`:
- [BLOCKING] lines 469-475: Removing the blockchain-sync gate makes block acceptance depend on pre-sync ISLOCK state
  This method used to disable conflict rejection until masternode sync had reached the blockchain-synced stage, which kept transient InstantSend state out of `ConnectBlock`. After this change, `validation.cpp` calls `ShouldInstantSendRejectConflicts()` and returns `conflict-tx-lock` for any locally known conflicting lock, while `NetInstantSend::ProcessMessage()` still accepts and queues incoming ISLOCKs without checking `IsBlockchainSynced()`. A node that is still catching up can therefore learn a conflicting ISLOCK from the network and reject an otherwise valid block before it has finished syncing, which is a behavioral regression in the block-validation path.

@github-actions
Copy link
Copy Markdown

This pull request has conflicts, please rebase.

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.

Caution

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

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

159-163: ⚠️ Potential issue | 🟡 Minor

Backdate hashLowFeeTx too, or this assertion can pass for the wrong reason.

At Line 162, hashLowFeeTx is added but not aged before Line 163 block assembly. It may be filtered by InstantSend safety timeout instead of the intended min-fee package logic.

💡 Proposed fix
     tx.vin[0].prevout.hash = hashFreeTx;
     tx.vout[0].nValue = 5000000000LL - 1000 - 50000 - feeToUse;
     uint256 hashLowFeeTx = tx.GetHash();
     m_node.mempool->addUnchecked(entry.Fee(feeToUse).FromTx(tx));
+    // Age transaction for InstantSend
+    m_node.clhandler->UpdateTxFirstSeenMap(
+        {hashLowFeeTx},
+        std::chrono::duration_cast<std::chrono::seconds>(Now<NodeSeconds>().time_since_epoch()).count() - WAIT_FOR_ISLOCK_TIMEOUT);
     pblocktemplate = AssemblerForTest(chainparams).CreateNewBlock(scriptPubKey);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/test/miner_tests.cpp` around lines 159 - 163, The test computes
hashLowFeeTx (via tx.GetHash()) and adds it to the mempool with
m_node.mempool->addUnchecked(entry.Fee(feeToUse).FromTx(tx)) but never backdates
that mempool entry, so the subsequent
AssemblerForTest(...).CreateNewBlock(scriptPubKey) may exclude it due to
InstantSend safety timeout instead of exercising the min-fee package logic; fix
by setting the mempool entry time before adding (e.g. use the CTxMemPoolEntry
builder's Time(...) or equivalent on the same `entry` used for FromTx(tx)) to a
value older than the InstantSend safety window so hashLowFeeTx is aged and
evaluated by the min-fee package logic when CreateNewBlock is called.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Outside diff comments:
In `@src/test/miner_tests.cpp`:
- Around line 159-163: The test computes hashLowFeeTx (via tx.GetHash()) and
adds it to the mempool with
m_node.mempool->addUnchecked(entry.Fee(feeToUse).FromTx(tx)) but never backdates
that mempool entry, so the subsequent
AssemblerForTest(...).CreateNewBlock(scriptPubKey) may exclude it due to
InstantSend safety timeout instead of exercising the min-fee package logic; fix
by setting the mempool entry time before adding (e.g. use the CTxMemPoolEntry
builder's Time(...) or equivalent on the same `entry` used for FromTx(tx)) to a
value older than the InstantSend safety window so hashLowFeeTx is aged and
evaluated by the min-fee package logic when CreateNewBlock is called.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 48feb5b5-c791-4aef-be0a-c13d7b0f366d

📥 Commits

Reviewing files that changed from the base of the PR and between 24f2705 and a142f65.

📒 Files selected for processing (20)
  • src/chainlock/signing.cpp
  • src/evo/chainhelper.cpp
  • src/evo/chainhelper.h
  • src/governance/governance.cpp
  • src/governance/governance.h
  • src/init.cpp
  • src/instantsend/instantsend.cpp
  • src/instantsend/instantsend.h
  • src/llmq/context.cpp
  • src/llmq/context.h
  • src/masternode/payments.cpp
  • src/masternode/payments.h
  • src/masternode/sync.cpp
  • src/masternode/sync.h
  • src/node/chainstate.cpp
  • src/node/chainstate.h
  • src/node/miner.cpp
  • src/test/miner_tests.cpp
  • src/test/util/setup_common.cpp
  • src/validation.cpp
💤 Files with no reviewable changes (2)
  • src/node/chainstate.h
  • src/init.cpp
✅ Files skipped from review due to trivial changes (1)
  • src/test/util/setup_common.cpp
🚧 Files skipped from review as they are similar to previous changes (8)
  • src/governance/governance.cpp
  • src/governance/governance.h
  • src/masternode/sync.h
  • src/masternode/sync.cpp
  • src/evo/chainhelper.h
  • src/llmq/context.h
  • src/node/chainstate.cpp
  • src/masternode/payments.h

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: a142f65bf0

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

if (nOffset < m_consensus_params.nBudgetPaymentsWindowBlocks) {
// NOTE: old budget system is disabled since 12.1
if(m_mn_sync.IsSynced()) {
if (m_govman.IsValidAndSynced()) {
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 old-budget reward checks independent of gov cache

This branch now depends on m_govman.IsValidAndSynced(), but the previous logic only required masternode sync; as a result, when governance is disabled (e.g. -disablegovernance) or governance state is unavailable, a fully synced node takes the "accepting block" path and skips the blockReward cap for old-budget-window heights. That can allow overpaying coinbase amounts to be accepted between nBudgetPaymentsStartBlock and nSuperblockStartBlock, creating validation differences versus nodes that still enforce the cap.

Useful? React with 👍 / 👎.

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

I validated the reviewer outputs directly against the checked-out diff because the verifier ACP lane stalled during startup. Most of the refactor looks coherent: the IsInitialBlockDownload() swap is an intentional policy change, the governance helper preserves the practical payment gating, and the miner test aging updates match the new InstantSend behavior. The only review-worthy issue I found is in the newly added NullNodeSyncNotifier stub: its documented invariant is enforced only by debug-only asserts, so the stub does not actually stay inert in release builds.

Reviewed commit: a142f65

🟡 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/masternode/sync.cpp`:
- [SUGGESTION] lines 14-15: NullNodeSyncNotifier silently stops enforcing its invariant in release builds
  The new stub is documented as keeping `CMasternodeSync` permanently unsynced for chainstate-only contexts, but these callbacks only do `assert(false)`. Under `-DNDEBUG` they become no-ops, so `CMasternodeSync::SwitchToNextAsset()` can still advance `nCurrentAsset` to `MASTERNODE_SYNC_FINISHED` before `SyncFinished()` runs. That means the newly added stub does not actually guarantee `IsBlockchainSynced()==false` / `IsSynced()==false` in production builds, which defeats the contract described in `sync.h`. If this notifier is used for the planned chainstate-only path, release builds can accidentally re-enable network-dependent validation gates that were supposed to stay off.

Comment thread src/masternode/sync.cpp
@knst knst force-pushed the kernel-zero-is-mn-sync branch from a142f65 to 94eeabd Compare April 29, 2026 06:08
@knst
Copy link
Copy Markdown
Collaborator Author

knst commented Apr 29, 2026

@knst knst force-pushed the kernel-zero-is-mn-sync branch from a142f65 to 94eeabd now

Force-pushed to be sure that it still compiles and has no linter warnings after #7247

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

Caution

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

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

309-324: ⚠️ Potential issue | 🟠 Major

Add transaction aging to "child with higher feerate" test—un-aged transactions will be excluded from block assembly.

Both transactions (parent at line 314 and child at line 323) lack aging via UpdateTxFirstSeenMap. Without aging, IsTxSafeForMining returns false (since un-aged transactions have age=0 and threshold is 601 seconds), preventing inclusion in the block. This contradicts the test's apparent intent to verify parent-child transaction ordering. All similar tests in this file explicitly age transactions before expecting them to be included.

Add before each addUnchecked call:

m_node.clhandler->UpdateTxFirstSeenMap({hash}, pblocktemplate->block.nTime - WAIT_FOR_ISLOCK_TIMEOUT);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/test/miner_tests.cpp` around lines 309 - 324, The two transactions added
with m_node.mempool->addUnchecked (the parent and the child around
tx.GetHash()/hash) are un-aged so IsTxSafeForMining rejects them; before each
addUnchecked call call m_node.clhandler->UpdateTxFirstSeenMap({hash},
pblocktemplate->block.nTime - WAIT_FOR_ISLOCK_TIMEOUT) using the transaction
hash computed by tx.GetHash(), then proceed with entry.Fee(...).FromTx(tx). This
ensures the txs are aged for mining checks (see UpdateTxFirstSeenMap,
m_node.clhandler, addUnchecked, IsTxSafeForMining, pblocktemplate,
WAIT_FOR_ISLOCK_TIMEOUT).
🤖 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/masternode/sync.h`:
- Around line 35-41: The doc comment for the chainstate-only stub overstates its
guarantee by saying CMasternodeSync "permanently" returns
IsBlockchainSynced()=false and IsSynced()=false; change the wording to a softer
statement such as "expected in chainstate-only usage" and note that
SwitchToNextAsset() can advance sync state (e.g., to MASTERNODE_SYNC_GOVERNANCE)
before notifier asserts, so IsBlockchainSynced()/IsSynced() are not strictly
guaranteed to be permanently false; keep the note that any actual sync
advancement should use a real notifier (NodeSyncNotifierImpl) and that the stub
asserts on calls.

---

Outside diff comments:
In `@src/test/miner_tests.cpp`:
- Around line 309-324: The two transactions added with
m_node.mempool->addUnchecked (the parent and the child around tx.GetHash()/hash)
are un-aged so IsTxSafeForMining rejects them; before each addUnchecked call
call m_node.clhandler->UpdateTxFirstSeenMap({hash}, pblocktemplate->block.nTime
- WAIT_FOR_ISLOCK_TIMEOUT) using the transaction hash computed by tx.GetHash(),
then proceed with entry.Fee(...).FromTx(tx). This ensures the txs are aged for
mining checks (see UpdateTxFirstSeenMap, m_node.clhandler, addUnchecked,
IsTxSafeForMining, pblocktemplate, WAIT_FOR_ISLOCK_TIMEOUT).
🪄 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: d1c91f47-da04-4848-a177-5f6687580699

📥 Commits

Reviewing files that changed from the base of the PR and between a142f65 and 94eeabd.

📒 Files selected for processing (20)
  • src/chainlock/signing.cpp
  • src/evo/chainhelper.cpp
  • src/evo/chainhelper.h
  • src/governance/governance.cpp
  • src/governance/governance.h
  • src/init.cpp
  • src/instantsend/instantsend.cpp
  • src/instantsend/instantsend.h
  • src/llmq/context.cpp
  • src/llmq/context.h
  • src/masternode/payments.cpp
  • src/masternode/payments.h
  • src/masternode/sync.cpp
  • src/masternode/sync.h
  • src/node/chainstate.cpp
  • src/node/chainstate.h
  • src/node/miner.cpp
  • src/test/miner_tests.cpp
  • src/test/util/setup_common.cpp
  • src/validation.cpp
💤 Files with no reviewable changes (2)
  • src/node/chainstate.h
  • src/init.cpp
🚧 Files skipped from review as they are similar to previous changes (8)
  • src/node/miner.cpp
  • src/test/util/setup_common.cpp
  • src/llmq/context.h
  • src/masternode/payments.h
  • src/node/chainstate.cpp
  • src/masternode/payments.cpp
  • src/masternode/sync.cpp
  • src/instantsend/instantsend.cpp

Comment thread src/masternode/sync.h
Comment on lines +35 to +41
/** Stub implementation for use in chainstate-only (non-network) contexts.
* CMasternodeSync constructed with this notifier permanently returns
* IsBlockchainSynced()=false and IsSynced()=false, which correctly disables
* network-dependent validation paths.
*
* Asserts on any call — if sync state is being advanced, a real notifier
* (NodeSyncNotifierImpl) must be used instead. */
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Doc comment overstates the notifier guarantee

Line 36–38 says this setup permanently keeps IsBlockchainSynced() and IsSynced() false, but CMasternodeSync::SwitchToNextAsset() can move from MASTERNODE_SYNC_BLOCKCHAIN to MASTERNODE_SYNC_GOVERNANCE before any notifier assert, which makes IsBlockchainSynced() true. Please soften this to “expected in chainstate-only usage” to avoid a misleading contract.

Suggested wording tweak
-/** Stub implementation for use in chainstate-only (non-network) contexts.
- *  CMasternodeSync constructed with this notifier permanently returns
- *  IsBlockchainSynced()=false and IsSynced()=false, which correctly disables
- *  network-dependent validation paths.
+/** Stub implementation for use in chainstate-only (non-network) contexts.
+ *  In expected chainstate-only usage, sync state is not advanced, so
+ *  IsBlockchainSynced() and IsSynced() remain false, disabling
+ *  network-dependent validation paths.
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
/** Stub implementation for use in chainstate-only (non-network) contexts.
* CMasternodeSync constructed with this notifier permanently returns
* IsBlockchainSynced()=false and IsSynced()=false, which correctly disables
* network-dependent validation paths.
*
* Asserts on any callif sync state is being advanced, a real notifier
* (NodeSyncNotifierImpl) must be used instead. */
/** Stub implementation for use in chainstate-only (non-network) contexts.
* In expected chainstate-only usage, sync state is not advanced, so
* IsBlockchainSynced() and IsSynced() remain false, disabling
* network-dependent validation paths.
*
* Asserts on any callif sync state is being advanced, a real notifier
* (NodeSyncNotifierImpl) must be used instead. */
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/masternode/sync.h` around lines 35 - 41, The doc comment for the
chainstate-only stub overstates its guarantee by saying CMasternodeSync
"permanently" returns IsBlockchainSynced()=false and IsSynced()=false; change
the wording to a softer statement such as "expected in chainstate-only usage"
and note that SwitchToNextAsset() can advance sync state (e.g., to
MASTERNODE_SYNC_GOVERNANCE) before notifier asserts, so
IsBlockchainSynced()/IsSynced() are not strictly guaranteed to be permanently
false; keep the note that any actual sync advancement should use a real notifier
(NodeSyncNotifierImpl) and that the stub asserts on calls.

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

Both reviewers converge on a real behavioral change in ConnectBlock: replacing RejectConflictingBlocks() (gated on CMasternodeSync::IsBlockchainSynced()) with !IsInitialBlockDownload() widens the window during which the islock-conflict check runs. Because IsInitialBlockDownload() latches once and CMasternodeSync can be reset back to MASTERNODE_SYNC_BLOCKCHAIN after long sleeps, a fully-synced node post-resync will now reject blocks that conflict with persisted islocks — arguably more correct than before, but a semantic shift not justified in the PR description. The m_govman.IsValidAndSynced() substitution in IsOldBudgetBlockValueValid introduces a similar, smaller relaxation. The new NullNodeSyncNotifier is dead in this PR, and the miner-test adaptation duplicates a magic constant from chainlock/handler.cpp. No build- or consensus-breaking issues; downgrading Codex's blocking call to a suggestion since the new behavior is defensible.

Reviewed commit: 94eeabd

🟡 2 suggestion(s) | 💬 3 nitpick(s)

1 additional finding

🟡 suggestion: `IsOldBudgetBlockValueValid`: switch to `IsValidAndSynced()` relaxes the strict path when governance failed to load

src/masternode/payments.cpp (lines 161-172)

Pre-PR the strict reward check at this site triggered on m_mn_sync.IsSynced() alone; the new m_govman.IsValidAndSynced() is is_valid && IsSynced(). On a fully-synced node where governance failed to load (is_valid==false), the old code still ran the strict check, while the new code falls through to the permissive 'rely on online nodes' path. The PR description only justifies the equivalence at the IsBlockValueValid/IsBlockPayeeValid sites — the change here is a real (small) relaxation. In practice the path is mostly dead on mainnet (the window is nBudgetPaymentsStartBlock to nSuperblockStartBlock), but it is reachable on devnet/regtest. Either preserve prior behavior (m_govman.IsValid() ? m_govman.IsValidAndSynced() : m_mn_sync.IsSynced()) or call out the intentional behavior change in the commit message.

🤖 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] line 2615: `!IsInitialBlockDownload()` is not equivalent to the old `RejectConflictingBlocks()` gate after a sync reset
  Pre-PR, this islock-conflict check only ran when `CInstantSendManager::RejectConflictingBlocks()` returned true, which in practice required `CMasternodeSync::IsBlockchainSynced()` to be true. The replacement `!IsInitialBlockDownload()` is not equivalent: `CChainState::IsInitialBlockDownload()` latches to false permanently (`src/validation.cpp:1715`), but `CMasternodeSync` can be reset back to `MASTERNODE_SYNC_BLOCKCHAIN` after a long sleep / lost-tip condition (`src/node/sync_manager.cpp:154-158`, `src/masternode/sync.cpp:27-44`). During such a resync window, `NetInstantSend::TransactionAddedToMempool` and `BlockConnected` (`src/instantsend/net_instantsend.cpp:432, 522`) still skip IS processing because they check `IsBlockchainSynced()`, yet `ConnectBlock` will now reject any block that conflicts with whatever islocks happen to be persisted in `CInstantSendDb`. This is arguably more correct (signed islocks don't go stale, and the `has_chainlock` override is preserved), but the PR description does not document the change and there is no functional test exercising a freshly started or post-reset node receiving a chain that conflicts with persisted islocks. Either add a regression test or note the semantic widening explicitly in the commit message.

In `src/masternode/payments.cpp`:
- [SUGGESTION] lines 161-172: `IsOldBudgetBlockValueValid`: switch to `IsValidAndSynced()` relaxes the strict path when governance failed to load
  Pre-PR the strict reward check at this site triggered on `m_mn_sync.IsSynced()` alone; the new `m_govman.IsValidAndSynced()` is `is_valid && IsSynced()`. On a fully-synced node where governance failed to load (`is_valid==false`), the old code still ran the strict check, while the new code falls through to the permissive 'rely on online nodes' path. The PR description only justifies the equivalence at the `IsBlockValueValid`/`IsBlockPayeeValid` sites — the change here is a real (small) relaxation. In practice the path is mostly dead on mainnet (the window is `nBudgetPaymentsStartBlock` to `nSuperblockStartBlock`), but it is reachable on devnet/regtest. Either preserve prior behavior (`m_govman.IsValid() ? m_govman.IsValidAndSynced() : m_mn_sync.IsSynced()`) or call out the intentional behavior change in the commit message.

Comment thread src/validation.cpp
// DASH : CHECK TRANSACTIONS FOR INSTANTSEND

if (m_chain_helper->ShouldInstantSendRejectConflicts()) {
if (!IsInitialBlockDownload()) {
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: !IsInitialBlockDownload() is not equivalent to the old RejectConflictingBlocks() gate after a sync reset

Pre-PR, this islock-conflict check only ran when CInstantSendManager::RejectConflictingBlocks() returned true, which in practice required CMasternodeSync::IsBlockchainSynced() to be true. The replacement !IsInitialBlockDownload() is not equivalent: CChainState::IsInitialBlockDownload() latches to false permanently (src/validation.cpp:1715), but CMasternodeSync can be reset back to MASTERNODE_SYNC_BLOCKCHAIN after a long sleep / lost-tip condition (src/node/sync_manager.cpp:154-158, src/masternode/sync.cpp:27-44). During such a resync window, NetInstantSend::TransactionAddedToMempool and BlockConnected (src/instantsend/net_instantsend.cpp:432, 522) still skip IS processing because they check IsBlockchainSynced(), yet ConnectBlock will now reject any block that conflicts with whatever islocks happen to be persisted in CInstantSendDb. This is arguably more correct (signed islocks don't go stale, and the has_chainlock override is preserved), but the PR description does not document the change and there is no functional test exercising a freshly started or post-reset node receiving a chain that conflicts with persisted islocks. Either add a regression test or note the semantic widening explicitly in the commit message.

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/validation.cpp`:
- [SUGGESTION] line 2615: `!IsInitialBlockDownload()` is not equivalent to the old `RejectConflictingBlocks()` gate after a sync reset
  Pre-PR, this islock-conflict check only ran when `CInstantSendManager::RejectConflictingBlocks()` returned true, which in practice required `CMasternodeSync::IsBlockchainSynced()` to be true. The replacement `!IsInitialBlockDownload()` is not equivalent: `CChainState::IsInitialBlockDownload()` latches to false permanently (`src/validation.cpp:1715`), but `CMasternodeSync` can be reset back to `MASTERNODE_SYNC_BLOCKCHAIN` after a long sleep / lost-tip condition (`src/node/sync_manager.cpp:154-158`, `src/masternode/sync.cpp:27-44`). During such a resync window, `NetInstantSend::TransactionAddedToMempool` and `BlockConnected` (`src/instantsend/net_instantsend.cpp:432, 522`) still skip IS processing because they check `IsBlockchainSynced()`, yet `ConnectBlock` will now reject any block that conflicts with whatever islocks happen to be persisted in `CInstantSendDb`. This is arguably more correct (signed islocks don't go stale, and the `has_chainlock` override is preserved), but the PR description does not document the change and there is no functional test exercising a freshly started or post-reset node receiving a chain that conflicts with persisted islocks. Either add a regression test or note the semantic widening explicitly in the commit message.

Comment thread src/masternode/sync.h
Comment on lines +38 to 50
* network-dependent validation paths.
*
* Asserts on any call — if sync state is being advanced, a real notifier
* (NodeSyncNotifierImpl) must be used instead. */
class NullNodeSyncNotifier final : public NodeSyncNotifier
{
public:
void SyncReset() override;
void SyncFinished() override;
};

//
// CMasternodeSync : Sync masternode assets in stages
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: NullNodeSyncNotifier is added but never instantiated in this PR

The commit message for 94eeabd says the stub is introduced 'to use it for CChainState', but a grep across src/ finds only the declaration in src/masternode/sync.h:42 and the asserting bodies in src/masternode/sync.cpp:14-15 — no construction site. The class is dead code as merged. Either move the commit into the follow-up that actually consumes it, or note in the PR description that this is staging for a follow-up so reviewers don't expect the integration here. (The assert(false) bodies are safe — src/util/check.h makes building with NDEBUG a hard error in this codebase, so the asserts cannot be elided in release builds.)

source: ['claude']

Comment thread src/test/miner_tests.cpp
BlockAssembler AssemblerForTest(const CChainParams& params);
};

static constexpr int WAIT_FOR_ISLOCK_TIMEOUT{601};
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: Duplicated WAIT_FOR_ISLOCK_TIMEOUT magic constant drifts from production source-of-truth

miner_tests defines static constexpr int WAIT_FOR_ISLOCK_TIMEOUT{601} (seconds), duplicating the production constant static constexpr auto WAIT_FOR_ISLOCK_TIMEOUT{10min} in src/chainlock/handler.cpp:37. If the production value ever changes, these tests will silently start backdating txs by the wrong amount — passing if the new value is smaller (false confidence) or becoming flaky if larger. Either expose the production constant in a header and reuse it, or at minimum add a comment cross-referencing chainlock/handler.cpp:37 so future changes stay in sync. The +1 second is also undocumented — it appears to compensate for the strict-greater-than comparison but isn't called out.

source: ['claude']

Comment thread src/test/miner_tests.cpp
Comment on lines +138 to 140
// Age transaction for InstantSend
m_node.clhandler->UpdateTxFirstSeenMap({hashParentTx, hashMediumFeeTx, hashHighFeeTx}, std::chrono::duration_cast<std::chrono::seconds>(Now<NodeSeconds>().time_since_epoch()).count() - WAIT_FOR_ISLOCK_TIMEOUT);
std::unique_ptr<CBlockTemplate> pblocktemplate = AssemblerForTest(chainparams).CreateNewBlock(scriptPubKey);
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: Pre-aging txs via UpdateTxFirstSeenMap couples tests to chainlock internals

Each UpdateTxFirstSeenMap({...}, now - WAIT_FOR_ISLOCK_TIMEOUT) call backdates txs so IsTxSafeForMining returns true unconditionally — restoring the pre-PR behavior where RejectConflictingBlocks() == false simply skipped the gate. The result is that the tests no longer exercise the IS gating path at all and instead reach into chainlock-handler internals from miner tests. Consider extracting a single helper (e.g. AgeTxForIS({hashes...}) on MinerTestingSetup) instead of inlining the same std::chrono::duration_cast<std::chrono::seconds>(...) ten times, and consider a parallel test variant that does NOT pre-age txs, to verify the gate still rejects un-aged unconfirmed txs once IS is enabled.

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

Two valid findings against this CMasternodeSync decoupling refactor. (1) Claude flagged that NullNodeSyncNotifier uses bare assert(false) which is a no-op under NDEBUG, breaking the documented invariant in release builds. (2) Codex flagged that removing RejectConflictingBlocks() from BlockAssembler::TestPackageTransactions changes real mining behavior on regtest/devnet/isolated-node InstantSend setups — confirmed by the test fixture having to artificially age transactions via UpdateTxFirstSeenMap. Claude's secondary finding about the ConnectBlock IBD-vs-IsBlockchainSynced semantic shift is real but lower-impact and likely intentional; kept as a nitpick asking for a clarifying comment.

Reviewed commit: 94eeabd

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

1 additional finding

🟡 suggestion: Removing RejectConflictingBlocks() gate changes mining behavior beyond the unit tests

src/node/miner.cpp (lines 381-385)

Pre-PR this gate was RejectConflictingBlocks() || !IsInstantSendEnabled() || IsLocked(txid) — the first clause returned m_mn_sync.IsBlockchainSynced(), which short-circuited the IsTxSafeForMining check until masternode/blockchain sync had progressed past the blockchain stage. That sync gate also protected callers that don't wait on mn_sync: the hidden mining RPCs (generateblocks, generatetoaddress, generateblock) call BlockAssembler::CreateNewBlock() directly (src/rpc/mining.cpp). On regtest/devnet or any isolated InstantSend-enabled node, freshly submitted mempool transactions will now be silently filtered out of mined blocks until they are 10 minutes old or manually IS-locked. The numerous UpdateTxFirstSeenMap(..., now - WAIT_FOR_ISLOCK_TIMEOUT) additions in src/test/miner_tests.cpp are direct evidence the change affects real assembly behavior, not just a test harness quirk. Either gate this on !IsInitialBlockDownload() (to mirror the new ConnectBlock gate) or document/test the regtest impact explicitly.

🤖 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/masternode/sync.cpp`:
- [SUGGESTION] lines 14-15: NullNodeSyncNotifier uses lowercase assert(false) — compiled out under NDEBUG
  The header at src/masternode/sync.h:35-41 documents that NullNodeSyncNotifier 'asserts on any call — if sync state is being advanced, a real notifier (NodeSyncNotifierImpl) must be used instead.' The implementation uses the C `assert()` macro from <cassert>, which expands to nothing when NDEBUG is defined. Release builds of dashd define NDEBUG (only `--enable-debug` clears it), so in release builds these methods silently return rather than abort. If a chainstate-only context wired with NullNodeSyncNotifier ever routes a call into CMasternodeSync::SwitchToNextAsset() or Reset(true, /*fNotifyReset=*/true), the stub will silently no-op and the documented invariant will be violated. Use Bitcoin Core's `Assert()` from util/check.h, which survives NDEBUG.

In `src/node/miner.cpp`:
- [SUGGESTION] lines 381-385: Removing RejectConflictingBlocks() gate changes mining behavior beyond the unit tests
  Pre-PR this gate was `RejectConflictingBlocks() || !IsInstantSendEnabled() || IsLocked(txid)` — the first clause returned `m_mn_sync.IsBlockchainSynced()`, which short-circuited the IsTxSafeForMining check until masternode/blockchain sync had progressed past the blockchain stage. That sync gate also protected callers that don't wait on mn_sync: the hidden mining RPCs (`generateblocks`, `generatetoaddress`, `generateblock`) call BlockAssembler::CreateNewBlock() directly (src/rpc/mining.cpp). On regtest/devnet or any isolated InstantSend-enabled node, freshly submitted mempool transactions will now be silently filtered out of mined blocks until they are 10 minutes old or manually IS-locked. The numerous `UpdateTxFirstSeenMap(..., now - WAIT_FOR_ISLOCK_TIMEOUT)` additions in src/test/miner_tests.cpp are direct evidence the change affects real assembly behavior, not just a test harness quirk. Either gate this on `!IsInitialBlockDownload()` (to mirror the new ConnectBlock gate) or document/test the regtest impact explicitly.

Comment thread src/masternode/sync.cpp
Comment on lines +14 to +15
void NullNodeSyncNotifier::SyncReset() { assert(false); }
void NullNodeSyncNotifier::SyncFinished() { assert(false); }
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: NullNodeSyncNotifier uses lowercase assert(false) — compiled out under NDEBUG

The header at src/masternode/sync.h:35-41 documents that NullNodeSyncNotifier 'asserts on any call — if sync state is being advanced, a real notifier (NodeSyncNotifierImpl) must be used instead.' The implementation uses the C assert() macro from , which expands to nothing when NDEBUG is defined. Release builds of dashd define NDEBUG (only --enable-debug clears it), so in release builds these methods silently return rather than abort. If a chainstate-only context wired with NullNodeSyncNotifier ever routes a call into CMasternodeSync::SwitchToNextAsset() or Reset(true, /fNotifyReset=/true), the stub will silently no-op and the documented invariant will be violated. Use Bitcoin Core's Assert() from util/check.h, which survives NDEBUG.

💡 Suggested change
Suggested change
void NullNodeSyncNotifier::SyncReset() { assert(false); }
void NullNodeSyncNotifier::SyncFinished() { assert(false); }
void NullNodeSyncNotifier::SyncReset() { Assert(false); }
void NullNodeSyncNotifier::SyncFinished() { Assert(false); }

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 `src/masternode/sync.cpp`:
- [SUGGESTION] lines 14-15: NullNodeSyncNotifier uses lowercase assert(false) — compiled out under NDEBUG
  The header at src/masternode/sync.h:35-41 documents that NullNodeSyncNotifier 'asserts on any call — if sync state is being advanced, a real notifier (NodeSyncNotifierImpl) must be used instead.' The implementation uses the C `assert()` macro from <cassert>, which expands to nothing when NDEBUG is defined. Release builds of dashd define NDEBUG (only `--enable-debug` clears it), so in release builds these methods silently return rather than abort. If a chainstate-only context wired with NullNodeSyncNotifier ever routes a call into CMasternodeSync::SwitchToNextAsset() or Reset(true, /*fNotifyReset=*/true), the stub will silently no-op and the documented invariant will be violated. Use Bitcoin Core's `Assert()` from util/check.h, which survives NDEBUG.

Comment thread src/validation.cpp
// DASH : CHECK TRANSACTIONS FOR INSTANTSEND

if (m_chain_helper->ShouldInstantSendRejectConflicts()) {
if (!IsInitialBlockDownload()) {
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: ConnectBlock IS-conflict gate now fires earlier than the previous IsBlockchainSynced check

The previous gate m_chain_helper->ShouldInstantSendRejectConflicts() resolved to m_mn_sync.IsBlockchainSynced() (true only once nCurrentAsset > MASTERNODE_SYNC_BLOCKCHAIN). The new gate is !IsInitialBlockDownload(). There is a window where !IsInitialBlockDownload() && !IsBlockchainSynced() during which the node will now reject islock-conflicting blocks where it previously would not. Using IBD as the gate for chainstate validation is reasonable, and chain-split risk is low because islock DB state is locally consistent across peers in the same situation. Adding a brief comment explaining why IBD is the correct gate here would help future readers and reduce the risk of someone undoing the change in a future refactor.

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.

No blockers from me on the current head. My earlier request-changes review applied to an older commit; the remaining comments are advisory suggestions/nits.

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