Skip to content

Fix ChangeSet write-once invariants - #517

Merged
ValuedMammal merged 2 commits into
masterfrom
copilot/changeset-invariants-release-fix
Jul 27, 2026
Merged

Fix ChangeSet write-once invariants#517
ValuedMammal merged 2 commits into
masterfrom
copilot/changeset-invariants-release-fix

Conversation

Copilot AI commented Jul 23, 2026

Copy link
Copy Markdown
Contributor

Description

ChangeSet's three write-once fields (descriptor, change_descriptor, network) had inconsistent invariant enforcement: the Merge impl's debug_assert! was a no-op in release builds, and the SQLite upsert unconditionally overwrote stored values.

Changes

Merge impl (src/wallet/changeset.rs)

  • Changed from "always overwrite if incoming is Some" to "only accept if existing is None" (first-write-wins)
  • debug_assert! moved to the else branch — still catches violations in dev builds
// Before: silently overwrites in release
if other.descriptor.is_some() {
    debug_assert!(...);
    self.descriptor = other.descriptor;
}

// After: first-write-wins, assert catches conflicts in debug
if self.descriptor.is_none() && other.descriptor.is_some() {
    self.descriptor = other.descriptor;
} else {
    debug_assert!(other.descriptor.is_none() || self.descriptor == other.descriptor, ...);
}

SQLite persistence (persist_to_sqlite)

  • Replaced DO UPDATE SET field = :field with DO UPDATE SET field = COALESCE(bdk_wallet.field, :field) for all three write-once columns. COALESCE returns a copy of its first non-NULL argument, or NULL if all arguments are NULL.
  • Mirrors first-write-wins at the SQL layer; safe across multiple independent upsert statements (one per field) since each only touches its own column

Tests

  • merge_first_write_wins: covers None+Some, Some+None, Some+Same scenarios, gated by #[cfg(not(debug_assertions))]

Fixes #470

Changelog notice

Fixed

  • ChangeSet::merge enforces descriptor and network invariants by ensuring only the first write is retained.
  • Fixed an issue in ChangeSet::persist_to_sqlite that would have allowed changing the descriptor or network in the wallet database

Before submitting

Copilot AI linked an issue Jul 23, 2026 that may be closed by this pull request
Copilot AI changed the title [WIP] Fix ChangeSet invariants for descriptor and network in release builds Fix ChangeSet write-once invariants: first-write-wins in Merge and SQLite Jul 23, 2026
Copilot AI requested a review from ValuedMammal July 23, 2026 18:11
@codecov

codecov Bot commented Jul 23, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 33.33333% with 12 lines in your changes missing coverage. Please review.
✅ Project coverage is 81.77%. Comparing base (20349c7) to head (9a75129).

Files with missing lines Patch % Lines
src/wallet/changeset.rs 33.33% 6 Missing and 6 partials ⚠️
Additional details and impacted files
@@           Coverage Diff           @@
##           master     #517   +/-   ##
=======================================
  Coverage   81.77%   81.77%           
=======================================
  Files          25       25           
  Lines        6484     6487    +3     
  Branches      296      296           
=======================================
+ Hits         5302     5305    +3     
+ Misses       1083     1080    -3     
- Partials       99      102    +3     
Flag Coverage Δ
rust 81.77% <33.33%> (+<0.01%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

- In `Merge` impl only accept incoming value when existing is None;
  keep debug_assert in else branch to detect conflicts in debug builds
- In `persist_to_sqlite` use COALESCE to preserve existing non-null
  values for descriptor, change_descriptor, and network columns
- Add test `merge_first_write_wins` to changeset::test module

Signed-off-by: valued mammal <valuedmammal@protonmail.com>
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
@ValuedMammal
ValuedMammal force-pushed the copilot/changeset-invariants-release-fix branch from 982df2b to 262538c Compare July 27, 2026 03:09
Signed-off-by: valued mammal <valuedmammal@protonmail.com>
@ValuedMammal ValuedMammal changed the title Fix ChangeSet write-once invariants: first-write-wins in Merge and SQLite Fix ChangeSet write-once invariants Jul 27, 2026
@ValuedMammal
ValuedMammal marked this pull request as ready for review July 27, 2026 03:36
@ValuedMammal
ValuedMammal merged commit 0416409 into master Jul 27, 2026
23 of 24 checks passed
@ValuedMammal
ValuedMammal deleted the copilot/changeset-invariants-release-fix branch July 27, 2026 03:42
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

ChangeSet invariants for descriptor/network not enforced in release build

2 participants