Skip to content

Add ancestor-aware coin selection for CPFP bump fee calculation#43

Draft
evanlinjin wants to merge 1 commit intobitcoindevkit:masterfrom
evanlinjin:feature/ancestor-aware-cs
Draft

Add ancestor-aware coin selection for CPFP bump fee calculation#43
evanlinjin wants to merge 1 commit intobitcoindevkit:masterfrom
evanlinjin:feature/ancestor-aware-cs

Conversation

@evanlinjin
Copy link
Copy Markdown
Member

Summary

When spending unconfirmed UTXOs, miners evaluate the transaction as a package with its unconfirmed ancestors. If ancestors paid below the target feerate, the child must overpay (CPFP). This PR adds support for computing that bump fee during coin selection, with automatic deduplication when multiple candidates share ancestors.

Closes #24. Competes with #38 and #42.

Design

Each ancestor is tracked individually via UnconfirmedAncestor { weight, fee_paid }. Candidates declare their ancestry through ancestors: &[usize] — indices into a shared ancestor slice passed to CoinSelector::with_ancestors(). This keeps Candidate Copy (a slice reference is Copy).

The bump fee is computed at the package level: collect unique ancestor indices across selected candidates, sum their weights and fees, then compute max(0, implied_fee(total_weight, feerate) - total_fees). This is more accurate than per-ancestor computation because high-feerate ancestors subsidize low-feerate ones within the package (matching Bitcoin Core's package relay approach from PR #27021).

Comparison with alternative approaches

#42 (ancestor_bump_fee field on Candidate) pre-computes the bump fee per candidate. This is simple but has two drawbacks:

  1. No deduplication — when two candidates share an ancestor, the bump fee is double-counted. Add ancestor_bump_fee to Candidate #42 acknowledges this and defers deduplication to the wallet layer, which means coin selection overpays (conservative but suboptimal).
  2. Fixed feerate — the bump fee is baked in at construction time. If the selector needs to evaluate candidates at different feerates (e.g. during branch-and-bound), the pre-computed value is wrong for all but the original feerate.

This PR avoids both issues by storing raw ancestor data (weight + fee_paid) and computing the bump fee lazily at the selection level, where the full set of selected candidates and the current feerate are known.

#38 (Package struct) uses a single aggregate Package { parent_fee, parent_weight } blob. That works for the single-parent case but breaks down when candidates have different ancestors — there's no way to deduplicate shared ancestors as the selection changes. The per-ancestor model handles this naturally:

  Ancestor A (400 wu, 10 sat fee)
    ├── Candidate 0 depends on [A]
    └── Candidate 1 depends on [A]

Selecting both candidates → ancestor A counted once, not twice.

Where the bump fee lives

Rather than modifying weight() or fee() (which would require _without_package escape hatches for RBF), the bump fee is a separate term subtracted in the excess calculations. This keeps the core weight/fee accounting clean and avoids special-casing RBF rule 4.

API changes

  • UnconfirmedAncestor — new struct: { weight: u64, fee_paid: u64 }
  • Candidate — gains ancestors: &'a [usize] field (set to &[] for confirmed UTXOs). This adds a lifetime parameter to Candidate<'a>.
  • CoinSelector::with_ancestors(ancestors) -> Self — builder method, backward compatible
  • CoinSelector::selected_ancestor_bump_fee(feerate) -> u64 — package-level bump fee with deduplication
  • rate_excess, rate_excess_wu, replacement_excess, replacement_excess_wu, effective_value — now subtract the ancestor bump fee

Existing code only needs to add ancestors: &[] to Candidate struct literals.

When spending unconfirmed UTXOs, miners evaluate the transaction as a
package with its ancestors. This adds support for computing the
package-level bump fee needed to bring ancestors up to the target
feerate, with automatic deduplication of shared ancestors across
candidates.

- Add UnconfirmedAncestor struct (weight + fee_paid)
- Add ancestors field to Candidate (indices into shared ancestor slice)
- Add with_ancestors() builder on CoinSelector
- Add selected_ancestor_bump_fee() with package-level computation
- Subtract ancestor bump fee from excess and effective_value methods

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@evanlinjin evanlinjin force-pushed the feature/ancestor-aware-cs branch from d168359 to ce09897 Compare April 24, 2026 09:13
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.

CPFP: Allow telling coin selector that a candidate has unconfirmed parents

1 participant