@@ -31,7 +31,7 @@ use stacks::burnchains::bitcoin::indexer::{
3131 BitcoinIndexer , BitcoinIndexerConfig , BitcoinIndexerRuntime ,
3232} ;
3333use stacks:: burnchains:: bitcoin:: spv:: SpvClient ;
34- use stacks:: burnchains:: bitcoin:: BitcoinNetworkType ;
34+ use stacks:: burnchains:: bitcoin:: { BitcoinNetworkType , Error as btc_error } ;
3535use stacks:: burnchains:: db:: BurnchainDB ;
3636use stacks:: burnchains:: indexer:: BurnchainIndexer ;
3737use stacks:: burnchains:: {
@@ -78,7 +78,9 @@ use url::Url;
7878use super :: super :: operations:: BurnchainOpSigner ;
7979use super :: super :: Config ;
8080use super :: { BurnchainController , BurnchainTip , Error as BurnchainControllerError } ;
81- use crate :: burnchains:: rpc:: bitcoin_rpc_client:: { BitcoinRpcClient , BitcoinRpcClientError } ;
81+ use crate :: burnchains:: rpc:: bitcoin_rpc_client:: {
82+ BitcoinRpcClient , BitcoinRpcClientError , ImportDescriptorsRequest , Timestamp ,
83+ } ;
8284
8385/// The number of bitcoin blocks that can have
8486/// passed since the UTXO cache was last refreshed before
@@ -302,6 +304,20 @@ impl<T> BitcoinRpcClientResultExt<T> for Result<T, BitcoinRpcClientError> {
302304 }
303305}
304306
307+ /// Represents errors that can occur when using [`BitcoinRegtestController`].
308+ #[ derive( Debug , thiserror:: Error ) ]
309+ pub enum BitcoinRegtestControllerError {
310+ /// Error related to Bitcoin RPC failures.
311+ #[ error( "Bitcoin RPC error: {0}" ) ]
312+ Rpc ( #[ from] BitcoinRpcClientError ) ,
313+ /// Error related to invalid or malformed [`Secp256k1PublicKey`].
314+ #[ error( "Invalid public key: {0}" ) ]
315+ InvalidPublicKey ( btc_error ) ,
316+ }
317+
318+ /// Alias for results returned from [`BitcoinRegtestController`] operations.
319+ pub type BitcoinRegtestControllerResult < T > = Result < T , BitcoinRegtestControllerError > ;
320+
305321impl BitcoinRegtestController {
306322 pub fn new ( config : Config , coordinator_channel : Option < CoordinatorChannels > ) -> Self {
307323 BitcoinRegtestController :: with_burnchain ( config, coordinator_channel, None , None )
@@ -664,7 +680,10 @@ impl BitcoinRegtestController {
664680 } ;
665681
666682 test_debug ! ( "Import public key '{}'" , & pubk. to_hex( ) ) ;
667- let _result = BitcoinRPCRequest :: import_public_key ( & self . config , & pubk) ;
683+ let result = self . import_public_key ( & pubk) ;
684+ if let Err ( error) = result {
685+ warn ! ( "Import public key '{}' failed: {error:?}" , & pubk. to_hex( ) ) ;
686+ }
668687
669688 sleep_ms ( 1000 ) ;
670689
@@ -740,13 +759,13 @@ impl BitcoinRegtestController {
740759 }
741760
742761 /// Retrieve all loaded wallets.
743- pub fn list_wallets ( & self ) -> Result < Vec < String > , BitcoinRpcClientError > {
744- self . rpc_client . list_wallets ( )
762+ pub fn list_wallets ( & self ) -> BitcoinRegtestControllerResult < Vec < String > > {
763+ Ok ( self . rpc_client . list_wallets ( ) ? )
745764 }
746765
747766 /// Checks if the config-supplied wallet exists.
748767 /// If it does not exist, this function creates it.
749- pub fn create_wallet_if_dne ( & self ) -> Result < ( ) , BitcoinRpcClientError > {
768+ pub fn create_wallet_if_dne ( & self ) -> BitcoinRegtestControllerResult < ( ) > {
750769 let wallets = self . list_wallets ( ) ?;
751770 let wallet = self . get_wallet_name ( ) ;
752771 if !wallets. contains ( wallet) {
@@ -807,7 +826,10 @@ impl BitcoinRegtestController {
807826 // Assuming that miners are in charge of correctly operating their bitcoind nodes sounds
808827 // reasonable to me.
809828 // $ bitcoin-cli importaddress mxVFsFW5N4mu1HPkxPttorvocvzeZ7KZyk
810- let _result = BitcoinRPCRequest :: import_public_key ( & self . config , & pubk) ;
829+ let result = self . import_public_key ( & pubk) ;
830+ if let Err ( error) = result {
831+ warn ! ( "Import public key '{}' failed: {error:?}" , & pubk. to_hex( ) ) ;
832+ }
811833 sleep_ms ( 1000 ) ;
812834 }
813835
@@ -2111,7 +2133,7 @@ impl BitcoinRegtestController {
21112133
21122134 for pk in pks {
21132135 debug ! ( "Import public key '{}'" , & pk. to_hex( ) ) ;
2114- if let Err ( e) = BitcoinRPCRequest :: import_public_key ( & self . config , pk) {
2136+ if let Err ( e) = self . import_public_key ( pk) {
21152137 warn ! ( "Error when importing pubkey: {e:?}" ) ;
21162138 }
21172139 }
@@ -2174,6 +2196,57 @@ impl BitcoinRegtestController {
21742196 fn get_wallet_name ( & self ) -> & String {
21752197 & self . config . burnchain . wallet_name
21762198 }
2199+
2200+ /// Imports a public key into configured wallet by registering its
2201+ /// corresponding addresses as descriptors.
2202+ ///
2203+ /// This computes both **legacy (P2PKH)** and, if the miner is configured
2204+ /// with `segwit` enabled, also **SegWit (P2WPKH)** addresses, then imports
2205+ /// the related descriptors into the wallet.
2206+ pub fn import_public_key (
2207+ & self ,
2208+ public_key : & Secp256k1PublicKey ,
2209+ ) -> BitcoinRegtestControllerResult < ( ) > {
2210+ let pkh = Hash160 :: from_data ( & public_key. to_bytes ( ) )
2211+ . to_bytes ( )
2212+ . to_vec ( ) ;
2213+ let ( _, network_id) = self . config . burnchain . get_bitcoin_network ( ) ;
2214+
2215+ // import both the legacy and segwit variants of this public key
2216+ let mut addresses = vec ! [ BitcoinAddress :: from_bytes_legacy(
2217+ network_id,
2218+ LegacyBitcoinAddressType :: PublicKeyHash ,
2219+ & pkh,
2220+ )
2221+ . map_err( BitcoinRegtestControllerError :: InvalidPublicKey ) ?] ;
2222+
2223+ if self . config . miner . segwit {
2224+ addresses. push (
2225+ BitcoinAddress :: from_bytes_segwit_p2wpkh ( network_id, & pkh)
2226+ . map_err ( BitcoinRegtestControllerError :: InvalidPublicKey ) ?,
2227+ ) ;
2228+ }
2229+
2230+ for address in addresses. into_iter ( ) {
2231+ debug ! (
2232+ "Import address {address} for public key {}" ,
2233+ public_key. to_hex( )
2234+ ) ;
2235+
2236+ let descriptor = format ! ( "addr({address})" ) ;
2237+ let info = self . rpc_client . get_descriptor_info ( & descriptor) ?;
2238+
2239+ let descr_req = ImportDescriptorsRequest {
2240+ descriptor : format ! ( "addr({address})#{}" , info. checksum) ,
2241+ timestamp : Timestamp :: Time ( 0 ) ,
2242+ internal : Some ( true ) ,
2243+ } ;
2244+
2245+ self . rpc_client
2246+ . import_descriptors ( self . get_wallet_name ( ) , & [ & descr_req] ) ?;
2247+ }
2248+ Ok ( ( ) )
2249+ }
21772250}
21782251
21792252impl BurnchainController for BitcoinRegtestController {
@@ -2643,64 +2716,6 @@ impl BitcoinRPCRequest {
26432716 Ok ( ( ) )
26442717 }
26452718
2646- pub fn import_public_key ( config : & Config , public_key : & Secp256k1PublicKey ) -> RPCResult < ( ) > {
2647- let pkh = Hash160 :: from_data ( & public_key. to_bytes ( ) )
2648- . to_bytes ( )
2649- . to_vec ( ) ;
2650- let ( _, network_id) = config. burnchain . get_bitcoin_network ( ) ;
2651-
2652- // import both the legacy and segwit variants of this public key
2653- let mut addresses = vec ! [ BitcoinAddress :: from_bytes_legacy(
2654- network_id,
2655- LegacyBitcoinAddressType :: PublicKeyHash ,
2656- & pkh,
2657- )
2658- . expect( "Public key incorrect" ) ] ;
2659-
2660- if config. miner . segwit {
2661- addresses. push (
2662- BitcoinAddress :: from_bytes_segwit_p2wpkh ( network_id, & pkh)
2663- . expect ( "Public key incorrect" ) ,
2664- ) ;
2665- }
2666-
2667- for address in addresses. into_iter ( ) {
2668- debug ! (
2669- "Import address {address} for public key {}" ,
2670- public_key. to_hex( )
2671- ) ;
2672-
2673- let payload = BitcoinRPCRequest {
2674- method : "getdescriptorinfo" . to_string ( ) ,
2675- params : vec ! [ format!( "addr({address})" ) . into( ) ] ,
2676- id : "stacks" . to_string ( ) ,
2677- jsonrpc : "2.0" . to_string ( ) ,
2678- } ;
2679-
2680- let result = BitcoinRPCRequest :: send ( config, payload) ?;
2681- let checksum = result
2682- . get ( "result" )
2683- . and_then ( |res| res. as_object ( ) )
2684- . and_then ( |obj| obj. get ( "checksum" ) )
2685- . and_then ( |checksum_val| checksum_val. as_str ( ) )
2686- . ok_or ( RPCError :: Bitcoind ( format ! (
2687- "Did not receive an object with `checksum` from `getdescriptorinfo \" {address}\" `" ,
2688- ) ) ) ?;
2689-
2690- let payload = BitcoinRPCRequest {
2691- method : "importdescriptors" . to_string ( ) ,
2692- params : vec ! [
2693- json!( [ { "desc" : format!( "addr({address})#{checksum}" ) , "timestamp" : 0 , "internal" : true } ] ) ,
2694- ] ,
2695- id : "stacks" . to_string ( ) ,
2696- jsonrpc : "2.0" . to_string ( ) ,
2697- } ;
2698-
2699- BitcoinRPCRequest :: send ( config, payload) ?;
2700- }
2701- Ok ( ( ) )
2702- }
2703-
27042719 pub fn send ( config : & Config , payload : BitcoinRPCRequest ) -> RPCResult < serde_json:: Value > {
27052720 let request = BitcoinRPCRequest :: build_rpc_request ( config, & payload) ;
27062721 let timeout = Duration :: from_secs ( u64:: from ( config. burnchain . timeout ) ) ;
@@ -3430,6 +3445,99 @@ mod tests {
34303445 ) ;
34313446 }
34323447
3448+ #[ test]
3449+ #[ ignore]
3450+ fn test_import_public_key_ok ( ) {
3451+ if env:: var ( "BITCOIND_TEST" ) != Ok ( "1" . into ( ) ) {
3452+ return ;
3453+ }
3454+
3455+ let miner_pubkey = utils:: create_miner1_pubkey ( ) ;
3456+
3457+ let config = utils:: create_config ( ) ;
3458+
3459+ let mut btcd_controller = BitcoinCoreController :: from_stx_config ( & config) ;
3460+ btcd_controller
3461+ . start_bitcoind ( )
3462+ . expect ( "bitcoind should be started!" ) ;
3463+
3464+ let btc_controller = BitcoinRegtestController :: new ( config. clone ( ) , None ) ;
3465+ btc_controller
3466+ . create_wallet_if_dne ( )
3467+ . expect ( "Wallet should be created!" ) ;
3468+
3469+ let result = btc_controller. import_public_key ( & miner_pubkey) ;
3470+ assert ! (
3471+ result. is_ok( ) ,
3472+ "Should be ok, got err instead: {:?}" ,
3473+ result. unwrap_err( )
3474+ ) ;
3475+ }
3476+
3477+ #[ test]
3478+ #[ ignore]
3479+ fn test_import_public_key_twice_ok ( ) {
3480+ if env:: var ( "BITCOIND_TEST" ) != Ok ( "1" . into ( ) ) {
3481+ return ;
3482+ }
3483+
3484+ let miner_pubkey = utils:: create_miner1_pubkey ( ) ;
3485+
3486+ let config = utils:: create_config ( ) ;
3487+
3488+ let mut btcd_controller = BitcoinCoreController :: from_stx_config ( & config) ;
3489+ btcd_controller
3490+ . start_bitcoind ( )
3491+ . expect ( "bitcoind should be started!" ) ;
3492+
3493+ let btc_controller = BitcoinRegtestController :: new ( config. clone ( ) , None ) ;
3494+ btc_controller
3495+ . create_wallet_if_dne ( )
3496+ . expect ( "Wallet should be created!" ) ;
3497+
3498+ btc_controller
3499+ . import_public_key ( & miner_pubkey)
3500+ . expect ( "Import should be ok: first time!" ) ;
3501+
3502+ //ok, but it is basically a no-op
3503+ let result = btc_controller. import_public_key ( & miner_pubkey) ;
3504+ assert ! (
3505+ result. is_ok( ) ,
3506+ "Should be ok, got err instead: {:?}" ,
3507+ result. unwrap_err( )
3508+ ) ;
3509+ }
3510+
3511+ #[ test]
3512+ #[ ignore]
3513+ fn test_import_public_key_segwit_ok ( ) {
3514+ if env:: var ( "BITCOIND_TEST" ) != Ok ( "1" . into ( ) ) {
3515+ return ;
3516+ }
3517+
3518+ let miner_pubkey = utils:: create_miner1_pubkey ( ) ;
3519+
3520+ let mut config = utils:: create_config ( ) ;
3521+ config. miner . segwit = true ;
3522+
3523+ let mut btcd_controller = BitcoinCoreController :: from_stx_config ( & config) ;
3524+ btcd_controller
3525+ . start_bitcoind ( )
3526+ . expect ( "bitcoind should be started!" ) ;
3527+
3528+ let btc_controller = BitcoinRegtestController :: new ( config. clone ( ) , None ) ;
3529+ btc_controller
3530+ . create_wallet_if_dne ( )
3531+ . expect ( "Wallet should be created!" ) ;
3532+
3533+ let result = btc_controller. import_public_key ( & miner_pubkey) ;
3534+ assert ! (
3535+ result. is_ok( ) ,
3536+ "Should be ok, got err instead: {:?}" ,
3537+ result. unwrap_err( )
3538+ ) ;
3539+ }
3540+
34333541 /// Tests related to Leader Block Commit operation
34343542 mod leader_commit_op {
34353543 use super :: * ;
0 commit comments