Skip to content

Upgrade Cashu to CDK 0.18.2 with wallet migration and recovery - #114

Merged
benthecarman merged 4 commits into
lightningdevkit:masterfrom
thesimplekid:update_cdk_0.18
Nov 5, 2026
Merged

benthecarman merged 4 commits into
lightningdevkit:masterfrom
thesimplekid:update_cdk_0.18

Conversation

@thesimplekid

@thesimplekid thesimplekid commented Nov 2, 2026 •

Copy link
Copy Markdown
Contributor

Upgrade the Cashu backend and test mint dependencies from CDK 0.16 to 0.18.2, including migration of existing Orange wallet stores and recovery of interrupted payments.

  • Rekey persisted saga transactions to CDK's new IDs before recovery. The migration copies before deleting, resumes after interruption, and rejects conflicting records without overwriting them.
  • Update the KV adapter for derivation counters, quote/saga version checks, and proof ownership. Preserve legacy reservations and reload the proof cache after uncertain writes.
  • Recover incomplete operations on every wallet open, before starting payment tasks, including wallets already restored from seed. Storage or recovery failures stop initialization; operations still pending at the mint retain their reservations for the next startup.
  • Map pending, completed, and failed transaction states into Orange payments. Keep quote-based public payment IDs and collapse outgoing retries into one history entry, preferring a completed attempt or otherwise the newest attempt.

Adds migration and storage-failure regression tests using records produced by the original CDK 0.16 adapter. Funded upgrade tests exercise prepared, paid, failed, and pending melts, lost responses, mint unavailability, and repeated startup against a local mint with a fake Lightning backend. The Cashu CI job now runs these tests through bash ci/test-cashu-upgrade.sh; they remain ignored in ordinary test runs because they require the separately built old-version generator.

Documents the upgrade procedure and limitations: back up the complete store, use one active wallet instance per store, and restore the pre-upgrade backup if rollback is needed. Downgrading an upgraded store is unsupported.

Upgrade the Cashu dependencies from CDK 0.16 to 0.18.2 and adapt the wallet,
FFI, mint test harness, and storage implementation to the new APIs.

Persist operation state and migrate legacy wallet records so existing wallets
can reopen with the new adapter. Recover incomplete operations before exposing
the wallet, map transaction states into payment status, and select the
relevant outgoing attempt for each quote.

Keep adapter unit tests alongside the implementation; cross-version fixtures
and live upgrade coverage follow in a separate commit.
Exercise the upgraded adapter with persisted data produced by CDK 0.16,
including a fixture generator built against the old adapter. Add wallet-store
contract checks and live funded-wallet upgrade coverage to catch compatibility
regressions beyond fresh-wallet tests.

Run the funded upgrade check in CI and document the fixture provenance,
upgrade procedure, and rollback limitations.
@thesimplekid thesimplekid changed the title Update cdk 0.18 Upgrade Cashu to CDK 0.18.2 with wallet migration and recovery Nov 2, 2026

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

Thanks, the upgrade tests are thorough. I checked the store against cdk-sql-common 0.18.2: the version checks on quotes and sagas, all-or-nothing reservation, release only touching RESERVED/PENDING, and the rekey all match upstream. The main concern is the startup recovery failure below.

Nits:

  • Why was update_proofs_cache_deduplicates_added_proofs_by_y removed? The dedup logic is still there and the test looks like it would still pass.
  • The generator builds a second dependency tree (cdk 0.16 plus an ldk-node git rev), which will add noticeable time to the Cashu CI job.


