Skip to content

fix(afs)!: preserve input timelock requirements#65

Merged
evanlinjin merged 2 commits into
bitcoindevkit:masterfrom
aagbotemi:fix/afs-locktime
May 16, 2026
Merged

fix(afs)!: preserve input timelock requirements#65
evanlinjin merged 2 commits into
bitcoindevkit:masterfrom
aagbotemi:fix/afs-locktime

Conversation

@aagbotemi
Copy link
Copy Markdown
Collaborator

@aagbotemi aagbotemi commented May 12, 2026

Description

Fixes consensus-validity issues in BIP-326 anti-fee-sniping.

Closes #62
Closes #63
Closes #64

Bugs

  • The nLockTime strategy could overwrite tx.lock_time with a value lower than the accumulated input CLTV.
  • The nSequence strategy zeroed tx.lock_time, also breaking accumulated CLTV.
  • The nSequence strategy could select a Taproot input that already had a relative-timelock (CSV) requirement and overwrite its sequence.

Fixes

  • The locktime path only updates tx.lock_time when the AFS-proposed value still satisfies the existing requirement.
  • The sequence path leaves tx.lock_time alone and filters CSV-bearing Taproot inputs out of the candidate pool.

Breaking changes

  • PsbtParams::enable_anti_fee_sniping: bool + fallback_locktimeanti_fee_sniping: Option<absolute::Height> + min_locktime. The tip height is now required when AFS is enabled, and time-based tips are unrepresentable.
  • apply_anti_fee_sniping is now pub(crate); rbf_enabled param removed (derived from tx.is_explicitly_rbf()).
  • New AntiFeeSnipingError type, wrapped under CreatePsbtError::AntiFeeSniping.

Copy link
Copy Markdown
Member

@evanlinjin evanlinjin left a comment

Choose a reason for hiding this comment

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

Thanks for working on this.

PR description is misleading - none of these bugs were flagged in the post-merge review. It also says this "fixes two consensus-validity issues", however this PR attempts to close 3 issues?

Comment thread src/utils.rs Outdated
Comment thread src/selection.rs
Comment thread src/utils.rs Outdated
Comment thread src/utils.rs
Copy link
Copy Markdown
Member

@evanlinjin evanlinjin left a comment

Choose a reason for hiding this comment

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

There are two versions of the random_probability and random_range methods. The std versions add no meaningful value imo - I would just remove them.

@aagbotemi
Copy link
Copy Markdown
Collaborator Author

Thank you for the review

@aagbotemi aagbotemi requested a review from evanlinjin May 13, 2026 13:29
evanlinjin

This comment was marked as outdated.

Comment thread src/utils.rs Outdated
@aagbotemi aagbotemi force-pushed the fix/afs-locktime branch 2 times, most recently from f3d7e60 to 3b72e6d Compare May 14, 2026 09:59
@aagbotemi aagbotemi requested a review from evanlinjin May 14, 2026 10:02
Comment thread src/selection.rs
@aagbotemi aagbotemi force-pushed the fix/afs-locktime branch 2 times, most recently from 3b72e6d to b0b0eb1 Compare May 14, 2026 13:56
Copy link
Copy Markdown
Member

@evanlinjin evanlinjin left a comment

Choose a reason for hiding this comment

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

Make apply_anti_fee_sniping explicitly private!

Comment thread src/utils.rs Outdated
Comment thread src/utils.rs Outdated
Comment thread src/utils.rs Outdated
Comment thread src/utils.rs Outdated
Comment thread src/utils.rs Outdated
Comment thread src/utils.rs Outdated
@aagbotemi aagbotemi force-pushed the fix/afs-locktime branch 3 times, most recently from 15943a8 to adf472f Compare May 14, 2026 15:35
Comment thread examples/anti_fee_sniping.rs
@evanlinjin
Copy link
Copy Markdown
Member

We are lacking commit messages, PR description has factual errors, missing ! to signal breaking change.

I'll push it forward!

@evanlinjin evanlinjin changed the title fix(afs): preserve accumulated locktime fix(afs)!: preserve input timelock requirements May 15, 2026
@evanlinjin evanlinjin force-pushed the fix/afs-locktime branch 2 times, most recently from b8671c3 to c83079a Compare May 15, 2026 18:59
@evanlinjin evanlinjin requested a review from nymius May 15, 2026 20:01
@evanlinjin
Copy link
Copy Markdown
Member

@aagbotemi I pushed a Claude-generated test commit on top, let me know what you think.

Comment thread src/selection.rs Outdated
@evanlinjin evanlinjin force-pushed the fix/afs-locktime branch 3 times, most recently from 059752f to 2fcd3c5 Compare May 16, 2026 12:31
aagbotemi and others added 2 commits May 16, 2026 12:41
Anti-fee-sniping could produce consensus-invalid transactions when
inputs had timelock requirements:

- The nLockTime strategy could overwrite `tx.lock_time` with a value
  lower than the accumulated input CLTV.
- The nSequence strategy zeroed `tx.lock_time`, also breaking
  accumulated CLTV.
- The nSequence strategy could select a Taproot input that already
  had a relative-timelock (CSV) requirement and overwrite its
  sequence.

The locktime path now only updates `tx.lock_time` when the proposed
AFS value still satisfies the existing requirement. The sequence
path leaves `tx.lock_time` alone and filters CSV-bearing Taproot
inputs out of the candidate pool.

API changes:

- `PsbtParams::enable_anti_fee_sniping: bool` and `fallback_locktime`
  are replaced with `anti_fee_sniping: Option<absolute::Height>` and
  `min_locktime`. The tip height is now required when AFS is enabled
  and time-based tips are unrepresentable.
- `apply_anti_fee_sniping` is now `pub(crate)`; the `rbf_enabled`
  parameter is removed (derived from `tx.is_explicitly_rbf()`).
- New `AntiFeeSnipingError` type, wrapped under
  `CreatePsbtError::AntiFeeSniping`.

Closes bitcoindevkit#62
Closes bitcoindevkit#63
Closes bitcoindevkit#64
Three new tests, each verified to fail on the pre-fix algorithm:

- `test_anti_fee_sniping_preserves_input_cltv` — input CLTV above tip
  must not be lowered.
- `test_anti_fee_sniping_skips_taproot_csv_input` — a Taproot input
  carrying a CSV requirement must be excluded from the nSequence
  candidate pool; a regular Taproot input remains to ensure the
  sequence path is still reachable.
- `test_anti_fee_sniping_rejects_time_based_locktime` — a time-based
  CLTV must surface `AntiFeeSnipingError::UnsupportedLockTime`.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Copy link
Copy Markdown
Member

@evanlinjin evanlinjin left a comment

Choose a reason for hiding this comment

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

ACK 5df9bb1

@evanlinjin evanlinjin merged commit 8b51d07 into bitcoindevkit:master May 16, 2026
7 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

2 participants