refactor!: eliminate redundant satisfaction weight in SelectorParams#39
refactor!: eliminate redundant satisfaction weight in SelectorParams#39evanlinjin wants to merge 2 commits intobitcoindevkit:masterfrom
Conversation
Previously `SelectorParams` had both `change_script: ScriptSource` and `change_weight: DrainWeights`, which could disagree. Replace these with `ChangeScript` (which bundles the script with its satisfaction weight) and `ChangePolicy` (an enum instead of raw `bdk_coin_select::ChangePolicy`). `DrainWeights` is now derived internally from `ChangeScript`. Also removes `FeeStrategy` in favor of a direct `target_feerate` field, as the `AbsoluteFee` variant had a hacky implementation that conflicted with RBF logic.
Add ChangeScript::from_descriptor, from_descriptor_with_assets, and from_script constructors to reduce boilerplate. Add ChangePolicy::no_dust and no_dust_least_waste constructors with a builder-style min_value method. Also adds satisfaction_assets field to ChangeScript::Descriptor for tighter weight estimates via Plan, and folds satisfaction weight errors into SelectorError.
e0a17bc to
ffd1211
Compare
aagbotemi
left a comment
There was a problem hiding this comment.
Since the previous implementation for AbsoluteFee was broken for RBF, removing it is correct. Targeting absolute fee is a legitimate use case and the open PR on bdk_coin_select #38 adding TargetFee::absolute as a field addresses this. Once that merges, SelectorParams and to_cs_target() should be updated by exposing and mapping onto it.
nymius
left a comment
There was a problem hiding this comment.
cACK ffd1211
This is similar to #18 original's idea.
I have to review closer the RBF considerations, but I understand the removal of FeeStrategy as it is mainly needed for the absolute fee case, and it can be covered in other better ways.
I agree that ChangePolicy is a decision based on constraints, and the weight derived from ChangeScript is a property, so I don't see any concern raising from clearly reflecting that in the API.
| /// Either target a specific feerate or an absolute fee. | ||
| pub fee_strategy: FeeStrategy, | ||
| /// The actual feerate of the resulting transaction may be higher due to RBF requirements or | ||
| /// rounding. |
There was a problem hiding this comment.
Can't this be considered external to the target feerate?
When including the target feerate, can't the feerate be computed externally based on RBF rules?
There was a problem hiding this comment.
target_feerate is a lower bound, not an exact target. The actual feerate can be higher for multiple reasons — RBF feerate floor is one, but coin selection also raises it when dropping the change output reduces waste. The doc comment is just stating this fact.
Making the caller compute the RBF-adjusted feerate externally would just push complexity outward for no gain — the library already has RbfParams with the original tx feerates, so it can enforce the floor automatically.
|
Appreciate your efforts, I'd just ask that you stick to the contribution guidelines in particular with regard to the contribution workflow.
|
Added: #40
These changes can't be split — they're one refactor of
Splitting would create intermediate states where the API is half-migrated.
You can't really "test" that two fields can't disagree anymore - the fix is that the problematic API shape no longer exists. The compiler enforces it. |
Fixes #40
Summary
This PR fixes two issues with
SelectorParams:1. Redundant satisfaction weight (invalid representation)
SelectorParamspreviously had two independent fields that could both specify the change output's satisfaction weight:change_script: ScriptSource— when this is theDescriptorvariant, the satisfaction weight is derivable from the descriptor viamax_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 optionalsatisfaction_weight, or holds aDefiniteDescriptorfrom which it is derived automatically.DrainWeightsis now computed internally fromChangeScript— there is a single source of truth.ChangeScript::Descriptoralso accepts optionalsatisfaction_assets— when provided, the satisfaction weight is computed viaPlanfor a tighter estimate (useful for multisig/complex descriptors). Otherwise it falls back tomax_weight_to_satisfy.The raw
bdk_coin_select::ChangePolicyis also replaced with aChangePolicyenum (NoDust,NoDustLeastWaste) that is converted to thebdk_coin_selecttype internally. This isDebug + Clone + PartialEq + Eq.Both
ChangeScriptandChangePolicyhave 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
AbsoluteFeeimplementationFeeStrategy::AbsoluteFeewas implemented by smuggling the fee intovalue_sumand 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 directtarget_feerate: FeeRatefield.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_scriptis 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::Scriptmakes this explicit.Context
ChangePolicyTypeforbdk_coin_select::ChangePolicy#32 (comment)