// Recover before exposing the wallet or starting tasks that create operations.
// This must run on every open, independently of the seed-restore flag.
let recovery = cashu_wallet.recover_incomplete_sagas().await.map_err(|e| {

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.

A failed recovery makes Cashu::init fail, which makes Wallet::new fail. Recovery can fail while the mint is unreachable. If a mint request hits a timeout or 5xx, CDK leaves the issue saga in MintRequested (issue/saga/mod.rs, the non-definitive branch runs no compensation); killing the app mid-receive does the same. On the next start, try_replay_mint falls back to /restore on a network error, and recover_outputs_from_blinded_messages returns Err for a non-definitive HTTP error, so the saga counts as failed. The wallet then won't open until the mint is back, which also blocks access to LDK funds.

The existing unavailability test only covers a melt saga, where the quote-state check skips first. Could failed recoveries be logged with startup continuing, since the proofs stay reserved? Could we also add an issue-saga-offline test?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Addressed in b1ca7a16. Individual recovery failures now log a warning and allow startup to continue, retaining reservations for retry on the next open. Database initialization errors and an unreadable recovery queue still fail startup.

Added cdk016_funded_wallet_opens_with_issue_saga_offline_then_recovers: it kills a funded 0.16 wallet after the mint processes issuance but before the response arrives, verifies startup succeeds while the mint returns HTTP 503 and recovery reports a failed issue saga, then reopens online and checks that all 64 sats and exactly one completed incoming transaction are recovered across repeated opens. This test and the five existing funded upgrade tests pass locally.

outgoing_quotes.insert(quote_id.clone(), selected.len());
}
}
// Reusable incoming quotes can represent multiple actual receipts.

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.

Minor: CDK 0.18 now writes a Pending incoming transaction before the mint request and marks it Failed on a definitive error, so non-completed incoming payments now reach list_payments. needs_trusted_rebalance in rebalancer.rs doesn't check payment.status, so it will record TxMetadata for them and can pick one as the rebalance trigger. Today Orange doesn't retry a quote after a failed mint, so I don't think a failed plus completed pair with the same payment ID can happen yet, but it would if quotes ever get retried. Worth having the rebalancer skip non-Completed payments?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Addressed in b1ca7a16. needs_trusted_rebalance now skips payments whose status is not TxStatus::Completed before reading or writing their metadata or considering them as a trigger. Outbound payments remain excluded.

&self, mint_url: Option<MintUrl>, unit: Option<CurrencyUnit>, state: Option<Vec<State>>,
spending_conditions: Option<Vec<SpendingConditions>>,
) -> Result<Vec<ProofInfo>, cdk::cdk_database::Error> {
let _lock = self.proofs_mutation_lock.lock().await;

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.

Optional: reads now take proofs_mutation_lock (here and in get_proofs_by_ys), so get_balance waits behind a full proof-set write. With VSS that's a remote round trip. Could reads take the lock only when proofs_cache_needs_reload is set?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Updated in b1ca7a16. Both read paths now use a shared helper that reads the cache directly when proofs_cache_needs_reload is clear and acquires the mutation mutex only when a reload may be needed. The regression test checks that proof lookups and balance reads complete while the mutation mutex is held and the cache is clean.

The flag remains set during persistence, so reads still wait for an in-flight remote write or an uncertain write to be resolved. This removes unconditional locking without changing that consistency guarantee.

Comment thread ci/test-cashu-upgrade.sh Outdated
else
source_path=orange-sdk/src/dyn_store.rs
fi
if [[ -d .jj ]]; then

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.

Nit: the jj branch is local tooling and shouldn't be in a CI script.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Removed in b1ca7a16. The CI script now always uses git show to extract the pinned old adapter. The funded upgrade harness passes with that path.

Log individual saga recovery failures and continue startup while retaining
reservations for the next recovery attempt. Keep unreadable recovery queues
fatal, and verify offline issue recovery with a funded CDK 0.16 wallet.

Exclude unfinished receipts from rebalance selection. Let clean proof-cache
reads bypass the mutation mutex and restore the deduplication regression test.

Use Git to extract the old adapter in CI and cache the standalone fixture
workspace dependencies.

Validation: 95 library tests and 6 funded upgrade tests passed; formatting
and shell syntax checks passed.
@thesimplekid

Copy link
Copy Markdown
Contributor Author

For the two nits in the review summary, b1ca7a16 restores update_proofs_cache_deduplicates_added_proofs_by_y, alongside the existing persistence/reopen deduplication test.

The Cashu CI cache now includes the standalone fixture generator workspace so its dependencies can be reused between runs. A cold build still compiles the second dependency tree; this preserves the tests against the actual 0.16 adapter. I have not measured the CI time savings.

The dependency bump is separate in 2fda04f8, targeting CDK 0.18.3. Local validation of the pushed tree passed: 95 library tests, all 6 funded upgrade tests, formatting, and shell syntax checks.

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

thanks!

@benthecarman
benthecarman merged commit 4acb3bc into lightningdevkit:master Nov 5, 2026
2 of 6 checks passed
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