Skip to content

Commit

Permalink
Fix: TxOut::minimal_non_dust
Browse files Browse the repository at this point in the history
TxOut::minimal_non_dust has 2 problems.

1. There is an invisible dependency on Bitcoin Core's default minrelaytxfee value. It has been made explicit.
2. There is an off by one error. The dust limit comparison uses < and therefore `+ 1` was not needed. It has been fixed.
  • Loading branch information
junderw committed Dec 7, 2023
1 parent e09ef5c commit c2224e9
Show file tree
Hide file tree
Showing 3 changed files with 29 additions and 28 deletions.
36 changes: 22 additions & 14 deletions bitcoin/src/blockdata/script/borrowed.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ use hashes::Hash;
use secp256k1::{Secp256k1, Verification};

use super::PushBytes;
use crate::blockdata::fee_rate::FeeRate;
use crate::blockdata::opcodes::all::*;
use crate::blockdata::opcodes::{self, Opcode};
use crate::blockdata::script::witness_version::WitnessVersion;
Expand All @@ -19,7 +20,7 @@ use crate::blockdata::script::{
};
use crate::consensus::Encodable;
use crate::key::{PublicKey, UntweakedPublicKey};
use crate::policy::DUST_RELAY_TX_FEE;
use crate::policy::DEFAULT_MIN_RELAY_TX_FEE;
use crate::prelude::*;
use crate::taproot::{LeafVersion, TapLeafHash, TapNodeHash};

Expand Down Expand Up @@ -390,21 +391,28 @@ impl Script {

/// Returns the minimum value an output with this script should have in order to be
/// broadcastable on today's Bitcoin network.
pub fn dust_value(&self) -> crate::Amount {
///
/// Pass `None` for `min_relay_tx_fee` if you'd like to use the Bitcoin Core default (1 sat/vByte)
/// and the 3x multiplier for dust will be applied internally.
pub fn dust_value(&self, min_relay_tx_fee: Option<FeeRate>) -> crate::Amount {
// This must never be lower than Bitcoin Core's GetDustThreshold() (as of v0.21) as it may
// otherwise allow users to create transactions which likely can never be broadcast/confirmed.
let sats = DUST_RELAY_TX_FEE as u64 / 1000 * // The default dust relay fee is 3000 satoshi/kB (i.e. 3 sat/vByte)
if self.is_op_return() {
0
} else if self.is_witness_program() {
32 + 4 + 1 + (107 / 4) + 4 + // The spend cost copied from Core
8 + // The serialized size of the TxOut's amount field
self.consensus_encode(&mut sink()).expect("sinks don't error") as u64 // The serialized size of this script_pubkey
} else {
32 + 4 + 1 + 107 + 4 + // The spend cost copied from Core
8 + // The serialized size of the TxOut's amount field
self.consensus_encode(&mut sink()).expect("sinks don't error") as u64 // The serialized size of this script_pubkey
};
let sats = min_relay_tx_fee.map_or(DEFAULT_MIN_RELAY_TX_FEE as u64, |fr| fr.to_sat_per_kwu() * 4) *
3 * // 3x multiplier for dust
if self.is_op_return() {
0
} else if self.is_witness_program() {
32 + 4 + 1 + (107 / 4) + 4 + // The spend cost copied from Core
8 + // The serialized size of the TxOut's amount field
self.consensus_encode(&mut sink()).expect("sinks don't error") as u64 // The serialized size of this script_pubkey
} else {
32 + 4 + 1 + 107 + 4 + // The spend cost copied from Core
8 + // The serialized size of the TxOut's amount field
self.consensus_encode(&mut sink()).expect("sinks don't error") as u64 // The serialized size of this script_pubkey
} /
1000; // divide by 1000 like in Core to get 3 * sat/B as it cancels out DEFAULT_MIN_RELAY_TX_FEE
// Note: We ensure the division happens at the end, since Core performs the division at the end.
// This will make sure none of the implicit floor operations mess with the value.

crate::Amount::from_sat(sats)
}
Expand Down
4 changes: 2 additions & 2 deletions bitcoin/src/blockdata/script/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -649,7 +649,7 @@ fn defult_dust_value_tests() {
// well-known scriptPubKey types.
let script_p2wpkh = Builder::new().push_int(0).push_slice([42; 20]).into_script();
assert!(script_p2wpkh.is_p2wpkh());
assert_eq!(script_p2wpkh.dust_value(), crate::Amount::from_sat(294));
assert_eq!(script_p2wpkh.dust_value(None), crate::Amount::from_sat(294));

let script_p2pkh = Builder::new()
.push_opcode(OP_DUP)
Expand All @@ -659,7 +659,7 @@ fn defult_dust_value_tests() {
.push_opcode(OP_CHECKSIG)
.into_script();
assert!(script_p2pkh.is_p2pkh());
assert_eq!(script_p2pkh.dust_value(), crate::Amount::from_sat(546));
assert_eq!(script_p2pkh.dust_value(None), crate::Amount::from_sat(546));
}

#[test]
Expand Down
17 changes: 5 additions & 12 deletions bitcoin/src/blockdata/transaction.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ use hashes::{self, sha256d, Hash};
use internals::write_err;

use super::Weight;
use crate::blockdata::fee_rate::FeeRate;
use crate::blockdata::locktime::absolute::{self, Height, Time};
use crate::blockdata::locktime::relative;
use crate::blockdata::script::{Script, ScriptBuf};
Expand Down Expand Up @@ -534,19 +535,11 @@ impl TxOut {
/// Creates a `TxOut` with given script and the smallest possible `value` that is **not** dust
/// per current Core policy.
///
/// The current dust fee rate is 3 sat/vB.
pub fn minimal_non_dust(script_pubkey: ScriptBuf) -> Self {
let len = size_from_script_pubkey(&script_pubkey);
let len = len
+ if script_pubkey.is_witness_program() {
32 + 4 + 1 + (107 / 4) + 4
} else {
32 + 4 + 1 + 107 + 4
};
let dust_amount = (len as u64) * 3;

/// Pass `None` for `min_relay_tx_fee` if you'd like to use the Bitcoin Core default (1 sat/vByte)
/// and the 3x multiplier for dust will be applied internally.
pub fn minimal_non_dust(script_pubkey: ScriptBuf, min_relay_tx_fee: Option<FeeRate>) -> Self {
TxOut {
value: Amount::from_sat(dust_amount + 1), // minimal non-dust amount is one higher than dust amount
value: script_pubkey.dust_value(min_relay_tx_fee),
script_pubkey,
}
}
Expand Down

0 comments on commit c2224e9

Please sign in to comment.