diff --git a/bitcoin/src/blockdata/script/borrowed.rs b/bitcoin/src/blockdata/script/borrowed.rs index 117e34d0..828a23b9 100644 --- a/bitcoin/src/blockdata/script/borrowed.rs +++ b/bitcoin/src/blockdata/script/borrowed.rs @@ -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; @@ -390,21 +391,51 @@ 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 { + /// + /// Dust depends on the -dustrelayfee value of the Bitcoin Core node you are broadcasting to. + /// This function uses the default value of 0.00003 BTC/kB (3 sat/vByte). + /// + /// To use a custom value, use [`minimal_non_dust_custom`]. + /// + /// [`minimal_non_dust_custom`]: Script::minimal_non_dust_custom + pub fn minimal_non_dust(&self) -> crate::Amount { + self.minimal_non_dust_inner(DUST_RELAY_TX_FEE.into()) + } + + /// Returns the minimum value an output with this script should have in order to be + /// broadcastable on today's Bitcoin network. + /// + /// Dust depends on the -dustrelayfee value of the Bitcoin Core node you are broadcasting to. + /// This function lets you set the fee rate used in dust calculation. + /// + /// The current default value in Bitcoin Core (as of v26) is 3 sat/vByte. + /// + /// To use the default Bitcoin Core value, use [`minimal_non_dust`]. + /// + /// [`minimal_non_dust`]: Script::minimal_non_dust + pub fn minimal_non_dust_custom(&self, dust_relay_fee: FeeRate) -> crate::Amount { + self.minimal_non_dust_inner(dust_relay_fee.to_sat_per_kwu() * 4) + } + + fn minimal_non_dust_inner(&self, dust_relay_fee: u64) -> 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 = dust_relay_fee.checked_mul( + 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 + } + ).expect("dust_relay_fee or script length should not be absurdly large") / + 1000; // divide by 1000 like in Core to get value 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) } diff --git a/bitcoin/src/blockdata/script/tests.rs b/bitcoin/src/blockdata/script/tests.rs index c0a4c10b..e0a60f4e 100644 --- a/bitcoin/src/blockdata/script/tests.rs +++ b/bitcoin/src/blockdata/script/tests.rs @@ -6,6 +6,7 @@ use hashes::Hash; use hex_lit::hex; use super::*; +use crate::FeeRate; use crate::blockdata::opcodes; use crate::consensus::encode::{deserialize, serialize}; use crate::crypto::key::{PubkeyHash, PublicKey, WPubkeyHash, XOnlyPublicKey}; @@ -649,7 +650,8 @@ 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.minimal_non_dust(), crate::Amount::from_sat(294)); + assert_eq!(script_p2wpkh.minimal_non_dust_custom(FeeRate::from_sat_per_vb_unchecked(6)), crate::Amount::from_sat(588)); let script_p2pkh = Builder::new() .push_opcode(OP_DUP) @@ -659,7 +661,8 @@ 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.minimal_non_dust(), crate::Amount::from_sat(546)); + assert_eq!(script_p2pkh.minimal_non_dust_custom(FeeRate::from_sat_per_vb_unchecked(6)), crate::Amount::from_sat(1092)); } #[test] diff --git a/bitcoin/src/blockdata/transaction.rs b/bitcoin/src/blockdata/transaction.rs index 02a34055..7a617be5 100644 --- a/bitcoin/src/blockdata/transaction.rs +++ b/bitcoin/src/blockdata/transaction.rs @@ -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}; @@ -548,19 +549,33 @@ 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. + /// Dust depends on the -dustrelayfee value of the Bitcoin Core node you are broadcasting to. + /// This function uses the default value of 0.00003 BTC/kB (3 sat/vByte). + /// + /// To use a custom value, use [`minimal_non_dust_custom`]. + /// + /// [`minimal_non_dust_custom`]: TxOut::minimal_non_dust_custom 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; - TxOut { - value: Amount::from_sat(dust_amount + 1), // minimal non-dust amount is one higher than dust amount + value: script_pubkey.minimal_non_dust(), + script_pubkey, + } + } + + /// Creates a `TxOut` with given script and the smallest possible `value` that is **not** dust + /// per current Core policy. + /// + /// Dust depends on the -dustrelayfee value of the Bitcoin Core node you are broadcasting to. + /// This function lets you set the fee rate used in dust calculation. + /// + /// The current default value in Bitcoin Core (as of v26) is 3 sat/vByte. + /// + /// To use the default Bitcoin Core value, use [`minimal_non_dust`]. + /// + /// [`minimal_non_dust`]: TxOut::minimal_non_dust + pub fn minimal_non_dust_custom(script_pubkey: ScriptBuf, dust_relay_fee: FeeRate) -> Self { + TxOut { + value: script_pubkey.minimal_non_dust_custom(dust_relay_fee), script_pubkey, } }