@@ -1373,22 +1373,44 @@ impl BitcoinRegtestController {
1373
1373
previous_fees : Option < LeaderBlockCommitFees > ,
1374
1374
previous_txids : & Vec < Txid > ,
1375
1375
) -> Option < Transaction > {
1376
- let mut estimated_fees = match previous_fees {
1376
+ let _ = self . sortdb_mut ( ) ;
1377
+ let burn_chain_tip = self . burnchain_db . as_ref ( ) ?. get_canonical_chain_tip ( ) . ok ( ) ?;
1378
+ let estimated_fees = match previous_fees {
1377
1379
Some ( fees) => fees. fees_from_previous_tx ( & payload, & self . config ) ,
1378
1380
None => LeaderBlockCommitFees :: estimated_fees_from_payload ( & payload, & self . config ) ,
1379
1381
} ;
1380
1382
1381
- let _ = self . sortdb_mut ( ) ;
1382
- let burn_chain_tip = self . burnchain_db . as_ref ( ) ?. get_canonical_chain_tip ( ) . ok ( ) ?;
1383
+ self . send_block_commit_operation_at_burnchain_height (
1384
+ epoch_id,
1385
+ payload,
1386
+ signer,
1387
+ utxos_to_include,
1388
+ utxos_to_exclude,
1389
+ estimated_fees,
1390
+ previous_txids,
1391
+ burn_chain_tip. block_height ,
1392
+ )
1393
+ }
1383
1394
1395
+ fn send_block_commit_operation_at_burnchain_height (
1396
+ & mut self ,
1397
+ epoch_id : StacksEpochId ,
1398
+ payload : LeaderBlockCommitOp ,
1399
+ signer : & mut BurnchainOpSigner ,
1400
+ utxos_to_include : Option < UTXOSet > ,
1401
+ utxos_to_exclude : Option < UTXOSet > ,
1402
+ mut estimated_fees : LeaderBlockCommitFees ,
1403
+ previous_txids : & Vec < Txid > ,
1404
+ burnchain_block_height : u64 ,
1405
+ ) -> Option < Transaction > {
1384
1406
let public_key = signer. get_public_key ( ) ;
1385
1407
let ( mut tx, mut utxos) = self . prepare_tx (
1386
1408
epoch_id,
1387
1409
& public_key,
1388
1410
estimated_fees. estimated_amount_required ( ) ,
1389
1411
utxos_to_include,
1390
1412
utxos_to_exclude,
1391
- burn_chain_tip . block_height ,
1413
+ burnchain_block_height ,
1392
1414
) ?;
1393
1415
1394
1416
// Serialize the payload
@@ -1817,7 +1839,7 @@ impl BitcoinRegtestController {
1817
1839
debug ! ( "Not enough change to clear dust limit. Not adding change address." ) ;
1818
1840
}
1819
1841
1820
- for ( i , utxo) in utxos_set. utxos . iter ( ) . enumerate ( ) {
1842
+ for ( _i , utxo) in utxos_set. utxos . iter ( ) . enumerate ( ) {
1821
1843
let input = TxIn {
1822
1844
previous_output : OutPoint {
1823
1845
txid : utxo. txid ,
@@ -1828,7 +1850,8 @@ impl BitcoinRegtestController {
1828
1850
witness : vec ! [ ] ,
1829
1851
} ;
1830
1852
tx. input . push ( input) ;
1831
-
1853
+ }
1854
+ for ( i, utxo) in utxos_set. utxos . iter ( ) . enumerate ( ) {
1832
1855
let script_pub_key = utxo. script_pub_key . clone ( ) ;
1833
1856
let sig_hash_all = 0x01 ;
1834
1857
@@ -2805,6 +2828,12 @@ mod tests {
2805
2828
use std:: fs:: File ;
2806
2829
use std:: io:: Write ;
2807
2830
2831
+ use stacks:: burnchains:: BurnchainSigner ;
2832
+ use stacks_common:: deps_common:: bitcoin:: blockdata:: script:: Builder ;
2833
+ use stacks_common:: types:: chainstate:: { BlockHeaderHash , StacksAddress , VRFSeed } ;
2834
+ use stacks_common:: util:: hash:: to_hex;
2835
+ use stacks_common:: util:: secp256k1:: Secp256k1PrivateKey ;
2836
+
2808
2837
use super :: * ;
2809
2838
use crate :: config:: DEFAULT_SATS_PER_VB ;
2810
2839
@@ -2825,4 +2854,160 @@ mod tests {
2825
2854
2826
2855
assert_eq ! ( get_satoshis_per_byte( & config) , 51 ) ;
2827
2856
}
2857
+
2858
+ /// Verify that we can build a valid Bitcoin transaction with multiple UTXOs.
2859
+ /// Taken from production data.
2860
+ /// Tests `serialize_tx()` and `send_block_commit_operation_at_burnchain_height()`
2861
+ #[ test]
2862
+ fn test_multiple_inputs ( ) {
2863
+ let spend_utxos = vec ! [
2864
+ UTXO {
2865
+ txid: Sha256dHash :: from_hex(
2866
+ "d3eafb3aba3cec925473550ed2e4d00bcb0d00744bb3212e4a8e72878909daee" ,
2867
+ )
2868
+ . unwrap( ) ,
2869
+ vout: 3 ,
2870
+ script_pub_key: Builder :: from(
2871
+ hex_bytes( "76a9141dc27eba0247f8cc9575e7d45e50a0bc7e72427d88ac" ) . unwrap( ) ,
2872
+ )
2873
+ . into_script( ) ,
2874
+ amount: 42051 ,
2875
+ confirmations: 1421 ,
2876
+ } ,
2877
+ UTXO {
2878
+ txid: Sha256dHash :: from_hex(
2879
+ "01132f2d4a98cc715624e033214c8d841098a1ee15b30188ab89589a320b3b24" ,
2880
+ )
2881
+ . unwrap( ) ,
2882
+ vout: 0 ,
2883
+ script_pub_key: Builder :: from(
2884
+ hex_bytes( "76a9141dc27eba0247f8cc9575e7d45e50a0bc7e72427d88ac" ) . unwrap( ) ,
2885
+ )
2886
+ . into_script( ) ,
2887
+ amount: 326456 ,
2888
+ confirmations: 1421 ,
2889
+ } ,
2890
+ ] ;
2891
+
2892
+ // test serialize_tx()
2893
+ let mut config = Config :: default ( ) ;
2894
+ config. burnchain . magic_bytes = "T3" . as_bytes ( ) . into ( ) ;
2895
+
2896
+ let mut btc_controller = BitcoinRegtestController :: new ( config, None ) ;
2897
+ let mut utxo_set = UTXOSet {
2898
+ bhh : BurnchainHeaderHash ( [ 0x01 ; 32 ] ) ,
2899
+ utxos : spend_utxos. clone ( ) ,
2900
+ } ;
2901
+ let mut transaction = Transaction {
2902
+ input : vec ! [ ] ,
2903
+ output : vec ! [
2904
+ TxOut {
2905
+ value: 0 ,
2906
+ script_pubkey: Builder :: from( hex_bytes( "6a4c5054335be88c3d30cb59a142f83de3b27f897a43bbb0f13316911bb98a3229973dae32afd5b9f21bc1f40f24e2c101ecd13c55b8619e5e03dad81de2c62a1cc1d8c1b375000008a300010000059800015a" ) . unwrap( ) ) . into_script( ) ,
2907
+ } ,
2908
+ TxOut {
2909
+ value: 10000 ,
2910
+ script_pubkey: Builder :: from( hex_bytes( "76a914000000000000000000000000000000000000000088ac" ) . unwrap( ) ) . into_script( ) ,
2911
+ } ,
2912
+ TxOut {
2913
+ value: 10000 ,
2914
+ script_pubkey: Builder :: from( hex_bytes( "76a914000000000000000000000000000000000000000088ac" ) . unwrap( ) ) . into_script( ) ,
2915
+ } ,
2916
+ ] ,
2917
+ version : 1 ,
2918
+ lock_time : 0 ,
2919
+ } ;
2920
+
2921
+ let mut signer = BurnchainOpSigner :: new (
2922
+ Secp256k1PrivateKey :: from_hex (
2923
+ "9e446f6b0c6a96cf2190e54bcd5a8569c3e386f091605499464389b8d4e0bfc201" ,
2924
+ )
2925
+ . unwrap ( ) ,
2926
+ false ,
2927
+ ) ;
2928
+ assert ! ( btc_controller. serialize_tx(
2929
+ StacksEpochId :: Epoch25 ,
2930
+ & mut transaction,
2931
+ 44950 ,
2932
+ & mut utxo_set,
2933
+ & mut signer,
2934
+ true
2935
+ ) ) ;
2936
+ assert_eq ! ( transaction. output[ 3 ] . value, 323557 ) ;
2937
+
2938
+ // test send_block_commit_operation_at_burn_height()
2939
+ let utxo_set = UTXOSet {
2940
+ bhh : BurnchainHeaderHash ( [ 0x01 ; 32 ] ) ,
2941
+ utxos : spend_utxos. clone ( ) ,
2942
+ } ;
2943
+
2944
+ let commit_op = LeaderBlockCommitOp {
2945
+ block_header_hash : BlockHeaderHash :: from_hex (
2946
+ "e88c3d30cb59a142f83de3b27f897a43bbb0f13316911bb98a3229973dae32af" ,
2947
+ )
2948
+ . unwrap ( ) ,
2949
+ new_seed : VRFSeed :: from_hex (
2950
+ "d5b9f21bc1f40f24e2c101ecd13c55b8619e5e03dad81de2c62a1cc1d8c1b375" ,
2951
+ )
2952
+ . unwrap ( ) ,
2953
+ parent_block_ptr : 2211 , // 0x000008a3
2954
+ parent_vtxindex : 1 , // 0x0001
2955
+ key_block_ptr : 1432 , // 0x00000598
2956
+ key_vtxindex : 1 , // 0x0001
2957
+ memo : vec ! [ 11 ] , // 0x5a >> 3
2958
+
2959
+ burn_fee : 0 ,
2960
+ input : ( Txid ( [ 0x00 ; 32 ] ) , 0 ) ,
2961
+ burn_parent_modulus : 2 , // 0x5a & 0b111
2962
+
2963
+ apparent_sender : BurnchainSigner ( "mgbpit8FvkVJ9kuXY8QSM5P7eibnhcEMBk" . to_string ( ) ) ,
2964
+ commit_outs : vec ! [
2965
+ PoxAddress :: Standard ( StacksAddress :: burn_address( false ) , None ) ,
2966
+ PoxAddress :: Standard ( StacksAddress :: burn_address( false ) , None ) ,
2967
+ ] ,
2968
+
2969
+ treatment : vec ! [ ] ,
2970
+ sunset_burn : 0 ,
2971
+
2972
+ txid : Txid ( [ 0x00 ; 32 ] ) ,
2973
+ vtxindex : 0 ,
2974
+ block_height : 2212 ,
2975
+ burn_header_hash : BurnchainHeaderHash ( [ 0x01 ; 32 ] ) ,
2976
+ } ;
2977
+
2978
+ assert_eq ! ( to_hex( & commit_op. serialize_to_vec( ) ) , "5be88c3d30cb59a142f83de3b27f897a43bbb0f13316911bb98a3229973dae32afd5b9f21bc1f40f24e2c101ecd13c55b8619e5e03dad81de2c62a1cc1d8c1b375000008a300010000059800015a" . to_string( ) ) ;
2979
+
2980
+ let leader_fees = LeaderBlockCommitFees {
2981
+ sunset_fee : 0 ,
2982
+ fee_rate : 50 ,
2983
+ sortition_fee : 20000 ,
2984
+ outputs_len : 2 ,
2985
+ default_tx_size : 380 ,
2986
+ spent_in_attempts : 0 ,
2987
+ is_rbf_enabled : false ,
2988
+ final_size : 498 ,
2989
+ } ;
2990
+
2991
+ assert_eq ! ( leader_fees. amount_per_output( ) , 10000 ) ;
2992
+ assert_eq ! ( leader_fees. total_spent( ) , 44900 ) ;
2993
+
2994
+ let block_commit = btc_controller
2995
+ . send_block_commit_operation_at_burnchain_height (
2996
+ StacksEpochId :: Epoch30 ,
2997
+ commit_op,
2998
+ & mut signer,
2999
+ Some ( utxo_set) ,
3000
+ None ,
3001
+ leader_fees,
3002
+ & vec ! [ ] ,
3003
+ 2212 ,
3004
+ )
3005
+ . unwrap ( ) ;
3006
+
3007
+ debug ! ( "send_block_commit_operation:\n {:#?}" , & block_commit) ;
3008
+ debug ! ( "{}" , & SerializedTx :: new( block_commit. clone( ) ) . to_hex( ) ) ;
3009
+ assert_eq ! ( block_commit. output[ 3 ] . value, 323507 ) ;
3010
+
3011
+ assert_eq ! ( & SerializedTx :: new( block_commit. clone( ) ) . to_hex( ) , "0100000002eeda098987728e4a2e21b34b74000dcb0bd0e4d20e55735492ec3cba3afbead3030000006a4730440220558286e20e10ce31537f0625dae5cc62fac7961b9d2cf272c990de96323d7e2502202255adbea3d2e0509b80c5d8a3a4fe6397a87bcf18da1852740d5267d89a0cb20121035379aa40c02890d253cfa577964116eb5295570ae9f7287cbae5f2585f5b2c7cfdffffff243b0b329a5889ab8801b315eea19810848d4c2133e0245671cc984a2d2f1301000000006a47304402206d9f8de107f9e1eb15aafac66c2bb34331a7523260b30e18779257e367048d34022013c7dabb32a5c281aa00d405e2ccbd00f34f03a65b2336553a4acd6c52c251ef0121035379aa40c02890d253cfa577964116eb5295570ae9f7287cbae5f2585f5b2c7cfdffffff040000000000000000536a4c5054335be88c3d30cb59a142f83de3b27f897a43bbb0f13316911bb98a3229973dae32afd5b9f21bc1f40f24e2c101ecd13c55b8619e5e03dad81de2c62a1cc1d8c1b375000008a300010000059800015a10270000000000001976a914000000000000000000000000000000000000000088ac10270000000000001976a914000000000000000000000000000000000000000088acb3ef0400000000001976a9141dc27eba0247f8cc9575e7d45e50a0bc7e72427d88ac00000000" ) ;
3012
+ }
2828
3013
}
0 commit comments