@@ -4,7 +4,7 @@ use bitcoin::hashes::hex::{FromHex, ToHex};
44use bitcoin:: hashes:: sha256d;
55use bitcoin:: { Address , Amount , Script , Transaction , Txid , Witness } ;
66pub use bitcoincore_rpc:: bitcoincore_rpc_json:: AddressType ;
7- pub use bitcoincore_rpc:: { Auth , Client as RpcClient , RpcApi } ;
7+ pub use bitcoincore_rpc:: { Client as RpcClient , RpcApi } ;
88use core:: str:: FromStr ;
99use electrsd:: bitcoind:: BitcoinD ;
1010use electrsd:: { bitcoind, ElectrsD } ;
@@ -358,6 +358,194 @@ where
358358 }
359359}
360360
361+ use crate :: blockchain:: AnyBlockchain ;
362+
363+ #[ allow( dead_code) ]
364+ pub enum BlockchainType {
365+ #[ cfg( any( feature = "test-rpc" , feature = "test-rpc-legacy" ) ) ]
366+ RpcBlockchain ,
367+ #[ cfg( feature = "test-esplora" ) ]
368+ EsploraBlockchain ,
369+ }
370+
371+ #[ cfg( test) ]
372+ pub mod test {
373+ use std:: any:: Any ;
374+
375+ use crate :: bitcoin:: { Network , Transaction } ;
376+ #[ cfg( feature = "test-esplora" ) ]
377+ use crate :: blockchain:: EsploraBlockchain ;
378+ #[ cfg( any( feature = "test-rpc" , feature = "test-rpc-legacy" ) ) ]
379+ use crate :: blockchain:: { rpc:: Auth , ConfigurableBlockchain , RpcBlockchain , RpcConfig } ;
380+ use crate :: database:: MemoryDatabase ;
381+ use crate :: testutils;
382+ use crate :: testutils:: blockchain_tests:: TestClient ;
383+ use crate :: types:: KeychainKind ;
384+ use crate :: { FeeRate , SyncOptions , Wallet } ;
385+
386+ use super :: * ;
387+
388+ fn get_wallet_from_descriptors (
389+ descriptors : & ( String , Option < String > ) ,
390+ ) -> Wallet < MemoryDatabase > {
391+ Wallet :: new (
392+ & descriptors. 0 . to_string ( ) ,
393+ descriptors. 1 . as_ref ( ) ,
394+ Network :: Regtest ,
395+ MemoryDatabase :: new ( ) ,
396+ )
397+ . unwrap ( )
398+ }
399+
400+ #[ allow( dead_code) ]
401+ enum WalletType {
402+ WpkhSingleSig ,
403+ TaprootKeySpend ,
404+ TaprootScriptSpend ,
405+ TaprootScriptSpend2 ,
406+ TaprootScriptSpend3 ,
407+ }
408+
409+ fn init_wallet (
410+ ty : WalletType ,
411+ chain_type : BlockchainType ,
412+ ) -> (
413+ Wallet < MemoryDatabase > ,
414+ AnyBlockchain ,
415+ ( String , Option < String > ) ,
416+ TestClient ,
417+ ) {
418+ let _ = env_logger:: try_init ( ) ;
419+
420+ let descriptors = match ty {
421+ WalletType :: WpkhSingleSig => testutils ! {
422+ @descriptors ( "wpkh(Alice)" ) ( "wpkh(Alice)" ) ( @keys ( "Alice" => ( @generate_xprv "/44'/0'/0'/0/*" , "/44'/0'/0'/1/*" ) ) )
423+ } ,
424+ WalletType :: TaprootKeySpend => testutils ! {
425+ @descriptors ( "tr(Alice)" ) ( "tr(Alice)" ) ( @keys ( "Alice" => ( @generate_xprv "/44'/0'/0'/0/*" , "/44'/0'/0'/1/*" ) ) )
426+ } ,
427+ WalletType :: TaprootScriptSpend => testutils ! {
428+ @descriptors ( "tr(Key,and_v(v:pk(Script),older(6)))" ) ( "tr(Key,and_v(v:pk(Script),older(6)))" ) ( @keys ( "Key" => ( @literal "30e14486f993d5a2d222770e97286c56cec5af115e1fb2e0065f476a0fcf8788" ) , "Script" => ( @generate_xprv "/0/*" , "/1/*" ) ) )
429+ } ,
430+ WalletType :: TaprootScriptSpend2 => testutils ! {
431+ @descriptors ( "tr(Alice,pk(Bob))" ) ( "tr(Alice,pk(Bob))" ) ( @keys ( "Alice" => ( @literal "30e14486f993d5a2d222770e97286c56cec5af115e1fb2e0065f476a0fcf8788" ) , "Bob" => ( @generate_xprv "/0/*" , "/1/*" ) ) )
432+ } ,
433+ WalletType :: TaprootScriptSpend3 => testutils ! {
434+ @descriptors ( "tr(Alice,{pk(Bob),pk(Carol)})" ) ( "tr(Alice,{pk(Bob),pk(Carol)})" ) ( @keys ( "Alice" => ( @literal "30e14486f993d5a2d222770e97286c56cec5af115e1fb2e0065f476a0fcf8788" ) , "Bob" => ( @generate_xprv "/0/*" , "/1/*" ) , "Carol" => ( @generate_xprv "/0/*" , "/1/*" ) ) )
435+ } ,
436+ } ;
437+
438+ let test_client = TestClient :: default ( ) ;
439+ let blockchain: AnyBlockchain = match chain_type {
440+ #[ cfg( any( feature = "test-rpc" , feature = "test-rpc-legacy" ) ) ]
441+ BlockchainType :: RpcBlockchain => {
442+ let config = RpcConfig {
443+ url : test_client. bitcoind . rpc_url ( ) ,
444+ auth : Auth :: Cookie {
445+ file : test_client. bitcoind . params . cookie_file . clone ( ) ,
446+ } ,
447+ network : Network :: Regtest ,
448+ wallet_name : format ! (
449+ "client-wallet-test-{}" ,
450+ std:: time:: SystemTime :: now( )
451+ . duration_since( std:: time:: UNIX_EPOCH )
452+ . unwrap( )
453+ . as_nanos( )
454+ ) ,
455+ skip_blocks : None ,
456+ } ;
457+ AnyBlockchain :: Rpc ( Box :: new ( RpcBlockchain :: from_config ( & config) . unwrap ( ) ) )
458+ }
459+
460+ #[ cfg( feature = "test-esplora" ) ]
461+ BlockchainType :: EsploraBlockchain => {
462+ AnyBlockchain :: Esplora ( Box :: new ( EsploraBlockchain :: new (
463+ & format ! (
464+ "http://{}" ,
465+ test_client. electrsd. esplora_url. as_ref( ) . unwrap( )
466+ ) ,
467+ 20 ,
468+ ) ) )
469+ }
470+ } ;
471+ let wallet = get_wallet_from_descriptors ( & descriptors) ;
472+
473+ // rpc need to call import_multi before receiving any tx, otherwise will not see tx in the mempool
474+ #[ cfg( any( feature = "test-rpc" , feature = "test-rpc-legacy" ) ) ]
475+ wallet. sync ( & blockchain, SyncOptions :: default ( ) ) . unwrap ( ) ;
476+
477+ ( wallet, blockchain, descriptors, test_client)
478+ }
479+
480+ fn init_single_sig (
481+ chain_type : BlockchainType ,
482+ ) -> (
483+ Wallet < MemoryDatabase > ,
484+ AnyBlockchain ,
485+ ( String , Option < String > ) ,
486+ TestClient ,
487+ ) {
488+ init_wallet ( WalletType :: WpkhSingleSig , chain_type)
489+ }
490+
491+ pub fn test_sync_simple ( chain_type : BlockchainType ) {
492+ use crate :: database:: Database ;
493+ use std:: ops:: Deref ;
494+
495+ let ( wallet, blockchain, descriptors, mut test_client) = init_single_sig ( chain_type) ;
496+
497+ let tx = testutils ! {
498+ @tx ( ( @external descriptors, 0 ) => 50_000 )
499+ } ;
500+ println ! ( "{:?}" , tx) ;
501+ let txid = test_client. receive ( tx) ;
502+
503+ // the RPC blockchain needs to call `sync()` during initialization to import the
504+ // addresses (see `init_single_sig()`), so we skip this assertion
505+ #[ cfg( not( any( feature = "test-rpc" , feature = "test-rpc-legacy" ) ) ) ]
506+ assert ! (
507+ wallet. database( ) . deref( ) . get_sync_time( ) . unwrap( ) . is_none( ) ,
508+ "initial sync_time not none"
509+ ) ;
510+
511+ wallet. sync ( & blockchain, SyncOptions :: default ( ) ) . unwrap ( ) ;
512+ assert ! (
513+ wallet. database( ) . deref( ) . get_sync_time( ) . unwrap( ) . is_some( ) ,
514+ "sync_time hasn't been updated"
515+ ) ;
516+
517+ assert_eq ! ( wallet. get_balance( ) . unwrap( ) , 50_000 , "incorrect balance" ) ;
518+ assert_eq ! (
519+ wallet. list_unspent( ) . unwrap( ) [ 0 ] . keychain,
520+ KeychainKind :: External ,
521+ "incorrect keychain kind"
522+ ) ;
523+
524+ let list_tx_item = & wallet. list_transactions ( false ) . unwrap ( ) [ 0 ] ;
525+ assert_eq ! ( list_tx_item. txid, txid, "incorrect txid" ) ;
526+ assert_eq ! ( list_tx_item. received, 50_000 , "incorrect received" ) ;
527+ assert_eq ! ( list_tx_item. sent, 0 , "incorrect sent" ) ;
528+ assert_eq ! (
529+ list_tx_item. confirmation_time, None ,
530+ "incorrect confirmation time"
531+ ) ;
532+ }
533+ }
534+
535+ #[ macro_export]
536+ #[ doc( hidden) ]
537+ macro_rules! make_blockchain_tests {
538+ ( @type $chain_type: expr, @tests ( $( $x: tt) , + $( , ) ? ) ) => {
539+ $(
540+ #[ test]
541+ fn $x( )
542+ {
543+ $crate:: testutils:: blockchain_tests:: test:: $x( $chain_type) ;
544+ }
545+ ) +
546+ } ;
547+ }
548+
361549/// This macro runs blockchain tests against a `Blockchain` implementation. It requires access to a
362550/// Bitcoin core wallet via RPC. At the moment you have to dig into the code yourself and look at
363551/// the setup required to run the tests yourself.
0 commit comments