Skip to content

selector!: Swap ChangePolicyType for bdk_coin_select::ChangePolicy#32

Merged
ValuedMammal merged 6 commits intobitcoindevkit:masterfrom
ValuedMammal:refactor/change_policy
Dec 17, 2025
Merged

selector!: Swap ChangePolicyType for bdk_coin_select::ChangePolicy#32
ValuedMammal merged 6 commits intobitcoindevkit:masterfrom
ValuedMammal:refactor/change_policy

Conversation

@ValuedMammal
Copy link
Copy Markdown
Collaborator

@ValuedMammal ValuedMammal commented Dec 10, 2025

Description

Fix #29 by removing ChangePolicyType from SelectorParams in favor of bdk_coin_select::ChangePolicy. The idea is that this affords maximum flexibility when defining the change policy.

Builds on #31 which implements absolute fee targeting. h/t aagbotemi. A test test_absolute_fee_vs_feerate_target_value is added that checks the expected Target is constructed based on the chosen FeeStrategy.

Changelog

Added

  • selector: Added FeeStrategy enum which defines the fee target as either a feerate or fee amount.

Changed

BREAKING

  • SelectorParams::target_feerate field is changed to fee_strategy.
  • SelectorParams::change_policy field is changed to have type bdk_coin_select::ChangePolicy.
  • SelectorParams::new is changed to accept the fee_strategy and change_policy as inputs.

Removed

  • SelectorParams::change_weight field is removed now that the change weights are represented in the actual change policy.
  • Removed SelectorParams::to_cs_change_policy as the conversion is no longer necessary.
  • Removed ChangePolicyType enum to allow the user to construct the intended ChangePolicy.

The spend weight now accounts for both
the weight of a default `TxIn` as well as the
expected satisfaction weight.
This is redundant now that SelectorParams holds the
ChangePolicy directly.
The name "strategy" is better suited for the enum, as it implies
different strategies can be used, and helps to disambiguate from
other similarly named types `Target` and `TargetFee`, etc.
@ValuedMammal ValuedMammal force-pushed the refactor/change_policy branch from 71169fd to b04db7e Compare December 15, 2025 18:34
@ValuedMammal ValuedMammal changed the title [selector] Refactor change policy selector!: Swap ChangePolicyType for bdk_coin_select::ChangePolicy Dec 15, 2025
@ValuedMammal
Copy link
Copy Markdown
Collaborator Author

Rebased on #31.

@ValuedMammal ValuedMammal marked this pull request as ready for review December 15, 2025 18:40
Copy link
Copy Markdown
Collaborator

@aagbotemi aagbotemi left a comment

Choose a reason for hiding this comment

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

Thank you for working on this PR. This approach is solid.

ACK b04db7e

@ValuedMammal ValuedMammal merged commit b8a7a82 into bitcoindevkit:master Dec 17, 2025
6 checks passed
@ValuedMammal ValuedMammal deleted the refactor/change_policy branch December 17, 2025 20:07
Comment thread src/selector.rs
Comment on lines +176 to +179
TargetFee {
rate: bdk_coin_select::FeeRate::ZERO,
replace: self.replace.as_ref().map(|r| r.to_cs_replace()),
}
Copy link
Copy Markdown
Member

@evanlinjin evanlinjin Feb 17, 2026

Choose a reason for hiding this comment

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

This looks like abusing the API. The underlying crate bdk_coin_select does not support coin selection purely with an absolute fee target.

This may result in unexpected behavior if paired with RBF. The Replace struct would still enforce incremental relay fee requirements, but the zero feerate means the selector wouldn't try to meet the original tx's feerate floor (feerate_lb is ignored). So you could end up with a replacement that satisfies BIP125 rule 4 (absolute fee) but not rule 6 (feerate), or vice versa.

If this feature is needed, we should upstream the changes.

ValuedMammal added a commit that referenced this pull request Apr 11, 2026
… SelectorParams

8189b03 refactor!: inline ChangePolicy fields into SelectorParams (志宇)
ffd1211 feat: add constructor methods for ChangeScript and ChangePolicy (志宇)
b9e7b42 refactor!: eliminate redundant satisfaction weight in SelectorParams (志宇)

Pull request description:

  Fixes #40
  Fixes #42

  ## Summary

  This PR fixes two issues with `SelectorParams`:

  **1. Redundant satisfaction weight (invalid representation)**

  `SelectorParams` previously had two independent fields that could both specify the change output's satisfaction weight:

  - `change_script: ScriptSource` — when this is the `Descriptor` variant, the satisfaction weight is derivable from the descriptor via `max_weight_to_satisfy`.
  - `change_policy: ChangePolicy` — also encodes the satisfaction weight (the spend cost of the change output).

  Nothing enforced that these agreed with each other, so callers could silently provide inconsistent values.

  Fixed by introducing **`ChangeScript`**, which bundles a raw script with an optional `satisfaction_weight`, or holds a `DefiniteDescriptor` from which it is derived automatically. `DrainWeights` is now computed internally from `ChangeScript` — there is a single source of truth.

  `ChangeScript::Descriptor` also accepts optional `satisfaction_assets` — when provided, the satisfaction weight is computed via `Plan` for a tighter estimate (useful for multisig/complex descriptors). Otherwise it falls back to `max_weight_to_satisfy`.

  The raw `bdk_coin_select::ChangePolicy` is also replaced with a **`ChangePolicy` enum** (`NoDust`, `NoDustLeastWaste`) that is converted to the `bdk_coin_select` type internally. This is `Debug + Clone + PartialEq + Eq`.

  Both `ChangeScript` and `ChangePolicy` have constructor methods to reduce boilerplate:
  - `ChangeScript::from_descriptor(desc)`, `from_descriptor_with_assets(desc, assets)`, `from_script(script, weight)`
  - `ChangePolicy::no_dust()`, `no_dust_least_waste(rate)`, with a builder-style `.min_value(amt)`

  **2. Hacky `AbsoluteFee` implementation**

  `FeeStrategy::AbsoluteFee` was implemented by smuggling the fee into `value_sum` and setting the feerate to zero. This meant the coin selector had no awareness of the fee, which interacted poorly with RBF logic (the zero feerate bypasses the original tx's feerate floor). Removed in favor of a direct `target_feerate: FeeRate` field.

  ### Design rationale

  The satisfaction weight is a physical property of the script — it describes how much it costs to spend a particular output. It is not a policy choice. When `change_script` is a descriptor, the satisfaction weight is literally derived from it. The fact that it *affects* coin selection doesn't make it *part of* the change policy, any more than the dust threshold is (and dust is already derived from the script).

  For the raw script case (e.g. silent payments), the satisfaction weight can't be derived and must be provided externally — but it's still a property of the script, not a policy decision. Bundling it into `ChangeScript::Script` makes this explicit.

  ## Context

  * #18 (comment)
  * #32 (comment)

ACKs for top commit:
  aagbotemi:
    ACK 8189b03
  ValuedMammal:
    ACK 8189b03

Tree-SHA512: c3bd9af598ec55c60ab04656d7907ec3882bd50bbd0783add94057bd2603bc1d0cbe134a3a2d601988e73c57d4c83c91538060e4091e3a150005489c293d9b21
@ValuedMammal ValuedMammal mentioned this pull request Apr 29, 2026
21 tasks
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.

Make ChangePolicyType more flexible

3 participants