Skip to content

Treat reorged scripts-valid bodies as BIP22 duplicate - #596

Draft
metaphorics wants to merge 2 commits into
cursor/mining-prioritise-dust-1522from
cursor/mining-stale-duplicate-1522
Draft

Treat reorged scripts-valid bodies as BIP22 duplicate#596
metaphorics wants to merge 2 commits into
cursor/mining-prioritise-dust-1522from
cursor/mining-stale-duplicate-1522

Conversation

@metaphorics

@metaphorics metaphorics commented Sep 4, 2026

Copy link
Copy Markdown
Contributor

Summary

Core GBT proposal LookupBlockIndex returns duplicate when pindex->IsValid(BLOCK_VALID_SCRIPTS), including for a body that was fully connected and later reorged off the tip. This node previously treated only the current applied chain as scripts-valid, so a disconnected (or reorged) body became duplicate-inconclusive.

API-21 treats a block as scripts-valid when chain_tx_count != 0 or it is on the applied chain. The count is written by record_applied_tx_count after a successful apply and is not cleared on disconnect. Checkpoint restore writes the count only on the applied tip, so applied-chain membership is the restore fallback for ancestors. Header-only nodes stay 0 and off the applied chain, so submitheader then submitblock still applies the body. NodeStatus::Active / Stale remain header-chain displacement and are not used.

submitblock uses the same scripts-valid test. A stale scripts-valid resubmit is miner-facing duplicate, not inconclusive-not-best-prevblk. Core BLOCK_HAVE_DATA after prune is not modeled separately.

Stack

Draft on cursor/mining-prioritise-dust-1522 (API-20). Merge after #593.

Tests (local)

cargo test -p bitcoin-rs-node --no-default-features --features fjall --test mining

Includes:

  • proposal_of_a_disconnected_scripts_valid_block_is_duplicate
  • submit_of_a_disconnected_scripts_valid_block_is_duplicate
  • applied_ancestor_with_unset_chain_tx_count_is_duplicate
Open in Web Open in Cursor 

Core proposal LookupBlockIndex returns duplicate when
IsValid(BLOCK_VALID_SCRIPTS), including after a later reorg. Use
chain_tx_count != 0 (set on apply, kept on disconnect) instead of
applied-chain membership. submitblock matches ProcessNewBlock !new_block.

Co-authored-by: metaphorics <metaphorics@users.noreply.github.com>
@coderabbitai

coderabbitai Bot commented Sep 4, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Team

Run ID: 69480a17-9720-4d6e-9310-bfacaf58508e

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

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.

@cursor

cursor Bot commented Sep 4, 2026

Copy link
Copy Markdown
Contributor

Bugbot couldn't run - usage limit reached

Bugbot is counted against Cursor usage for this user or team, and this run hit a usage or spend limit.

A user or team admin can review and increase usage limits in the Cursor dashboard.

(requestId: serverGenReqId_43724ad7-a6a1-45fa-9cdb-7e870e6a9cf7)

@qodo-code-review

Copy link
Copy Markdown
Contributor

PR Summary by Qodo

Classify reorged scripts-valid blocks as BIP22 duplicates

🐞 Bug fix 🧪 Tests 📝 Documentation 🕐 20-40 Minutes

Grey Divider

AI Description

• Classifies disconnected scripts-valid block bodies as BIP22 duplicates using persistent
 transaction counts.
• Preserves header-only submission behavior while aligning mining semantics with Bitcoin Core.
• Adds reorg regression coverage and documents the API-21 compatibility contract.
Diagram

graph TD
  P["GBT Proposal"] --> L["Block Tree"] --> C{"Known State"}
  S["Submit Block"] --> L
  C -->|"invalid proposal"| X["Duplicate Invalid"]
  C -->|"chain tx count > 0"| D["Duplicate"]
  C -->|"header-only proposal"| I["Duplicate Inconclusive"]
  C -->|"unknown or submit"| V["Validate or Apply"]
Loading
High-Level Assessment

The chosen approach is appropriate because chain_tx_count is existing metadata written only after successful connection and retained across disconnects, directly matching Core's scripts-valid semantics. Applied-chain membership and NodeStatus were considered but cannot distinguish reorged validated bodies from header-only entries.

Files changed (7) +109 / -21

Bug fix (1) +10 / -12
mining.rsRecognize disconnected scripts-valid bodies as duplicates +10/-12

Recognize disconnected scripts-valid bodies as duplicates

• Changes known-block classification from current applied-chain membership to persistent nonzero 'chain_tx_count'. Proposal and submit paths now return 'Duplicate' for previously connected bodies while still allowing header-only submissions to apply their bodies.

crates/node/src/mining.rs

Tests (1) +71 / -0
mining.rsCover duplicate responses after block disconnection +71/-0

Cover duplicate responses after block disconnection

• Adds a disconnect helper and regression tests for proposal and submitblock behavior after disconnecting a scripts-valid block. The tests verify the transaction count remains populated and duplicate submission does not move the applied tip.

crates/node/tests/mining.rs

