Skip to content

Commit 5220d7b

Browse files
Created make_blockchain_tests macro and test module in blockchain_tests
to work on different blockchain back-ends. Co-authored-by: saikishore222 <saikishore.chsk@gmail.com>
1 parent 2c446e8 commit 5220d7b

File tree

3 files changed

+239
-3
lines changed

3 files changed

+239
-3
lines changed

src/blockchain/esplora/mod.rs

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -160,8 +160,28 @@ crate::bdk_blockchain_tests! {
160160
const DEFAULT_CONCURRENT_REQUESTS: u8 = 4;
161161

162162
#[cfg(test)]
163-
mod test {
163+
pub mod test {
164164
use super::*;
165+
use crate::make_blockchain_tests;
166+
use crate::testutils::blockchain_tests::BlockchainType;
167+
use crate::testutils::blockchain_tests::TestClient;
168+
169+
pub fn get_blockchain(test_client: &TestClient) -> EsploraBlockchain {
170+
EsploraBlockchain::new(
171+
&format!(
172+
"http://{}",
173+
test_client.electrsd.esplora_url.as_ref().unwrap()
174+
),
175+
20,
176+
)
177+
}
178+
179+
make_blockchain_tests![
180+
@type BlockchainType::EsploraBlockchain,
181+
@tests (
182+
test_sync_simple
183+
)
184+
];
165185

166186
#[test]
167187
fn feerate_parsing() {

src/blockchain/rpc.rs

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -566,8 +566,10 @@ impl BlockchainFactory for RpcBlockchainFactory {
566566

567567
#[cfg(test)]
568568
#[cfg(any(feature = "test-rpc", feature = "test-rpc-legacy"))]
569-
mod test {
569+
pub mod test {
570570
use super::*;
571+
use crate::make_blockchain_tests;
572+
use crate::testutils::blockchain_tests::BlockchainType;
571573
use crate::testutils::blockchain_tests::TestClient;
572574

573575
use bitcoin::Network;
@@ -586,6 +588,32 @@ mod test {
586588
}
587589
}
588590

591+
pub fn get_blockchain(test_client: &TestClient) -> RpcBlockchain {
592+
let config = RpcConfig {
593+
url: test_client.bitcoind.rpc_url(),
594+
auth: Auth::Cookie {
595+
file: test_client.bitcoind.params.cookie_file.clone(),
596+
},
597+
network: Network::Regtest,
598+
wallet_name: format!(
599+
"client-wallet-test-{}",
600+
std::time::SystemTime::now()
601+
.duration_since(std::time::UNIX_EPOCH)
602+
.unwrap()
603+
.as_nanos()
604+
),
605+
skip_blocks: None,
606+
};
607+
RpcBlockchain::from_config(&config).unwrap()
608+
}
609+
610+
make_blockchain_tests![
611+
@type BlockchainType::RpcBlockchain,
612+
@tests (
613+
test_sync_simple
614+
)
615+
];
616+
589617
fn get_factory() -> (TestClient, RpcBlockchainFactory) {
590618
let test_client = TestClient::default();
591619

src/testutils/blockchain_tests.rs

Lines changed: 189 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ use bitcoin::hashes::hex::{FromHex, ToHex};
44
use bitcoin::hashes::sha256d;
55
use bitcoin::{Address, Amount, Script, Transaction, Txid, Witness};
66
pub 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};
88
use core::str::FromStr;
99
use electrsd::bitcoind::BitcoinD;
1010
use 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

Comments
 (0)