7
7
// You may not use this file except in accordance with one or both of these
8
8
// licenses.
9
9
10
- use bitcoin:: blockdata:: transaction:: TxOut ;
10
+ use bitcoin:: blockdata:: transaction:: { Transaction , TxOut } ;
11
+ use bitcoin:: blockdata:: script:: Script ;
12
+ use bitcoin:: consensus:: Encodable ;
13
+ use bitcoin:: consensus:: encode:: VarInt ;
14
+
15
+ use ln:: msgs:: MAX_VALUE_MSAT ;
11
16
12
17
use std:: cmp:: Ordering ;
13
18
@@ -21,12 +26,60 @@ pub fn sort_outputs<T, C : Fn(&T, &T) -> Ordering>(outputs: &mut Vec<(TxOut, T)>
21
26
} ) ;
22
27
}
23
28
29
+ fn get_dust_value ( output_script : & Script ) -> u64 {
30
+ //TODO: This belongs in rust-bitcoin (https://github.com/rust-bitcoin/rust-bitcoin/pull/566)
31
+ if output_script. is_op_return ( ) {
32
+ 0
33
+ } else if output_script. is_witness_program ( ) {
34
+ 294
35
+ } else {
36
+ 546
37
+ }
38
+ }
39
+
40
+ /// Possibly adds a change output to the given transaction, always doing so if there are excess
41
+ /// funds available beyond the requested feerate.
42
+ /// Assumes at least one input will have a witness (ie spends a segwit output).
43
+ /// Returns an Err(()) if the requested feerate cannot be met.
44
+ pub ( crate ) fn maybe_add_change_output ( tx : & mut Transaction , input_value : u64 , witness_max_weight : usize , feerate_sat_per_1000_weight : u32 , change_destination_script : Script ) -> Result < ( ) , ( ) > {
45
+ if input_value > MAX_VALUE_MSAT / 1000 { return Err ( ( ) ) ; }
46
+
47
+ let mut output_value = 0 ;
48
+ for output in tx. output . iter ( ) {
49
+ output_value += output. value ;
50
+ if output_value >= input_value { return Err ( ( ) ) ; }
51
+ }
52
+
53
+ let dust_value = get_dust_value ( & change_destination_script) ;
54
+ let mut change_output = TxOut {
55
+ script_pubkey : change_destination_script,
56
+ value : 0 ,
57
+ } ;
58
+ let change_len = change_output. consensus_encode ( & mut std:: io:: sink ( ) ) . unwrap ( ) ;
59
+ let mut weight_with_change: i64 = tx. get_weight ( ) as i64 + 2 + witness_max_weight as i64 + change_len as i64 * 4 ;
60
+ // Include any extra bytes required to push an extra output.
61
+ weight_with_change += ( VarInt ( tx. output . len ( ) as u64 + 1 ) . len ( ) - VarInt ( tx. output . len ( ) as u64 ) . len ( ) ) as i64 * 4 ;
62
+ // When calculating weight, add two for the flag bytes
63
+ let change_value: i64 = ( input_value - output_value) as i64 - weight_with_change * feerate_sat_per_1000_weight as i64 / 1000 ;
64
+ if change_value >= dust_value as i64 {
65
+ change_output. value = change_value as u64 ;
66
+ tx. output . push ( change_output) ;
67
+ } else if ( input_value - output_value) as i64 - ( tx. get_weight ( ) as i64 + 2 + witness_max_weight as i64 ) * feerate_sat_per_1000_weight as i64 / 1000 < 0 {
68
+ return Err ( ( ) ) ;
69
+ }
70
+
71
+ Ok ( ( ) )
72
+ }
73
+
24
74
#[ cfg( test) ]
25
75
mod tests {
26
76
use super :: * ;
27
77
78
+ use bitcoin:: blockdata:: transaction:: { Transaction , TxOut , TxIn , OutPoint } ;
28
79
use bitcoin:: blockdata:: script:: { Script , Builder } ;
29
- use bitcoin:: blockdata:: transaction:: TxOut ;
80
+ use bitcoin:: hash_types:: Txid ;
81
+
82
+ use bitcoin:: hashes:: sha256d:: Hash as Sha256dHash ;
30
83
31
84
use hex:: decode;
32
85
@@ -158,4 +211,78 @@ mod tests {
158
211
bip69_txout_test_1: TXOUT1 . to_vec( ) ,
159
212
bip69_txout_test_2: TXOUT2 . to_vec( ) ,
160
213
}
214
+
215
+ #[ test]
216
+ fn test_tx_value_overrun ( ) {
217
+ // If we have a bogus input amount or outputs valued more than inputs, we should fail
218
+ let mut tx = Transaction { version : 2 , lock_time : 0 , input : Vec :: new ( ) , output : vec ! [ TxOut {
219
+ script_pubkey: Script :: new( ) , value: 1000
220
+ } ] } ;
221
+ assert ! ( maybe_add_change_output( & mut tx, 21_000_000_0000_0001 , 0 , 253 , Script :: new( ) ) . is_err( ) ) ;
222
+ assert ! ( maybe_add_change_output( & mut tx, 400 , 0 , 253 , Script :: new( ) ) . is_err( ) ) ;
223
+ assert ! ( maybe_add_change_output( & mut tx, 4000 , 0 , 253 , Script :: new( ) ) . is_ok( ) ) ;
224
+ }
225
+
226
+ #[ test]
227
+ fn test_tx_change_edge ( ) {
228
+ // Check that we never add dust outputs
229
+ let mut tx = Transaction { version : 2 , lock_time : 0 , input : Vec :: new ( ) , output : Vec :: new ( ) } ;
230
+ let orig_wtxid = tx. wtxid ( ) ;
231
+ // 9 sats isn't enough to pay fee on a dummy transaction...
232
+ assert_eq ! ( tx. get_weight( ) as u64 , 40 ) ; // ie 10 vbytes
233
+ assert ! ( maybe_add_change_output( & mut tx, 9 , 0 , 253 , Script :: new( ) ) . is_err( ) ) ;
234
+ assert_eq ! ( tx. wtxid( ) , orig_wtxid) ; // Failure doesn't change the transaction
235
+ // but 10-564 is, just not enough to add a change output...
236
+ assert ! ( maybe_add_change_output( & mut tx, 10 , 0 , 253 , Script :: new( ) ) . is_ok( ) ) ;
237
+ assert_eq ! ( tx. output. len( ) , 0 ) ;
238
+ assert_eq ! ( tx. wtxid( ) , orig_wtxid) ; // If we don't add an output, we don't change the transaction
239
+ assert ! ( maybe_add_change_output( & mut tx, 564 , 0 , 253 , Script :: new( ) ) . is_ok( ) ) ;
240
+ assert_eq ! ( tx. output. len( ) , 0 ) ;
241
+ assert_eq ! ( tx. wtxid( ) , orig_wtxid) ; // If we don't add an output, we don't change the transaction
242
+ // 565 is also not enough, if we anticipate 2 more weight units pushing us up to the next vbyte
243
+ // (considering the two bytes for segwit flags)
244
+ assert ! ( maybe_add_change_output( & mut tx, 565 , 2 , 253 , Script :: new( ) ) . is_ok( ) ) ;
245
+ assert_eq ! ( tx. output. len( ) , 0 ) ;
246
+ assert_eq ! ( tx. wtxid( ) , orig_wtxid) ; // If we don't add an output, we don't change the transaction
247
+ // at 565 we can afford the change output at the dust limit (546)
248
+ assert ! ( maybe_add_change_output( & mut tx, 565 , 0 , 253 , Script :: new( ) ) . is_ok( ) ) ;
249
+ assert_eq ! ( tx. output. len( ) , 1 ) ;
250
+ assert_eq ! ( tx. output[ 0 ] . value, 546 ) ;
251
+ assert_eq ! ( tx. output[ 0 ] . script_pubkey, Script :: new( ) ) ;
252
+ assert_eq ! ( tx. get_weight( ) / 4 , 565 -546 ) ; // New weight is exactly the fee we wanted.
253
+
254
+ tx. output . pop ( ) ;
255
+ assert_eq ! ( tx. wtxid( ) , orig_wtxid) ; // The only change is the addition of one output.
256
+ }
257
+
258
+ #[ test]
259
+ fn test_tx_extra_outputs ( ) {
260
+ // Check that we correctly handle existing outputs
261
+ let mut tx = Transaction { version : 2 , lock_time : 0 , input : vec ! [ TxIn {
262
+ previous_output: OutPoint :: new( Txid :: from_hash( Sha256dHash :: default ( ) ) , 0 ) , script_sig: Script :: new( ) , witness: Vec :: new( ) , sequence: 0 ,
263
+ } ] , output : vec ! [ TxOut {
264
+ script_pubkey: Builder :: new( ) . push_int( 1 ) . into_script( ) , value: 1000
265
+ } ] } ;
266
+ let orig_wtxid = tx. wtxid ( ) ;
267
+ let orig_weight = tx. get_weight ( ) ;
268
+ assert_eq ! ( orig_weight / 4 , 61 ) ;
269
+
270
+ // Input value of the output value + fee - 1 should fail:
271
+ assert ! ( maybe_add_change_output( & mut tx, 1000 + 61 + 100 - 1 , 400 , 250 , Builder :: new( ) . push_int( 2 ) . into_script( ) ) . is_err( ) ) ;
272
+ assert_eq ! ( tx. wtxid( ) , orig_wtxid) ; // Failure doesn't change the transaction
273
+ // but one more input sat should succeed, without changing the transaction
274
+ assert ! ( maybe_add_change_output( & mut tx, 1000 + 61 + 100 , 400 , 250 , Builder :: new( ) . push_int( 2 ) . into_script( ) ) . is_ok( ) ) ;
275
+ assert_eq ! ( tx. wtxid( ) , orig_wtxid) ; // If we don't add an output, we don't change the transaction
276
+ // In order to get a change output, we need to add 546 plus the output's weight / 4 (10)...
277
+ assert ! ( maybe_add_change_output( & mut tx, 1000 + 61 + 100 + 546 + 9 , 400 , 250 , Builder :: new( ) . push_int( 2 ) . into_script( ) ) . is_ok( ) ) ;
278
+ assert_eq ! ( tx. wtxid( ) , orig_wtxid) ; // If we don't add an output, we don't change the transaction
279
+
280
+ assert ! ( maybe_add_change_output( & mut tx, 1000 + 61 + 100 + 546 + 10 , 400 , 250 , Builder :: new( ) . push_int( 2 ) . into_script( ) ) . is_ok( ) ) ;
281
+ assert_eq ! ( tx. output. len( ) , 2 ) ;
282
+ assert_eq ! ( tx. output[ 1 ] . value, 546 ) ;
283
+ assert_eq ! ( tx. output[ 1 ] . script_pubkey, Builder :: new( ) . push_int( 2 ) . into_script( ) ) ;
284
+ assert_eq ! ( tx. get_weight( ) - orig_weight, 40 ) ; // Weight difference matches what we had to add above
285
+ tx. output . pop ( ) ;
286
+ assert_eq ! ( tx. wtxid( ) , orig_wtxid) ; // The only change is the addition of one output.
287
+ }
161
288
}
0 commit comments