Skip to content

Conversation

@aagbotemi
Copy link
Collaborator

@aagbotemi aagbotemi commented May 6, 2025

This PR implements Anti-Fee-Sniping with randomization as discussed in issue #4

Notes to the reviewers

The implementation adds randomization to anti-fee-sniping behavior:

  1. Uses a 50/50 chance to choose between nLockTime and nSequence (when possible)
  2. Adds a 10% chance to set either value further back in time (by a random value between 0-99)
  3. Detects taproot inputs and their confirmation status
  • I've signed all my commits
  • I ran cargo fmt and cargo clippy before committing
  • I've added docs for the new feature

Closes #4

@aagbotemi aagbotemi force-pushed the feature/anti-fee-sniping branch 2 times, most recently from 6a39ba9 to 94237e8 Compare May 19, 2025 02:58
@ValuedMammal
Copy link
Collaborator

Thanks @aagbotemi I'm still in the process of reviewing. I agree it would be nice to resolve the clippy error - my other suggestion would be to globally allow the lints in lib.rs for now and open an issue to fix them in a future PR, but in general I agree with your approach to simply Box the large enum variants.

@aagbotemi
Copy link
Collaborator Author

Thanks @aagbotemi I'm still in the process of reviewing.

Thank you @ValuedMammal. Clippy error and large enum variant error in the CI build has been fixed. For subsequent ones(if there is), I'll open another for it.

Copy link
Collaborator

@ValuedMammal ValuedMammal left a comment

Choose a reason for hiding this comment

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

I have some review notes

  • For imports, prefer alloc over std. This way we can actually support no-std.
  • I want to see a test of some kind that creates a PSBT with enable_anti_fee_sniping and checks the expected values of the locktime and/or sequence.

@notmandatory notmandatory added the enhancement New feature or request label May 28, 2025
@aagbotemi
Copy link
Collaborator Author

I have some review notes

Noted.
alloc is now preferred over std.
adding test to check enable_anti_fee_sniping

@aagbotemi aagbotemi force-pushed the feature/anti-fee-sniping branch 2 times, most recently from 42fe314 to cfe7b5b Compare May 29, 2025 23:21
@aagbotemi aagbotemi force-pushed the feature/anti-fee-sniping branch from cfe7b5b to 9041b2f Compare May 30, 2025 22:33
@ValuedMammal
Copy link
Collaborator

We should also be able to remove the clippy allow attributes in lib.rs.

@aagbotemi
Copy link
Collaborator Author

We should also be able to remove the clippy allow attributes in lib.rs.

Alright, I will remove the clippy allow attributes in lib.rs

@ValuedMammal
Copy link
Collaborator

I'm still in favor of using Box on PlanOrPsbtInput and ScriptSource::Descriptor.

@aagbotemi
Copy link
Collaborator Author

I'm still in favor of using Box on PlanOrPsbtInput and ScriptSource::Descriptor.

Fixed! I've updated the code to use Box.

@aagbotemi aagbotemi force-pushed the feature/anti-fee-sniping branch from 5506018 to 9fde5c1 Compare September 9, 2025 03:39
@aagbotemi aagbotemi force-pushed the feature/anti-fee-sniping branch from ff19f53 to c4dd696 Compare October 20, 2025 14:30
@ValuedMammal
Copy link
Collaborator

In general I think the git history would be cleaner if we didn't merge with merge commits but instead rebase the commits in this PR.

@aagbotemi aagbotemi force-pushed the feature/anti-fee-sniping branch 2 times, most recently from cfa9826 to 4649e8d Compare October 26, 2025 21:56
@aagbotemi
Copy link
Collaborator Author

aagbotemi commented Oct 26, 2025

In general I think the git history would be cleaner if we didn't merge with merge commits but instead rebase the commits in this PR.

Alright, I've done the cleanup

@ValuedMammal
Copy link
Collaborator

Approach ACK.

@ValuedMammal ValuedMammal requested a review from nymius October 28, 2025 20:09
Ok(())
}

/// Returns true with probability 1/n.
Copy link
Contributor

Choose a reason for hiding this comment

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