Documentation (5) +28 / -9
control.rsClarify duplicate block result semantics +1/-1

Clarify duplicate block result semantics

• Updates 'BlockValidationResult::Duplicate' documentation to include previously connected scripts-valid bodies that were later reorged.

crates/mining/src/control.rs

registry.rsDocument reorg-aware submitblock duplicates +1/-1

Document reorg-aware submitblock duplicates

• Updates the submitblock registry description to state that previously connected bodies remain duplicates after reorg.

crates/rpc/src/registry.rs

README.mdRegister the API-21 mining compatibility contract +1/-1

Register the API-21 mining compatibility contract

• Extends the external API contract index through API-21 and references the new reorged scripts-valid duplicate behavior and tests.

docs/contracts/README.md

external-api.mdDefine API-21 scripts-valid duplicate semantics +24/-5

Define API-21 scripts-valid duplicate semantics

• Documents 'chain_tx_count' as the scripts-valid indicator for proposal and submitblock classification. It distinguishes this metadata from header-chain status and records the associated regression tests.

docs/contracts/external-api.md

rpc-reference.mdUpdate submitblock duplicate behavior reference +1/-1

Update submitblock duplicate behavior reference

• Clarifies that a previously connected block body, including one disconnected by reorg, returns duplicate.

docs/rpc-reference.md

@qodo-code-review

Copy link
Copy Markdown
Contributor

Code Review by Qodo

🐞 Bugs (1)

Grey Divider


Action required

1. Restart loses duplicate state 🐞 Bug
Description
Checkpoint restore assigns chain_tx_count only to the applied tip, leaving already-applied
ancestors at zero, so known_block_result returns DuplicateInconclusive for them after restart.
submitblock then bypasses the duplicate fast path and attempts to process an already-applied
ancestor instead of returning duplicate.
Code

crates/node/src/mining.rs[R738-739]

