32
32
//! ```
33
33
34
34
use crate :: bitcoin:: consensus:: deserialize;
35
+ use crate :: bitcoin:: hashes:: hex:: ToHex ;
35
36
use crate :: bitcoin:: { Address , Network , OutPoint , Transaction , TxOut , Txid } ;
36
37
use crate :: blockchain:: * ;
37
38
use crate :: database:: { BatchDatabase , DatabaseUtils } ;
39
+ use crate :: descriptor:: get_checksum;
38
40
use crate :: { BlockTime , Error , FeeRate , KeychainKind , LocalUtxo , TransactionDetails } ;
39
41
use bitcoincore_rpc:: json:: {
40
42
GetAddressInfoResultLabel , ImportMultiOptions , ImportMultiRequest ,
41
43
ImportMultiRequestScriptPubkey , ImportMultiRescanSince ,
42
44
} ;
43
- use bitcoincore_rpc:: jsonrpc:: serde_json:: Value ;
45
+ use bitcoincore_rpc:: jsonrpc:: serde_json:: { json , Value } ;
44
46
use bitcoincore_rpc:: Auth as RpcAuth ;
45
47
use bitcoincore_rpc:: { Client , RpcApi } ;
46
48
use log:: debug;
@@ -54,6 +56,8 @@ use std::str::FromStr;
54
56
pub struct RpcBlockchain {
55
57
/// Rpc client to the node, includes the wallet name
56
58
client : Client ,
59
+ /// Whether the wallet is a "descriptor" or "legacy" wallet in Core
60
+ is_descriptors : bool ,
57
61
/// Blockchain capabilities, cached here at startup
58
62
capabilities : HashSet < Capability > ,
59
63
/// 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 {
177
181
"importing {} script_pubkeys (some maybe already imported)" ,
178
182
scripts_pubkeys. len( )
179
183
) ;
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
+ }
196
231
197
232
loop {
198
233
let current_height = self . get_height ( ) ?;
@@ -369,20 +404,36 @@ impl ConfigurableBlockchain for RpcBlockchain {
369
404
debug ! ( "connecting to {} auth:{:?}" , wallet_url, config. auth) ;
370
405
371
406
let client = Client :: new ( wallet_url. as_str ( ) , config. auth . clone ( ) . into ( ) ) ?;
407
+ let rpc_version = client. version ( ) ?;
408
+
372
409
let loaded_wallets = client. list_wallets ( ) ?;
373
410
if loaded_wallets. contains ( & wallet_name) {
374
411
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) ;
375
415
} 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 {
381
418
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) ?;
383
430
}
431
+
432
+ debug ! ( "wallet created {:?}" , wallet_name) ;
384
433
}
385
434
435
+ let is_descriptors = is_wallet_descriptor ( & client) ?;
436
+
386
437
let blockchain_info = client. get_blockchain_info ( ) ?;
387
438
let network = match blockchain_info. chain . as_str ( ) {
388
439
"main" => Network :: Bitcoin ,
@@ -399,7 +450,6 @@ impl ConfigurableBlockchain for RpcBlockchain {
399
450
}
400
451
401
452
let mut capabilities: HashSet < _ > = vec ! [ Capability :: FullHistory ] . into_iter ( ) . collect ( ) ;
402
- let rpc_version = client. version ( ) ?;
403
453
if rpc_version >= 210_000 {
404
454
let info: HashMap < String , Value > = client. call ( "getindexinfo" , & [ ] ) . unwrap ( ) ;
405
455
if info. contains_key ( "txindex" ) {
@@ -416,6 +466,7 @@ impl ConfigurableBlockchain for RpcBlockchain {
416
466
Ok ( RpcBlockchain {
417
467
client,
418
468
capabilities,
469
+ is_descriptors,
419
470
_storage_address : storage_address,
420
471
skip_blocks : config. skip_blocks ,
421
472
} )
@@ -438,6 +489,20 @@ fn list_wallet_dir(client: &Client) -> Result<Vec<String>, Error> {
438
489
Ok ( result. wallets . into_iter ( ) . map ( |n| n. name ) . collect ( ) )
439
490
}
440
491
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
+
441
506
/// Factory of [`RpcBlockchain`] instances, implements [`BlockchainFactory`]
442
507
///
443
508
/// Internally caches the node url and authentication params and allows getting many different [`RpcBlockchain`]
@@ -500,7 +565,7 @@ impl BlockchainFactory for RpcBlockchainFactory {
500
565
}
501
566
502
567
#[ cfg( test) ]
503
- #[ cfg( feature = "test-rpc" ) ]
568
+ #[ cfg( any ( feature = "test-rpc" , feature = "test-rpc-legacy" ) ) ]
504
569
mod test {
505
570
use super :: * ;
506
571
use crate :: testutils:: blockchain_tests:: TestClient ;
@@ -514,7 +579,7 @@ mod test {
514
579
url: test_client. bitcoind. rpc_url( ) ,
515
580
auth: Auth :: Cookie { file: test_client. bitcoind. params. cookie_file. clone( ) } ,
516
581
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 ( ) ) ,
518
583
skip_blocks: None ,
519
584
} ;
520
585
RpcBlockchain :: from_config( & config) . unwrap( )
0 commit comments