I would add a comment explaining why we are using these functions and not rand::random_bool.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

The anti fee sniping implementation started from here. So, I have been using rand_core for the implementation since then. I can change to rand if we agree to use that.

cc: @ValuedMammal, we have access to using Rng trait using rand

src/utils.rs Outdated
}

/// Returns a random value in the range [0, end).
fn random_range(rng: &mut OsRng, end: u32) -> u32 {
Copy link
Contributor

Choose a reason for hiding this comment

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

Same here for rand::random_range. Also, I would mention here we are using unbiased methods to pick from the range.

@aagbotemi aagbotemi force-pushed the feature/anti-fee-sniping branch from 782c3e3 to 0923128 Compare November 6, 2025 18:12
@aagbotemi aagbotemi requested a review from nymius November 6, 2025 23:33
src/utils.rs Outdated
Comment on lines 131 to 132
// if random_probability(TEN_PERCENT_PROBABILITY_RANGE) {
// let random_offset = random_range(MAX_RANDOM_OFFSET);
Copy link
Contributor

Choose a reason for hiding this comment

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

nit: remove these comments.

src/utils.rs Outdated
|| must_use_locktime
|| taproot_inputs.is_empty()
|| random_bool(FIFTY_PERCENT_PROBABILITY_RANGE);
// || random_probability(FIFTY_PERCENT_PROBABILITY_RANGE);
Copy link
Contributor

Choose a reason for hiding this comment

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

nit: remove this comment

// - RBF is disabled, OR
// - Any input requires locktime (non-taproot, unconfirmed, or >65535 confirmations), OR
// - There are no taproot inputs, OR
// - Random 50/50 coin flip chose locktime
Copy link
Contributor

Choose a reason for hiding this comment

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

To make the example more dynamic I would set up all the conditions to make the example dependent of this 50/50 coin flip. Because I'm never hitting the sequence anti fee sniping branch below.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Okay. I have fixed it.

if height_val >= min_expected && height_val <= max_expected {
println!("✓ Locktime is within expected range");
} else {
println!("⚠ Locktime is outside expected range");
Copy link
Contributor

Choose a reason for hiding this comment

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

Is this line reachable? I guess you want to show the feature is working, but if anti fee sniping works as intended, this is not going to ever happen. This isn't a test. Maybe showing the nLockTime or nSequence in a meaningful way is a better approach for the example.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Thank you for the review. I have fixed it.

@aagbotemi aagbotemi force-pushed the feature/anti-fee-sniping branch from 0923128 to ccc615c Compare November 11, 2025 22:48

println!("nLockTime approach used: {} times", locktime_count);
println!("nSequence approach used: {} times", sequence_count);
println!("Both approaches provide anti-fee-sniping protection:");
Copy link
Contributor

Choose a reason for hiding this comment

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

nit: this isn't providing new information, I recommend to remove it

Copy link
Contributor

Choose a reason for hiding this comment

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

@aagbotemi sorry for the confusion here, but I was just referring to line 149 here, 147 and 148 were good

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Alright.

@aagbotemi aagbotemi force-pushed the feature/anti-fee-sniping branch 2 times, most recently from 15d95a2 to 74d705d Compare November 12, 2025 07:55

[dependencies]
miniscript = { version = "12", default-features = false }
bdk_coin_select = "0.4.0"
Copy link
Contributor

Choose a reason for hiding this comment

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

@ValuedMammal what do you think about rand as additional dependency? I don't want to fall in the slippery slope of adding new dependencies with each feature, but I wouldn't implement anti-fee sniping without rand, so, is anti-fee sniping something bdk-tx should ship by default? Should we consider adding it as a feature dependency?

Copy link
Collaborator

Choose a reason for hiding this comment

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

Ideally we want to support no-std, so my question is how are we providing a source of randomness for no-std environments.

Copy link
Collaborator

Choose a reason for hiding this comment

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

The approach we took for bdk_wallet is to depend on rand_core in order to have RngCore trait and essentially split the interface based on whether std feature is enabled. If std, then we have access to rand via bitcoin::secp256k1::rand. If no-std then the user can pass in something that implements RngCore.

/// Create PSBT.
#[cfg(feature = "std")]
pub fn create_psbt(&self, params: PsbtParams) -> Result<bitcoin::Psbt, CreatePsbtError> {
    self.create_psbt_with_rng(params, &mut rand::thread_rng())
}

/// Create PSBT with `rng`.
pub fn create_psbt_with_rng(&self, params: PsbtParams, rng: &mut impl RngCore) -> Result<bitcoin::Psbt, CreatePsbtError> { ... }

The other option is to feature-gate the enable_anti_fee_sniping option entirely but that seems a bit less useful.

Copy link
Contributor

Choose a reason for hiding this comment

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

@aagbotemi could you adapt your code here and the current state of the PR to the above solution? Using feature gates

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Alright, will do that.

Copy link
Contributor

Choose a reason for hiding this comment

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

I think you are missing the std implementation using rand full features.

Copy link
Contributor

@nymius nymius Dec 4, 2025

Choose a reason for hiding this comment

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

Looks good overall, not sure if any review comments are still unresolved.

@ValuedMammal I'm not sure we have fulfilled this requirement yet. Do you think we should roll a custom random range even if the user has std available?

@aagbotemi aagbotemi force-pushed the feature/anti-fee-sniping branch from 74d705d to 65a6eec Compare November 12, 2025 15:58
@ValuedMammal
Copy link
Collaborator

Sorry about the merge conflict.

@aagbotemi
Copy link
Collaborator Author

Sorry about the merge conflict.

Alright. I will rebase to resolve the conflict.

@aagbotemi aagbotemi force-pushed the feature/anti-fee-sniping branch from 65a6eec to cd6e919 Compare November 14, 2025 21:27
@ValuedMammal ValuedMammal mentioned this pull request Nov 14, 2025
4 tasks
@aagbotemi aagbotemi force-pushed the feature/anti-fee-sniping branch 3 times, most recently from 3d7c798 to d3a378e Compare November 19, 2025 14:25
@aagbotemi
Copy link
Collaborator Author

aagbotemi commented Nov 19, 2025

Currently running cargo build​ is throwing error

error[E0433]: failed to resolve: could not find `rand` in `key`
   --> src/selection.rs:201:74
    |
201 | ...::bitcoin::key::rand::thread_rng())
    |                    ^^^^ could not find `rand` in `key`
    |
note: found an item that was configured out
   --> /home/abiodun/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/bitcoin-0.32.7/src/crypto/key.rs:29:20
    |
29  | pub use secp256k1::rand;
    |                    ^^^^
note: the item is gated behind the `rand-std` feature
   --> /home/abiodun/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/bitcoin-0.32.7/src/crypto/key.rs:28:7
    |
28  | #[cfg(feature = "rand-std")]
    |       ^^^^^^^^^^^^^^^^^^^^

For more information about this error, try `rustc --explain E0433`.
error: could not compile `bdk_tx` (lib) due to 1 previous error

The solution I could think of is moving bitcoin from dev-dependencies​ to dependencies​. But it's a very big dependency.

EDIT: another solution is to add the rand crate, which is better (I would like to know if it's okay we want to add a new dependency)

@aagbotemi aagbotemi force-pushed the feature/anti-fee-sniping branch from d3a378e to d9d165b Compare December 1, 2025 10:10
@aagbotemi aagbotemi force-pushed the feature/anti-fee-sniping branch from d9d165b to 9e8c355 Compare December 1, 2025 18:00
@aagbotemi aagbotemi force-pushed the feature/anti-fee-sniping branch from 9e8c355 to 88e798d Compare December 1, 2025 22:37
@aagbotemi aagbotemi force-pushed the feature/anti-fee-sniping branch from 88e798d to ddf46bf Compare December 2, 2025 14:27
@ValuedMammal
Copy link
Collaborator

Looks good overall, not sure if any review comments are still unresolved.

@aagbotemi aagbotemi force-pushed the feature/anti-fee-sniping branch from ddf46bf to ed9df3c Compare December 11, 2025 20:32
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feature request: Sequence based anti-fee-sniping BIP326

5 participants