1- use crate :: { cs_feerate , Input , Output , ScriptSource , Selection } ;
1+ use crate :: { Input , Output , ScriptSource , Selection } ;
22use alloc:: vec:: Vec ;
3- use bdk_coin_select:: { Candidate , CoinSelector , Target , TargetFee , TargetOutputs } ;
3+ use bdk_coin_select:: { Candidate , CoinSelector , DrainWeights , TargetOutputs } ;
44use miniscript:: bitcoin:: { Amount , FeeRate , TxOut , Weight } ;
55
66/// Parameters for creating a Child-Pays-For-Parent (CPFP) transaction.
@@ -30,33 +30,12 @@ pub struct CpfpParams {
3030}
3131
3232impl CpfpParams {
33- /// Create a new [CpfpParams] instance.
34- pub fn new (
35- package_fee : Amount ,
36- package_weight : Weight ,
37- inputs : impl IntoIterator < Item = impl Into < Input > > ,
38- target_package_feerate : FeeRate ,
39- output_script : crate :: ScriptSource ,
40- ) -> Self {
41- Self {
42- package_fee,
43- package_weight,
44- inputs : inputs. into_iter ( ) . map ( Into :: into) . collect ( ) ,
45- target_package_feerate,
46- output_script,
47- }
48- }
49-
5033 /// Convert the CPFP parameters into selection.
5134 ///
5235 /// This method calculates the required child transaction fee to achieve the
5336 /// target package feerate and creates a selection with the appropriate inputs
5437 /// and outputs.
5538 pub fn into_selection ( self ) -> Result < Selection , CpfpError > {
56- if self . inputs . is_empty ( ) {
57- return Err ( CpfpError :: NoSpendableOutputs ) ;
58- }
59-
6039 // Create candidates for coin selection
6140 let candidates = self
6241 . inputs
@@ -74,49 +53,30 @@ impl CpfpParams {
7453 let mut selector = CoinSelector :: new ( & candidates) ;
7554 selector. select_all ( ) ;
7655
56+ let total_input_value = Amount :: from_sat ( selector. selected_value ( ) ) ;
57+
7758 // Prepare output to calculate weight
7859 let script_pubkey = self . output_script . script ( ) ;
7960 let output = TxOut {
8061 value : Amount :: ZERO ,
8162 script_pubkey : script_pubkey. clone ( ) ,
8263 } ;
83- let output_weight = output. weight ( ) . to_wu ( ) ;
84-
85- // Calculate required child fee
86- let child_weight = self . compute_child_tx_weight ( & selector, output_weight) ;
87- let child_fee = self . compute_child_fee ( child_weight) ?;
88-
89- let total_input_value = Amount :: from_sat ( selector. selected_value ( ) ) ;
90-
91- let output_value = total_input_value
92- . checked_sub ( child_fee)
93- . ok_or ( CpfpError :: InsufficientInputValue ) ?;
9464
95- let dust_threshold = script_pubkey. minimal_non_dust ( ) ;
96- if output_value < dust_threshold {
97- return Err ( CpfpError :: OutputBelowDustLimit ) ;
98- }
99-
100- // Validate we achieve the target package feerate
101- let actual_package_feerate = self . compute_package_feerate ( child_fee, child_weight) ;
102- if actual_package_feerate < self . target_package_feerate {
103- return Err ( CpfpError :: InsufficientPackageFeerate {
104- actual : actual_package_feerate,
105- target : self . target_package_feerate ,
106- } ) ;
107- }
65+ let target_outputs = TargetOutputs :: fund_outputs ( vec ! [ ( output. weight( ) . to_wu( ) , 0 ) ] ) ;
66+ let cpfp_tx_weight = Weight :: from_wu ( selector. weight ( target_outputs, DrainWeights :: NONE ) ) ;
10867
109- // Verify the selection meets coin selection constraints
110- let target = Target {
111- fee : TargetFee {
112- rate : cs_feerate ( self . target_package_feerate ) ,
113- replace : None ,
114- } ,
115- outputs : TargetOutputs :: fund_outputs ( vec ! [ ( output_weight, output_value. to_sat( ) ) ] ) ,
68+ // Calculate required child fee
69+ let total_package_weight = self . package_weight + cpfp_tx_weight;
70+ let required_total_package_fee = self . target_package_feerate * total_package_weight;
71+ let required_child_fee = required_total_package_fee - self . package_fee ;
72+
73+ let output_value = match total_input_value. checked_sub ( required_child_fee) {
74+ Some ( value) => value,
75+ None => {
76+ let missing = required_child_fee - total_input_value;
77+ return Err ( CpfpError :: InsufficientInputValue { missing } ) ;
78+ }
11679 } ;
117- if !selector. is_target_met ( target) {
118- return Err ( CpfpError :: InsufficientInputValue ) ;
119- }
12080
12181 let outputs = vec ! [ Output :: with_script( script_pubkey, output_value) ] ;
12282
@@ -125,76 +85,28 @@ impl CpfpParams {
12585 outputs,
12686 } )
12787 }
128-
129- /// Computes the effective package feerate given the child fee and weight.
130- pub fn compute_package_feerate ( & self , child_fee : Amount , child_weight : Weight ) -> FeeRate {
131- let total_fee = self . package_fee + child_fee;
132- let total_weight = self . package_weight + child_weight;
133-
134- total_fee / total_weight
135- }
136-
137- /// Computes the required child fee to achieve target package feerate
138- pub fn compute_child_fee ( & self , child_weight : Weight ) -> Result < Amount , CpfpError > {
139- let total_target_weight = self . package_weight + child_weight;
140- let required_package_fee = self . target_package_feerate * total_target_weight;
141-
142- required_package_fee
143- . checked_sub ( self . package_fee )
144- . ok_or ( CpfpError :: InvalidFeeCalculation )
145- }
146-
147- /// Computes the weight of the child transaction.
148- ///
149- /// Uses the provided `selector` for input weights and `output_weight` for the output.
150- fn compute_child_tx_weight ( & self , selector : & CoinSelector , output_weight : u64 ) -> Weight {
151- const BASE_TX_WEIGHT : u64 = 10 * 4 ; // version, locktime, input/output counts
152- let input_weight = selector. input_weight ( ) ;
153-
154- Weight :: from_wu ( BASE_TX_WEIGHT + input_weight + output_weight)
155- }
15688}
15789
15890/// CPFP errors.
15991#[ derive( Debug ) ]
16092pub enum CpfpError {
161- /// Output value is below the dust threshold.
162- OutputBelowDustLimit ,
163- /// Total input value is insufficient.
164- InsufficientInputValue ,
165- /// No spendable outputs were found.
166- NoSpendableOutputs ,
167- /// Failed to compute a valid fee for the child transaction.
168- InvalidFeeCalculation ,
169- /// The package feerate (parent + child) is lower than the target feerate.
170- InsufficientPackageFeerate {
171- /// The actual feerate of the package.
172- actual : FeeRate ,
173- /// The target feerate that the package should meet or exceed.
174- target : FeeRate ,
93+ /// Total input value is insufficient to create a valid CPFP transaction.
94+ InsufficientInputValue {
95+ /// The additional amount needed to create a valid CPFP transaction
96+ missing : Amount ,
17597 } ,
176- /// Output script is invalid
177- InvalidOutputScript ,
17898}
17999
180100impl core:: fmt:: Display for CpfpError {
181101 fn fmt ( & self , f : & mut core:: fmt:: Formatter < ' _ > ) -> core:: fmt:: Result {
182102 match self {
183- Self :: OutputBelowDustLimit => write ! ( f, "output value is below dust threshold" ) ,
184- Self :: InsufficientInputValue => {
185- write ! ( f, "input value insufficient to cover required fee" )
186- }
187- Self :: NoSpendableOutputs => {
188- write ! ( f, "no spendable outputs found in parent transactions" )
189- }
190- Self :: InvalidFeeCalculation => {
191- write ! ( f, "failed to calculate valid child transaction fee" )
103+ Self :: InsufficientInputValue { missing } => {
104+ write ! (
105+ f,
106+ "input value insufficient: need {} more satoshis" ,
107+ missing. to_sat( )
108+ )
192109 }
193- Self :: InsufficientPackageFeerate { actual, target } => write ! (
194- f,
195- "package feerate {actual} is below target feerate {target}"
196- ) ,
197- Self :: InvalidOutputScript => write ! ( f, "output script is invalid or empty" ) ,
198110 }
199111 }
200112}
0 commit comments