Skip to content

fix(wallet): don't panic in create_tx when a descriptor has no policy - #583

Open
artofbitcoin wants to merge 2 commits into
bitcoindevkit:masterfrom
artofbitcoin:fix/create-tx-raw-pkh-policy-panic
Open

artofbitcoin wants to merge 2 commits into
bitcoindevkit:masterfrom
artofbitcoin:fix/create-tx-raw-pkh-policy-panic

Conversation

@artofbitcoin

Copy link
Copy Markdown

Description

Fixes #579

When TxParams::condition is not set, create_tx derives the spending condition from the
policies of the wallet descriptors and called .unwrap() on the Option<Policy> returned by
extract_policy (once for the external descriptor, once for the internal one). Policy
extraction returns None for descriptors without a policy representation, e.g.
c:expr_raw_pkh(<hash160>) and sh(c:expr_raw_pkh(<hash160>)). Such descriptors are accepted
when creating a wallet, so every build_tx().finish() on them panicked.

This PR treats a missing policy as contributing no requirements (Condition::default()) for
that keychain instead of unwrapping. Both the external and the internal (change) descriptor
paths are covered.

Notes to the reviewers

The issue suggests returning an error (e.g. a CreateTxError). I went with "no policy means
no requirements" instead, for these reasons:

  • CreateTxError is a public enum and not #[non_exhaustive], so a new variant would be a
    breaking change. None of the existing variants describes this case well
    (SpendingPolicyRequired means a policy path is missing, Policy(PolicyError) has no
    fitting PolicyError variant, and PolicyError is not #[non_exhaustive] either).
  • The policy is only used here to derive a Condition (csv / timelock). A top-level None
    is only produced when no fragment of the descriptor has a policy representation, so there is
    no policy path to select and no timelock to derive. Condition::default() is also what a
    caller would pass through TxBuilder::set_condition for such a descriptor, in which case
    the policy is not extracted at all.
  • The rest of create_tx does not depend on the policy. With the fix, a funded raw-pkh wallet
    produces a PSBT (version 2, default sequence) rather than failing somewhere later.

If you prefer an explicit error for descriptors without a policy, I am happy to change this to
a new CreateTxError variant; that would need to be marked as breaking.

Not addressed here: extract_policy silently drops raw-pkh fragments inside larger
miniscripts (make_and/make_or/thresh skip None children). That is existing behaviour
and unrelated to the panic.

The branch has two commits (fix, then tests) because I committed through the GitHub web editor.

Commands run locally (Rust stable 1.97.0, since the pinned 1.96.0 toolchain and nightly were
not available to me), following the justfile pre-push recipe:

  • New tests on master before the fix: 4 failed, all with
    called Option::unwrap() on a None value at src/wallet/mod.rs:1300 (external) and
    src/wallet/mod.rs:1309 (internal).
  • cargo fmt --all -- --check (stable rustfmt): no diff.
  • cargo check --all-targets --no-default-features --features miniscript/no-std,bdk_chain/hashbrown,
    with and without --cfg bdk_wallet_unstable: ok.
  • cargo check --all-targets --features std,compiler,all-keys,rusqlite,file_store,test-utils: ok.
  • RUSTFLAGS="--cfg bdk_wallet_unstable" cargo check --all-targets --all-features: ok.
  • cargo clippy --all-targets with -D warnings (both feature sets): fails on 1.97.0 with
    clippy::needless_return_with_question_mark in code not touched by this PR
    (src/descriptor/policy.rs:707,730, src/wallet/signer.rs:340,465,468, and
    src/wallet/mod.rs:3646 in the unstable build). With that one lint allowed, clippy passes
    for both feature sets.
  • RUSTFLAGS="" cargo test --workspace --features std,compiler,all-keys,rusqlite,file_store,test-utils:
    422 passed, 0 failed, 3 ignored (tests/wallet.rs: 131 passed, including the 4 new tests).
  • RUSTFLAGS="--cfg bdk_wallet_unstable" RUSTDOCFLAGS="--cfg bdk_wallet_unstable" cargo test --workspace --all-features:
    450 passed, 0 failed, 3 ignored.
  • RUSTDOCFLAGS="-D warnings --cfg bdk_wallet_unstable" cargo doc --workspace --all-features --no-deps: ok.

Not run: cargo +nightly fmt (stable rustfmt ignores the nightly-only options in
rustfmt.toml), anything on the pinned 1.96.0 toolchain or on the MSRV (1.85.0).

Changelog notice

Fixed

  • fix(wallet): TxBuilder::finish no longer panics for descriptors without an extractable policy, such as c:expr_raw_pkh(..)

Before submitting

I prepared this change with the help of an AI assistant and reviewed it; the test results above come from actual runs.

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

Labels

None yet

Projects

Status: Triage

Development

Successfully merging this pull request may close these issues.

create_tx panics unwrapping a None policy for raw-pkh (c:expr_raw_pkh) descriptors

1 participant