3232//! ```
3333
3434use crate :: bitcoin:: consensus:: deserialize;
35+ use crate :: bitcoin:: hashes:: hex:: ToHex ;
3536use crate :: bitcoin:: { Address , Network , OutPoint , Transaction , TxOut , Txid } ;
3637use crate :: blockchain:: * ;
3738use crate :: database:: { BatchDatabase , DatabaseUtils } ;
39+ use crate :: descriptor:: get_checksum;
3840use crate :: { BlockTime , Error , FeeRate , KeychainKind , LocalUtxo , TransactionDetails } ;
3941use bitcoincore_rpc:: json:: {
4042 GetAddressInfoResultLabel , ImportMultiOptions , ImportMultiRequest ,
4143 ImportMultiRequestScriptPubkey , ImportMultiRescanSince ,
4244} ;
43- use bitcoincore_rpc:: jsonrpc:: serde_json:: Value ;
45+ use bitcoincore_rpc:: jsonrpc:: serde_json:: { json , Value } ;
4446use bitcoincore_rpc:: Auth as RpcAuth ;
4547use bitcoincore_rpc:: { Client , RpcApi } ;
4648use log:: debug;
@@ -54,6 +56,8 @@ use std::str::FromStr;
5456pub struct RpcBlockchain {
5557 /// Rpc client to the node, includes the wallet name
5658 client : Client ,
59+ /// Whether the wallet is a "descriptor" or "legacy" wallet in Core
60+ is_descriptors : bool ,
5761 /// Blockchain capabilities, cached here at startup
5862 capabilities : HashSet < Capability > ,
5963 /// Skip this many blocks of the blockchain at the first rescan, if None the rescan is done from the genesis block
@@ -177,22 +181,53 @@ impl WalletSync for RpcBlockchain {
177181 "importing {} script_pubkeys (some maybe already imported)" ,
178182 scripts_pubkeys. len( )
179183 ) ;
180- let requests: Vec < _ > = scripts_pubkeys
181- . iter ( )
182- . map ( |s| ImportMultiRequest {
183- timestamp : ImportMultiRescanSince :: Timestamp ( 0 ) ,
184- script_pubkey : Some ( ImportMultiRequestScriptPubkey :: Script ( s) ) ,
185- watchonly : Some ( true ) ,
186- ..Default :: default ( )
187- } )
188- . collect ( ) ;
189- let options = ImportMultiOptions {
190- rescan : Some ( false ) ,
191- } ;
192- // Note we use import_multi because as of bitcoin core 0.21.0 many descriptors are not supported
193- // https://bitcoindevkit.org/descriptors/#compatibility-matrix
194- //TODO maybe convenient using import_descriptor for compatible descriptor and import_multi as fallback
195- self . client . import_multi ( & requests, Some ( & options) ) ?;
184+
185+ if self . is_descriptors {
186+ // Core still doesn't support complex descriptors like BDK, but when the wallet type is
187+ // "descriptors" we should import individual addresses using `importdescriptors` rather
188+ // than `importmulti`, using the `raw()` descriptor which allows us to specify an
189+ // arbitrary script
190+ let requests = Value :: Array (
191+ scripts_pubkeys
192+ . iter ( )
193+ . map ( |s| {
194+ let desc = format ! ( "raw({})" , s. to_hex( ) ) ;
195+ json ! ( {
196+ "timestamp" : "now" ,
197+ "desc" : format!( "{}#{}" , desc, get_checksum( & desc) . unwrap( ) ) ,
198+ } )
199+ } )
200+ . collect ( ) ,
201+ ) ;
202+
203+ let res: Vec < Value > = self . client . call ( "importdescriptors" , & [ requests] ) ?;
204+ res. into_iter ( )
205+ . map ( |v| match v[ "success" ] . as_bool ( ) {
206+ Some ( true ) => Ok ( ( ) ) ,
207+ Some ( false ) => Err ( Error :: Generic (
208+ v[ "error" ] [ "message" ]
209+ . as_str ( )
210+ . unwrap_or ( "Unknown error" )
211+ . to_string ( ) ,
212+ ) ) ,
213+ _ => Err ( Error :: Generic ( "Unexpected response from Core" . to_string ( ) ) ) ,
214+ } )
215+ . collect :: < Result < Vec < _ > , _ > > ( ) ?;
216+ } else {
217+ let requests: Vec < _ > = scripts_pubkeys
218+ . iter ( )
219+ . map ( |s| ImportMultiRequest {
220+ timestamp : ImportMultiRescanSince :: Timestamp ( 0 ) ,
221+ script_pubkey : Some ( ImportMultiRequestScriptPubkey :: Script ( s) ) ,
222+ watchonly : Some ( true ) ,
223+ ..Default :: default ( )
224+ } )
225+ . collect ( ) ;
226+ let options = ImportMultiOptions {
227+ rescan : Some ( false ) ,
228+ } ;
229+ self . client . import_multi ( & requests, Some ( & options) ) ?;
230+ }
196231
197232 loop {
198233 let current_height = self . get_height ( ) ?;
@@ -369,20 +404,36 @@ impl ConfigurableBlockchain for RpcBlockchain {
369404 debug ! ( "connecting to {} auth:{:?}" , wallet_url, config. auth) ;
370405
371406 let client = Client :: new ( wallet_url. as_str ( ) , config. auth . clone ( ) . into ( ) ) ?;
407+ let rpc_version = client. version ( ) ?;
408+
372409 let loaded_wallets = client. list_wallets ( ) ?;
373410 if loaded_wallets. contains ( & wallet_name) {
374411 debug ! ( "wallet already loaded {:?}" , wallet_name) ;
412+ } else if list_wallet_dir ( & client) ?. contains ( & wallet_name) {
413+ client. load_wallet ( & wallet_name) ?;
414+ debug ! ( "wallet loaded {:?}" , wallet_name) ;
375415 } else {
376- let existing_wallets = list_wallet_dir ( & client) ?;
377- if existing_wallets. contains ( & wallet_name) {
378- client. load_wallet ( & wallet_name) ?;
379- debug ! ( "wallet loaded {:?}" , wallet_name) ;
380- } else {
416+ // pre-0.21 use legacy wallets
417+ if rpc_version < 210_000 {
381418 client. create_wallet ( & wallet_name, Some ( true ) , None , None , None ) ?;
382- debug ! ( "wallet created {:?}" , wallet_name) ;
419+ } else {
420+ // TODO: move back to api call when https://github.com/rust-bitcoin/rust-bitcoincore-rpc/issues/225 is closed
421+ let args = [
422+ Value :: String ( wallet_name. clone ( ) ) ,
423+ Value :: Bool ( true ) ,
424+ Value :: Bool ( false ) ,
425+ Value :: Null ,
426+ Value :: Bool ( false ) ,
427+ Value :: Bool ( true ) ,
428+ ] ;
429+ let _: Value = client. call ( "createwallet" , & args) ?;
383430 }
431+
432+ debug ! ( "wallet created {:?}" , wallet_name) ;
384433 }
385434
435+ let is_descriptors = is_wallet_descriptor ( & client) ?;
436+
386437 let blockchain_info = client. get_blockchain_info ( ) ?;
387438 let network = match blockchain_info. chain . as_str ( ) {
388439 "main" => Network :: Bitcoin ,
@@ -399,7 +450,6 @@ impl ConfigurableBlockchain for RpcBlockchain {
399450 }
400451
401452 let mut capabilities: HashSet < _ > = vec ! [ Capability :: FullHistory ] . into_iter ( ) . collect ( ) ;
402- let rpc_version = client. version ( ) ?;
403453 if rpc_version >= 210_000 {
404454 let info: HashMap < String , Value > = client. call ( "getindexinfo" , & [ ] ) . unwrap ( ) ;
405455 if info. contains_key ( "txindex" ) {
@@ -416,6 +466,7 @@ impl ConfigurableBlockchain for RpcBlockchain {
416466 Ok ( RpcBlockchain {
417467 client,
418468 capabilities,
469+ is_descriptors,
419470 _storage_address : storage_address,
420471 skip_blocks : config. skip_blocks ,
421472 } )
@@ -438,6 +489,20 @@ fn list_wallet_dir(client: &Client) -> Result<Vec<String>, Error> {
438489 Ok ( result. wallets . into_iter ( ) . map ( |n| n. name ) . collect ( ) )
439490}
440491
492+ /// Returns whether a wallet is legacy or descriptors by calling `getwalletinfo`.
493+ ///
494+ /// This API is mapped by bitcoincore_rpc, but it doesn't have the fields we need (either
495+ /// "descriptors" or "format") so we have to call the RPC manually
496+ fn is_wallet_descriptor ( client : & Client ) -> Result < bool , Error > {
497+ #[ derive( Deserialize ) ]
498+ struct CallResult {
499+ descriptors : Option < bool > ,
500+ }
501+
502+ let result: CallResult = client. call ( "getwalletinfo" , & [ ] ) ?;
503+ Ok ( result. descriptors . unwrap_or ( false ) )
504+ }
505+
441506/// Factory of [`RpcBlockchain`] instances, implements [`BlockchainFactory`]
442507///
443508/// Internally caches the node url and authentication params and allows getting many different [`RpcBlockchain`]
@@ -500,7 +565,7 @@ impl BlockchainFactory for RpcBlockchainFactory {
500565}
501566
502567#[ cfg( test) ]
503- #[ cfg( feature = "test-rpc" ) ]
568+ #[ cfg( any ( feature = "test-rpc" , feature = "test-rpc-legacy" ) ) ]
504569mod test {
505570 use super :: * ;
506571 use crate :: testutils:: blockchain_tests:: TestClient ;
@@ -514,7 +579,7 @@ mod test {
514579 url: test_client. bitcoind. rpc_url( ) ,
515580 auth: Auth :: Cookie { file: test_client. bitcoind. params. cookie_file. clone( ) } ,
516581 network: Network :: Regtest ,
517- wallet_name: format!( "client-wallet-test-{:? }" , std:: time:: SystemTime :: now( ) ) ,
582+ wallet_name: format!( "client-wallet-test-{}" , std:: time:: SystemTime :: now( ) . duration_since ( std :: time :: UNIX_EPOCH ) . unwrap ( ) . as_nanos ( ) ) ,
518583 skip_blocks: None ,
519584 } ;
520585 RpcBlockchain :: from_config( & config) . unwrap( )
0 commit comments