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
Copy link
Contributor

@nymius nymius left a comment

Choose a reason for hiding this comment

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

Approach ACK 4649e8d.

I guess this will be squashed before merging, but I would reserve the revert type in commit messages for those actually reverting other commits, not manual commits undoing something.

I also checked the references in the BIP and bitcoin core seems to have implemented something similar back in the time.

Comment on lines 147 to 156
fn random_range(rng: &mut OsRng, end: u32) -> u32 {
let max = u32::MAX;
let max_multiple = max - (max % end);

loop {
let n = rng.next_u32();
if n < max_multiple {
return n % end;
}
}
}
Copy link
Contributor

Choose a reason for hiding this comment

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

Here you are defining the largest contiguous unbiased region within the numeric range representable by the u32 type. From what I have discovered, (-end) % end is the preferred version in the latest literature, although the performance is almost the same. Probably the compiler optimizes both forms to the same instructions, but I would use (-end) % (end) to fingerprint the technique you're using here for future code readers.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I tested end.wrapping_neg() % end for/same as (-end) % end but it causes infinite loops.

Copy link
Collaborator Author

@aagbotemi aagbotemi Nov 3, 2025

Choose a reason for hiding this comment

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

This has now been fixed using Debiased Modulo (Twice) — OpenBSD's Method

src/utils.rs Outdated
random_range(rng, probability) == 0
}

// Return a random value in the range [0, end].
Copy link
Contributor

Choose a reason for hiding this comment

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

You are trying to add a docstring here or just a comment? Use /// for docstrings.
Also, the range is not inclusive: [0, end), or [0, end - 1]

src/selection.rs Outdated
}
}

assert!(used_locktime || used_sequence);
Copy link
Contributor

Choose a reason for hiding this comment

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

Not needed anymore if using the loop.

src/selection.rs Outdated
used_sequence = true;
// One of the inputs should have modified sequence
let has_modified_sequence = tx.input.iter().any(|txin| {
dbg!(&txin.sequence.to_consensus_u32());
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 remove this.

src/selection.rs Outdated
let mut used_locktime = false;
let mut used_sequence = false;

for _ in 0..50 {
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 loop until I have one case for both and then I would exit. 50 seems to much.

src/selection.rs Outdated
let mut used_locktime = false;
let mut used_sequence = false;

for _ in 0..100 {
Copy link
Contributor

Choose a reason for hiding this comment

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

Same than below, a while checking both conditions are met is better.

Copy link
Contributor

Choose a reason for hiding this comment

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

To add to the above comment, you could count the number of loops and then check in a final assert than the amount of loops is less than a sane number, i.e.: assert!(loops < 20)

src/selection.rs Outdated

pub fn setup_test_input(confirmation_height: u32) -> anyhow::Result<(Input, absolute::Height)> {
let secp = Secp256k1::new();
let s = "tr([83737d5e/86h/1h/0h]tpubDDR5GgtoxS8fJyjjvdahN4VzV5DV6jtbcyvVXhEKq2XtpxjxBXmxH3r8QrNbQqHg4bJM1EGkxi7Pjfkgnui9jQWqS7kxHvX6rhUeriLDKxz/0/*)";
Copy link
Contributor

@nymius nymius Oct 30, 2025

Choose a reason for hiding this comment

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

I prefer to have the full name of the thing. Also, could this strings be constants somewhere?

Comment on lines 79 to 83
// Locktime is used, if rbf is disabled or any input requires locktime
// (e.g. non-taproot, unconfirmed, or >65535 confirmation) or there are
// no taproot inputs or the 50/50 coin flip chose locktime (USE_NLOCKTIME_PROBABILITY)
// Further-back randomness with 10% chance (FURTHER_BACK_PROBABILITY),
// will subtract a random 0–99 block offset to desynchronize from tip
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 this is more understandable if the conditions are enumerated as bullet points.

src/utils.rs Outdated
rbf_enabled: bool,
) -> Result<(), CreatePsbtError> {
const MAX_RELATIVE_HEIGHT: u32 = 65_535;
const USE_NLOCKTIME_PROBABILITY: u32 = 2;
Copy link
Contributor

Choose a reason for hiding this comment

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

This name is misleading, the intended probability is 50%. Maybe 50_PERCENT_PROBABILITY_RANGE is more accurate, if there is no one better.

src/utils.rs Outdated
const MAX_RELATIVE_HEIGHT: u32 = 65_535;
const USE_NLOCKTIME_PROBABILITY: u32 = 2;
const MIN_SEQUENCE_VALUE: u32 = 1;
const FURTHER_BACK_PROBABILITY: u32 = 10;
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, the intended probability is 10%.

@aagbotemi
Copy link
Collaborator Author

Approach ACK 4649e8d.

Thank you for the review. I'm working on fixing them.

@aagbotemi aagbotemi force-pushed the feature/anti-fee-sniping branch from 4649e8d to 63ecd47 Compare November 2, 2025 21:51
@aagbotemi aagbotemi requested a review from nymius November 2, 2025 21:58
@aagbotemi aagbotemi force-pushed the feature/anti-fee-sniping branch from 63ecd47 to 5a0b8cb Compare November 3, 2025 22:20
@aagbotemi aagbotemi force-pushed the feature/anti-fee-sniping branch from 5a0b8cb to 782c3e3 Compare November 4, 2025 08:40
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