Skip to content

platform-wallet: persist chain truncation (reorg) reported by key-wallet #5380

Open

Description

@Claudius-Maginificent

TL;DR: When the blockchain reorganizes, the wallet now forgets the transactions from the abandoned blocks in memory, but the copy saved on disk keeps them until the next change overwrites it.

User story

As a wallet user, I want a chain reorganization to be reflected in my saved wallet, to achieve the same balances and history after a restart as before it.

Scenario

Base flow

The wallet records a transaction from a block. The network then replaces that block with a different branch that does not contain the transaction. The app is restarted.

Actual behavior

While running, the wallet drops the transaction and corrects the balance. On disk the transaction, the coins it created, the coins it spent and the higher sync height remain, so after a restart the wallet can show the abandoned transaction again and resume scanning above the fork point.

Expected behavior

The saved wallet mirrors the reorganization: removed transactions and their coins are deleted, InstantSend-locked ones go back to unconfirmed, the coins they spent are available again, and the saved sync height is lowered to the fork height.

Detailed discussion

rust-dashcore dev (dashpay/rust-dashcore#1104) adds WalletInfoInterface::truncate_above and emits WalletEvent::ChainTruncated { wallet_id, height, txids, unconfirmed_txids, restored_outpoints, balance, account_balances }. Its documentation states what a consumer mirroring wallet state to disk must do: delete the removed transactions with any UTXO they created, move the unconfirmed ones back to the mempool with their UTXOs, mark the restored coins unspent, and lower its synced height to height when it is above it.

Platform picks the event up with the rust-dashcore pin update in #5307. There, packages/rs-platform-wallet/src/changeset/core_bridge.rs (build_core_changeset) logs the event and persists nothing, because CoreChangeSet cannot express it today:

  • synced_height / last_processed_height are monotonic-max on merge, so they cannot be lowered.
  • The only subtractive shape is SweepBatch, which requires a winning transaction (superseded_by); a truncation has none.
  • There is no shape for demoting a confirmed record back to unconfirmed.

Scope of a fix: a new changeset shape for truncation; SQLite, SwiftData (FFI) and Room (JNI) persisters applying it atomically; the DashPay sent-payment evidence for payments whose confirmation was truncated; the spend-observation fences for coins that became unspent again; restart tests on each backend.

Before the pin update the wallet did not handle reorganizations in memory either, so the saved state is no worse than before; the new gap is that memory and disk now disagree until restart.

Prior work

Attribution

🤖 Co-authored by Claudius the Magnificent AI Agent

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions