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