+        if node.chain_tx_count != 0 {
            return Some(BlockValidationResult::Duplicate);
Relevance

●● Moderate

Restart-state correctness concern is plausible, but no closely matching checkpoint-ancestor
precedent was found.

PR-#264

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
Block-tree insertion initializes every reconstructed node with a zero count, while checkpoint
loading restores the manifest count only on the applied tip. A checkpoint contains the full applied
ancestry, but neither checkpoint loading nor journal replay backfills those ancestors; therefore the
new predicate cannot recognize them as duplicates after restart.

crates/chain/src/tree.rs[649-656]
crates/node/src/checkpoint.rs[171-225]
crates/node/src/checkpoint.rs[1282-1308]
crates/node/src/chainstate_journal/replay.rs[475-508]
crates/node/src/state.rs[1029-1055]
crates/node/src/mining.rs[731-742]
crates/node/src/mining.rs[779-793]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`known_block_result` now assumes every scripts-valid block has a nonzero per-node `chain_tx_count`, but checkpoint restore only restores that value on the applied tip. Applied ancestors consequently lose their duplicate classification after restart.

## Issue Context
Header reconstruction initializes every node's count to zero. Restore then updates only `headers.applied_tip_id`, while journal replay only assigns counts to the replayed suffix. Preserve durable per-block scripts-valid state, or retain an applied-chain fallback while introducing durable state for disconnected scripts-valid blocks.

## Fix Focus Areas
- crates/node/src/mining.rs[724-742]
- crates/node/src/checkpoint.rs[1282-1308]
- crates/chain/src/tree.rs[649-656]
- crates/node/src/chainstate_journal/replay.rs[475-508]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

Context sources
✅ Compliance rules (platform): 18 rules
✅ Web pages:
  +12 more
Review mode: ⚖️ Balanced: This changes miner-facing duplicate classification across proposal and submitblock paths, with reorg-state semantics and public API contract updates, so it carries genuine behavioral and compatibility risk despite focused scope.
ⓘ  4 issues published inline · 1 in summary

Grey Divider

Comment thread crates/node/src/mining.rs Outdated
Comment on lines +726 to +729
/// `Invalid` is `BLOCK_FAILED_VALID`. A non-zero `chain_tx_count` is set
/// only after a successful apply (`record_applied_tx_count`) and survives
/// disconnect, matching Core `IsValid(BLOCK_VALID_SCRIPTS)` including
/// reorged bodies. Header-only entries stay 0 and are inconclusive —

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Remediation recommended

1. Comment duplicates api-21 rule 📘 Rule violation ⚙ Maintainability

The expanded known_block_result comment re-specifies the API-21 scripts-valid and reorg behavior
already defined in the authoritative external API contract without referencing it. This creates
parallel documentation that can drift from the contract.
Agent Prompt
## Issue description
The implementation comment duplicates the scripts-valid and reorg semantics already documented by the authoritative `API-21` contract.

## Issue Context
Keep only implementation-specific information locally and reference `docs/contracts/external-api.md` section `API-21` for the complete business rule. Apply the same treatment to the nearby `submit` comment if it repeats contract semantics.

## Fix Focus Areas
- crates/node/src/mining.rs[724-730]
- crates/node/src/mining.rs[781-785]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

Comment on lines +1270 to +1271
#[test]
fn proposal_of_a_disconnected_scripts_valid_block_is_duplicate() -> anyhow::Result<()> {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Remediation recommended

2. Tests omit api-21 annotation 📘 Rule violation ▣ Testability

The two new permanent compatibility tests do not identify API-21, BIP22, or another named contract
in their names or adjacent annotations. Although the contract catalog maps them externally, the
tests themselves do not exercise an explicitly documented contract as required.
Agent Prompt
## Issue description
The new permanent tests lack a test-local reference to their named current contract.

## Issue Context
`docs/contracts/external-api.md` maps both tests to active contract `API-21`. Add an adjacent contract/spec comment such as `// CONTRACT: API-21 (BIP22 duplicate behavior)` to each test.

## Fix Focus Areas
- crates/node/tests/mining.rs[1270-1271]
- crates/node/tests/mining.rs[1307-1308]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

Comment thread crates/node/src/mining.rs Outdated
Comment on lines 738 to 739
if node.chain_tx_count != 0 {
return Some(BlockValidationResult::Duplicate);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Action required

3. Restart loses duplicate state 🐞 Bug ≡ Correctness

Checkpoint restore assigns chain_tx_count only to the applied tip, leaving already-applied
ancestors at zero, so known_block_result returns DuplicateInconclusive for them after restart.
submitblock then bypasses the duplicate fast path and attempts to process an already-applied
ancestor instead of returning duplicate.
Agent Prompt
## Issue description
`known_block_result` now assumes every scripts-valid block has a nonzero per-node `chain_tx_count`, but checkpoint restore only restores that value on the applied tip. Applied ancestors consequently lose their duplicate classification after restart.

## Issue Context
Header reconstruction initializes every node's count to zero. Restore then updates only `headers.applied_tip_id`, while journal replay only assigns counts to the replayed suffix. Preserve durable per-block scripts-valid state, or retain an applied-chain fallback while introducing durable state for disconnected scripts-valid blocks.

## Fix Focus Areas
- crates/node/src/mining.rs[724-742]
- crates/node/src/checkpoint.rs[1282-1308]
- crates/chain/src/tree.rs[649-656]
- crates/node/src/chainstate_journal/replay.rs[475-508]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

Comment thread crates/node/src/mining.rs Outdated
Comment on lines 738 to 739
if node.chain_tx_count != 0 {
return Some(BlockValidationResult::Duplicate);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Remediation recommended

4. Pruned bodies report duplicate 🐞 Bug ≡ Correctness

A nonzero chain_tx_count records historical validation but does not prove that the body remains
stored, because pruning deletes bodies without clearing the count. A stale previously-applied block
whose body was pruned is therefore reported as duplicate and its submitted body is discarded
instead of being treated as new storage.
Agent Prompt
## Issue description
The shared duplicate predicate conflates historical scripts-valid state with current body availability. Pruned blocks retain `chain_tx_count` but no longer have a stored body, so `submitblock` must not take the body-present duplicate fast path solely from that count.

## Issue Context
Proposal mode may continue using durable scripts-valid state, but submit mode should independently determine whether the body is currently stored. If it was pruned, process and restore the submitted body using behavior equivalent to Core's `new_block` storage result.

## Fix Focus Areas
- crates/node/src/mining.rs[713-742]
- crates/node/src/mining.rs[779-793]
- crates/node/src/state.rs[1242-1290]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

@qodo-code-review

Copy link
Copy Markdown
Contributor

Qodo Fixer

🍒 Ready to be cherry-picked — ✅ Merged (0) · ☑ Fixed (3)

Grey Divider

🔗 Fix PR: #599

This fix PR was closed automatically. Its branch is preserved so you can cherry pick the changes into the original PR.

Prompt for coding agent

This is an automated fix prepared on a separate branch (#599). It is NOT applied to this PR.
To use it: review Fix PR #599 (https://github.com/gosuda/bitcoin-rs/pull/599), evaluate each change critically against your local context, and cherry-pick the changes that are correct into this branch. Do not accept them blindly.
Process — 3 fixed
  • ☑ Fixed: Restart loses duplicate state
  • ☑ Fixed: Comment duplicates API-21 rule
  • ☑ Fixed: Tests omit API-21 annotation
  • ⏭ Skipped (1)

Checkpoint restore writes chain_tx_count only on the applied tip, so
ancestors would lose BIP22 duplicate classification. Treat applied-chain
membership as the restore fallback; nonzero count still covers in-process
reorged bodies.

Co-authored-by: metaphorics <metaphorics@users.noreply.github.com>
@cursor

cursor Bot commented Sep 4, 2026

Copy link
Copy Markdown
Contributor

Bugbot couldn't run - usage limit reached

Bugbot is counted against Cursor usage for this user or team, and this run hit a usage or spend limit.

A user or team admin can review and increase usage limits in the Cursor dashboard.

(requestId: serverGenReqId_173b45db-db83-41c5-b185-4395e9af6c67)

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