Skip to content

fix(platform-wallet): owe a chain truncation's cursor to the DashPay backfill record - #5484

Merged
shumkov merged 1 commit into
v5.1-devfrom
shumkov/wallet51-truncation-backfill
Oct 19, 2026
Merged

shumkov merged 1 commit into
v5.1-devfrom
shumkov/wallet51-truncation-backfill

Conversation

@shumkov

@shumkov shumkov commented Oct 18, 2026 •

Copy link
Copy Markdown
Collaborator

Issue being fixed or feature implemented

After merging v5.0-dev into v5.1-dev (#5481), DashPay backfill coverage could be persisted beside a stale host cursor following a chain truncation. After a restart, the blocks of the new branch above the fork could then go unscanned for a newly registered contact.

This is a semantic conflict between two changes that are each correct alone, and git merged them without a textual conflict:

truncate_above skipped that bookkeeping. Here is how the bug plays out:

  1. A receival contact is registered with checkpoint 999. It counts as forward-covered at the lowered cursor, so floor and owed are both None.
  2. The record round stores covered_from = 999 with no cursor, next to the host's 1000.
  3. On relaunch the host restores 1000 plus the coverage. The coverage suppresses the reconcile's rewind, and the scan resumes past the new branch's block 1000. That block was never tested with the contact's addresses watched.

A second effect: advances emitted on the replaced branch, whether queued or already projected, were not invalidated. Once the new branch climbed back past their height they could still be stored, ahead of the new branch's rows.

Neither branch had the bug alone. Before the merge, v5.1 coverage was process-local, so a relaunch re-armed the contact. v5.0 has no truncate_above override, so v5.0-dev is not affected.

What was done?

  • PlatformWalletInfo::truncate_above now records the cursor it was at, forwards to key-wallet with ?, and then notes a lowering truncation the same way as the account-add rewind:
    • Barrier armed. This happens under the manager write lock, before ChainTruncated is emitted. Replaced-branch advances that are still queued are dropped as stale when projected. Already-projected ones carry a superseded epoch and are stripped at commit.
    • Cursor owed in the truncation's epoch. The next DashPay backfill record round writes it as a rewind beside the coverage. A host cursor committed from a pre-fork advance (older epoch) cannot settle this debt. A new-branch advance the host accepts (same epoch) does settle it, so the host is not dragged back.
    • Refused or non-lowering truncations note nothing.
  • The private helper note_account_add_rewind is renamed to note_in_memory_rewind, with its doc generalized to cover both lowerings. The six account-add call sites behave exactly as before.
  • The ChainTruncated arm comment in core_bridge.rs no longer says the stored sync height stays at the replaced branch's value.

Behaviour change, accepted. On hosts that store the backfill record durably, the first DashPay sweep after a fork runs one record-only round that lowers the host's synced_height to the fork height. It skips this if a new-branch advance has already paid the debt. The write only ever lowers the host cursor, so the cost is extra rescanning, never less coverage. This also narrows #5380's stale-cursor window for DashPay coverage that was recorded before the fork. #5380 itself is not fixed: stored rows above the fork, last_processed_height and record demotions are left as they are.

There are no FFI/JNI, schema, API or consensus changes.

Interaction with #4740 (open, base v5.1-dev, currently conflicting): its diff does not touch truncate_above or the renamed private helper. Its registration-time checkpoint restore calls the public add_managed_account, which reaches the same helper after its rebase. The two changes don't overlap or duplicate each other. Its account_generation hunk in platform_wallet_traits.rs already conflicts with v5.1-dev, independent of this PR.

How Has This Been Tested?

New tests, all written before the fix:

Test Before After
payments.rs should_rescan_a_new_contacts_blocks_after_a_restart_that_follows_a_chain_truncation: host accepted 1000 → engine WalletManager::truncate_above(999) at chain lock 999 → contact at 999 → forward-covered record round → relaunch from the host's cursor and record ✖ left: 1000, right: 999 "the relaunched scan must test the new branch's block 1000 with the contact watched" ✔
payments.rs a_chain_truncation_alone_is_owed_to_the_next_record_round: only pre-fork coverage; the debt alone runs a record-only round that writes 999 ✖ "the owed cursor alone runs a round" ✔
core_bridge.rs advances_from_before_a_chain_truncation_are_dropped_after_the_new_branch_climbs_past_them: replaced-branch advances, both projected and queued, are dropped after the new branch climbs past them (adapter level, engine truncation path) ✖ left: Some(1000), right: None "projected before the truncation: stripped at commit" ✔
platform_wallet_traits.rs should_note_a_truncation_only_when_it_lowers_the_scan_cursor: refused / at-cursor truncation notes nothing; a lowering truncation arms and owes ✖ left: 0, right: 1 ✔
payments.rs a_new_branch_advance_the_host_accepted_pays_a_chain_truncations_debt: guard against over-lowering ✔ (guard) ✔

The queued-advance variant is tested at the adapter level. In the manager-level tests the background adapter makes the "queued → committed alone after the new branch climbs" interleaving impossible to force deterministically. The end-to-end test models the reconcile-side consequence instead: the host cursor came from a pre-fork advance in the current epoch.

Mutation checks on the fix:

  • Owing without arming fails all four regression tests.
  • Arming without owing fails three of them.
  • Owing in the wrong epoch fails the guard test and the hook test.

Results on this branch:

  • cargo test -p platform-wallet: 1351 lib tests, plus the integration tests and doctests, all pass (1 test is ignored, as it already was).
  • cargo clippy -p platform-wallet --all-targets is clean (one existing dpp manifest warning).
  • cargo fmt --all -- --check is clean.

Breaking Changes

None.

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 added "!" to the title and described breaking changes in the corresponding section if my code contains any
  • I have made corresponding changes to the documentation if needed
  • If I added or changed GroveDB structure, I described it in the area's structure.rs, regenerated grovedb-structure.json, and checked the structure viewer link posted on this pull request

For repository code-owners and collaborators only

  • I have assigned this pull request to a milestone

🤖 Generated with Claude Code

PR Hygiene · 7ad0146

  • Bots — coderabbitai skipped after the window · thepastaclaw ✓
  • Self-review — post /self-reviewed
  • Reviewer requests paused — your 5 review slots are occupied; this PR is excluded from reviewers' queues. Required approvals still count without a slot.
  • Build green
  • Approvals
    • rs-platform-wallet (packages/rs-platform-wallet/src/changeset/core_bridge.rs, packages/rs-platform-wallet/src/wallet/identity/network/payments.rs, packages/rs-platform-wallet/src/wallet/platform_wallet_traits.rs) — HashEngineering or ZocoLini or llbartekll or romchornyi

When every merge requirement is met, the PR Hygiene check passes. Reviewer limits do not block merging; other required GitHub checks and protections still apply.

…backfill record

`PlatformWalletInfo::truncate_above` (brought to v5.1-dev with the
rust-dashcore update, #5307) lowers the in-memory scan cursor to the fork
height, while the host keeps the replaced branch's cursor because
`ChainTruncated` is not mirrored to the persister. Since #5026 made the
DashPay backfill coverage durable, a contact registered at or above the
fork height is forward-covered at the lowered cursor, so its record round
stored coverage with no cursor beside the host's stale one. On relaunch
the coverage suppressed the rewind and the scan resumed past the new
branch's blocks, which were never tested with the contact's addresses
watched. Neither branch had the bug alone: v5.1 coverage was process-local
and v5.0 had no truncation override.

A truncation that lowers the cursor is now noted like key-wallet's
account-add rewind: the rewind barrier is armed, so advances emitted on the
replaced branch (queued or already projected) are not stored after the
fork, and the lowered cursor is owed to the next DashPay backfill record
round, which writes it as a rewind beside the coverage. A refused or
non-lowering truncation notes nothing. The private helper is renamed
`note_account_add_rewind` -> `note_in_memory_rewind` to cover both.

Tests would have caught this in CI: ✖ before the fix, ✔ after:
- should_rescan_a_new_contacts_blocks_after_a_restart_that_follows_a_chain_truncation
  (relaunch resumed at 1000, expected 999)
- a_chain_truncation_alone_is_owed_to_the_next_record_round
  (no record round lowered the host after the fork)
- advances_from_before_a_chain_truncation_are_dropped_after_the_new_branch_climbs_past_them
  (a replaced-branch advance survived the commit: Some(1000), expected None)
- should_note_a_truncation_only_when_it_lowers_the_scan_cursor
  (rewind epoch 0, expected 1)
a_new_branch_advance_the_host_accepted_pays_a_chain_truncations_debt passes
before and after: it guards against the fix over-lowering the host once a
new-branch advance has paid the debt (it fails if the debt is owed in the
wrong epoch).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@github-actions github-actions Bot added this to the v5.1.0 milestone Oct 18, 2026
@coderabbitai

coderabbitai Bot commented Oct 18, 2026

Copy link
Copy Markdown
Contributor

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: Repository: dashpay/platform/.coderabbit.yaml
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: c044c86f-42e6-494f-bb0e-77c2624316e9

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
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

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.

@github-actions

github-actions Bot commented Oct 18, 2026 •

Copy link
Copy Markdown
Contributor

Waiting for bot review — coderabbitai not yet · thepastaclaw ✓. Wait for the missing reviews, or a writer can post /skip-bots to proceed without them; blocking findings still need addressing.
Full checklist in the description.

@github-actions github-actions Bot added the waiting-bots Waiting for the review bots to report on this head label Oct 18, 2026
@thepastaclaw

thepastaclaw commented Oct 18, 2026 •

Copy link
Copy Markdown
Collaborator

✅ Final review complete — no blockers (commit 7ad0146) · triage: normal

@thepastaclaw thepastaclaw left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Final validation — Phase 1 + Phase 2

Verified the complete three-file diff at 7ad0146 against the supplied reviewer evidence, surrounding persistence and epoch guards, and pinned/current upstream truncation implementations; no actionable in-scope defects were found. Lowering truncations correctly arm the rewind barrier and owe the cursor in the new epoch, while refused or non-lowering truncations leave bookkeeping unchanged. This was a static review: current CI reports successful Rust wallet tests, Swift build/tests and schema checks, and Kotlin build/tests; PR Hygiene remains pending.

Review provenance

Source: reviewer 1: gemini-3.8-flash-high (agent: phase1-reviewer, role: general); reviewer 2: gemini-3.8-flash-high (agent: phase1-reviewer, role: rust-quality); reviewer 3: gpt-6.1-sol (agent: phase2-reviewer, role: general); reviewer 4: gpt-6.1-sol (agent: phase2-reviewer, role: architecture-layering); reviewer 5: gpt-6.1-sol (agent: phase2-reviewer, role: platform-versioning); reviewer 6: gpt-6.1-sol (agent: phase2-reviewer, role: rust-quality); reviewer 7: gpt-6.1-sol (agent: phase2-reviewer, role: security-auditor); final verifier: gpt-6.1-sol (agent: sol-verifier, role: final-verifier)

  • Triage: normal by gpt-6.1-sol (effort low) — The change involves nontrivial wallet cursor, epoch, and persistence bookkeeping, but does not itself change consensus, funds movement, cryptography, key handling, network deserialization, or storage migrations.
  • Phase 1 reviewers: gemini-3.8-flash-high — general (completed, effort high); agent phase1-reviewer, gemini-3.8-flash-high — rust-quality (completed, effort high); agent phase1-reviewer
  • Phase 1 model: gemini-3.8-flash-high — antigravity quota: weekly 71% left, 5h 100% left
  • Fresh verifier: gpt-6.1-sol — final-verifier; agent sol-verifier
  • Phase 2 reviewers: gpt-6.1-sol — general (completed, effort high); agent phase2-reviewer, gpt-6.1-sol — architecture-layering (completed, effort high); agent phase2-reviewer, gpt-6.1-sol — platform-versioning (completed, effort high); agent phase2-reviewer, gpt-6.1-sol — rust-quality (completed, effort high); agent phase2-reviewer, gpt-6.1-sol — security-auditor (completed, effort high); agent phase2-reviewer

@github-actions

Copy link
Copy Markdown
Contributor

Bots are done — your move: post /self-reviewed.
Full checklist in the description.

@github-actions github-actions Bot added waiting-self-review Waiting for the author to post /self-reviewed bot-review-skipped A required review bot did not report; it was skipped by the window or by a person. and removed waiting-bots Waiting for the review bots to report on this head labels Oct 19, 2026
@shumkov
shumkov merged commit 4c8ff69 into v5.1-dev Oct 19, 2026
23 of 24 checks passed
@shumkov
shumkov deleted the shumkov/wallet51-truncation-backfill branch October 19, 2026 12:47
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bot-review-skipped A required review bot did not report; it was skipped by the window or by a person. waiting-self-review Waiting for the author to post /self-reviewed

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants