Skip to content

Add CPFP Support#370

Open
aagbotemi wants to merge 11 commits intobitcoindevkit:masterfrom
aagbotemi:feature/cpfp_support
Open

Add CPFP Support#370
aagbotemi wants to merge 11 commits intobitcoindevkit:masterfrom
aagbotemi:feature/cpfp_support

Conversation

@aagbotemi
Copy link

Description

This PR adds support for creating Child-Pays-For-Parent (CPFP) transactions to accelerate unconfirmed parent transactions by creating a child transaction that spends from an unconfirmed parent output with a higher fee rate.

Notes to the reviewers

Changelog notice

Added

  • CreateSweepError enum
  • New public wallet APIs:
    • Wallet::create_sweep
    • Wallet::create_sweep_with_rng
  • Documentation and example:
    • examples/cpfp.rs

Rebased on #297

Checklists

All Submissions:

New Features:

  • I've added tests for the new feature
  • I've added docs for the new feature

Bugfixes:

  • This pull request breaks the existing API
  • I've added tests to reproduce the issue which are now passing
  • I'm linking the issue being fixed by this PR

ValuedMammal and others added 11 commits January 14, 2026 12:21
`TxOrdering` is made generic by exposing the generic from
`TxSort` function. The benefit is that we're no longer limited
to ordering lists of only `TxIn` or `TxOut`.

We use bitcoin `TxIn` and `TxOut` as the default type parameter
to maintain backward compatibility.

Add `sort_with_rng` for `TxOrdering<In, Out>` for sorting
two generic mutable slices.

This will be useful in a later commit by applying a
`TxOrdering` to the Input/Output sets of a `Selection`.
We add the `psbt::params` module along with new types
including `PsbtParams` and `SelectionStrategy`.

`PsbtParams` is mostly inspired by `TxParams` from `tx_builder.rs`,
except that we've removed support for `policy_path` in favor of
`add_assets` API. Further enhancements include `.utxo_filter` and
`.canonical_params` member fields.

`PsbtParams<C>` contains a type parameter `C` indicating the context
in which the parameters can be used. Methods related to PSBT
creation exist within the `CreateTx` context, and methods
related to replacements (RBF) exist within the `ReplaceTx`
context.

In `lib.rs` re-export everything under `psbt` module.

- deps: Add `bdk_tx` 0.1.0
- deps: Add `bdk_coin_select` 0.4.1
We use the new `PsbtParams` to add methods on `Wallet` for
creating PSBTs, including RBF transactions.

`Wallet::create_psbt` and `Wallet::replace_by_fee` each have
no-std counterparts that take an additional `impl RngCore` parameter.

Also adds a high level convenience method
`Wallet::replace_by_fee_and_recipients` that exposes the minimum
information needed to create an RBF.

This commit re-introduces the `Wallet::insert_tx` API for adding
transaction data to the wallet that may or may not be canonical from
the point of view of the TxGraph.

Added `Wallet::transactions_with_params` that allows customizing
the internal canonicalization logic.

Added new errors to `wallet::errors`

- `CreatePsbtError`
- `ReplaceByFeeError`
Adds additional tests to exercise `PsbtParams` logic and
PSBT creation.

- test-utils: Add `insert_tx_anchor` test helper for adding
a transaction to the wallet with associated anchor block.
- `examples/psbt.rs`
- `examples/rbf.rs`
Made a number of API changes to `PsbtParams`

- Add `PsbtParams::sighash_type`
- psbt: Change `change_descriptor` to `change_script`
- Add `PsbtParams::change_policy`
- psbt: Change `feerate` to `fee` using `FeeStrategy`
- Remove `longterm_feerate`

TODO:
- This should be updated to depend on `bdk_tx` version 0.2.0
which is still unreleased.
@codecov
Copy link

codecov bot commented Jan 19, 2026

Codecov Report

❌ Patch coverage is 89.56434% with 103 lines in your changes missing coverage. Please review.
✅ Project coverage is 86.22%. Comparing base (8f8a8e9) to head (f333428).

Files with missing lines Patch % Lines
src/wallet/error.rs 0.00% 48 Missing ⚠️
src/wallet/mod.rs 95.42% 28 Missing ⚠️
src/psbt/params.rs 92.61% 22 Missing ⚠️
src/wallet/tx_builder.rs 58.33% 5 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##           master     #370      +/-   ##
==========================================
+ Coverage   85.33%   86.22%   +0.88%     
==========================================
  Files          24       25       +1     
  Lines        8335     9318     +983     
==========================================
+ Hits         7113     8034     +921     
- Misses       1222     1284      +62     
Flag Coverage Δ
rust 86.22% <89.56%> (+0.88%) ⬆️

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

☔ View full report in Codecov by Sentry.
📢 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.


// Create psbt
let mut psbt = selection
.create_psbt(bdk_tx::PsbtParams {
Copy link
Contributor

Choose a reason for hiding this comment

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

Hello, I am getting an error on this line.

error message:

missing field `enable_anti_fee_sniping` in initializer of `bdk_tx::PsbtParams`
missing `enable_anti_fee_sniping`

I was able to clear the error by adding enable_anti_fee_sniping: true, to the existing fields.

Copy link
Author

Choose a reason for hiding this comment

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

Thank you for pointing that out. We cannot outrightly set it to true, it's okay to give users the liberty to set the desired behaviour.
A dependency (bdk-tx) was updated after this PR was created, which introduced the new enable_anti_fee_sniping field, hence the build error.
It will be added as a configurable option in the PsbtParams struct.

Copy link
Contributor

Choose a reason for hiding this comment

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

Oh that's great!

@ValuedMammal
Copy link
Collaborator

Thank you @aagbotemi I can see this being useful for CPFP especially when sweeping an unconfirmed output to new destination. After studying the code these are my first impressions

  • Ideally we want to get the satisfaction weight of the child input from the Plan instead of using the max_weight_to_satisfy, but we're limited by the current interface.
  • It may have been mentioned elsewhere, but because of the candidate merging approach, the implementation would need to control for changes in input/output counts that could affect the compact size encoding of the vin/vout vectors, sometimes referred to as varint, and hence alter the perceived weight of the selection. Granted this is only a technicality and not really a deal breaker.

Put together, I think the ideal solution long-term is to integrate ancestor-aware fee targeting more fully into the logic of create_psbt by automatically accounting for unconfirmed parents during coin selection, or perhaps with a new PsbtParams option that will reinterpret the target feerate as a "package feerate".

cc @notmandatory as discussed

@aagbotemi
Copy link
Author

Thanks for the review. You point regarding the max_weight_to_satisfy overestimation and the VarInt edge cases caused by candidate merging are spot on.

After studying the code these are my first impressions

Ideally we want to get the satisfaction weight of the child input from the Plan instead of using the max_weight_to_satisfy, but we're limited by the current interface.
It may have been mentioned elsewhere, but because of the candidate merging approach, the implementation would need to control for changes in input/output counts that could affect the compact size encoding of the vin/vout vectors, sometimes referred to as varint, and hence alter the perceived weight of the selection. Granted this is only a technicality and not really a deal breaker.

I have refactored the implementation locally to integrate more cleanly into create_psbt. I've dropped the manual candidate merging in favor of a Package { parent_fee, parent_weight } struct within bdk-coin-select and PsbtParams.

Now, create_sweep will simply configure PsbtParams and passes them to create_psbt. This natively fixes the Plan vs max_weight issue (as create_psbt uses plan_input) and bypasses the VarInt bug entirely because we are no longer merging input counts.

I’ll push the refactored code once bdk_coin_select #38 is merged.

Put together, I think the ideal solution long-term is to integrate ancestor-aware fee targeting more fully into the logic of create_psbt by automatically accounting for unconfirmed parents during coin selection, or perhaps with a new PsbtParams option that will reinterpret the target feerate as a "package feerate".

For the long term, I plan to integrate ancestor-awareness directly into the create_psbt flow. I plan to use TxGraph and CanonicalView to automatically compute ancestor bump fees, allowing the CoinSelector to account for unconfirmed parents during any standard transaction creation. Reference: bitcoin/bitcoin#26152

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

Labels

new feature New feature or request

Projects

Status: In Progress

Development

Successfully merging this pull request may close these issues.

3 participants