Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -18,3 +18,4 @@ log = "^0.4"
rstest = "^0.11"
bdk-testutils = "^0.4"
bdk = { version = "0.23", default-features = true }
electrsd = { version = "0.20", features = ["bitcoind_22_0", "electrs_0_9_1"] }
37 changes: 22 additions & 15 deletions tests/mempool.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
mod regtestenv;
use bdk::bitcoin::Network;
use bdk::blockchain::{electrum::ElectrumBlockchain, Blockchain, GetHeight};
use bdk::database::memory::MemoryDatabase;
Expand All @@ -6,27 +7,27 @@ use bdk::wallet::{AddressIndex, SyncOptions, Wallet};
use bdk::Error;
use bdk::SignOptions;
use bdk_reserves::reserves::*;
use regtestenv::RegTestEnv;

fn construct_wallet(
desc: &str,
network: Network,
) -> Result<(Wallet<MemoryDatabase>, ElectrumBlockchain), Error> {
let client = Client::new("ssl://electrum.blockstream.info:60002")?;
fn construct_wallet(desc: &str, network: Network) -> Result<Wallet<MemoryDatabase>, Error> {
let wallet = Wallet::new(desc, None, network, MemoryDatabase::default())?;

let blockchain = ElectrumBlockchain::from(client);
wallet.sync(&blockchain, SyncOptions::default())?;

Ok((wallet, blockchain))
Ok(wallet)
}

#[test]
fn unconfirmed() -> Result<(), ProofError> {
let (wallet, blockchain) = construct_wallet(
let wallet = construct_wallet(
"wpkh(cTTgG6x13nQjAeECaCaDrjrUdcjReZBGspcmNavsnSRyXq7zXT7r)",
Network::Testnet,
Network::Regtest,
)?;

let regtestenv = RegTestEnv::new();
regtestenv.generate(&[&wallet]);
let client = Client::new(regtestenv.electrum_url()).unwrap();
let blockchain = ElectrumBlockchain::from(client);
wallet.sync(&blockchain, SyncOptions::default())?;

let balance = wallet.get_balance()?;
assert!(
balance.confirmed > 10_000,
Expand All @@ -36,7 +37,7 @@ fn unconfirmed() -> Result<(), ProofError> {
let addr = wallet.get_address(AddressIndex::New).unwrap();
assert_eq!(
addr.to_string(),
"tb1qexxes4qzr3m6a6mcqrp0d4xexagw08fgxv898e"
"bcrt1qexxes4qzr3m6a6mcqrp0d4xexagw08fgy97gss"
);

let mut builder = wallet.build_tx();
Expand Down Expand Up @@ -74,12 +75,18 @@ fn unconfirmed() -> Result<(), ProofError> {
#[test]
#[should_panic(expected = "NonSpendableInput")]
fn confirmed() {
let (wallet, blockchain) = construct_wallet(
let wallet = construct_wallet(
"wpkh(cTTgG6x13nQjAeECaCaDrjrUdcjReZBGspcmNavsnSRyXq7zXT7r)",
Network::Testnet,
Network::Regtest,
)
.unwrap();

let regtestenv = RegTestEnv::new();
regtestenv.generate(&[&wallet]);
let client = Client::new(regtestenv.electrum_url()).unwrap();
let blockchain = ElectrumBlockchain::from(client);
wallet.sync(&blockchain, SyncOptions::default()).unwrap();

let balance = wallet.get_balance().unwrap();
assert!(
balance.confirmed > 10_000,
Expand All @@ -89,7 +96,7 @@ fn confirmed() {
let addr = wallet.get_address(AddressIndex::New).unwrap();
assert_eq!(
addr.to_string(),
"tb1qexxes4qzr3m6a6mcqrp0d4xexagw08fgxv898e"
"bcrt1qexxes4qzr3m6a6mcqrp0d4xexagw08fgy97gss"
);

let mut builder = wallet.build_tx();
Expand Down
83 changes: 44 additions & 39 deletions tests/multi_sig.rs
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
mod regtestenv;
use bdk::bitcoin::secp256k1::Secp256k1;
use bdk::bitcoin::util::key::{PrivateKey, PublicKey};
use bdk::bitcoin::util::psbt::PartiallySignedTransaction as PSBT;
use bdk::bitcoin::Network;
use bdk::blockchain::electrum::ElectrumBlockchain;
use bdk::database::memory::MemoryDatabase;
use bdk::electrum_client::Client;
use bdk::wallet::{AddressIndex, SyncOptions, Wallet};
use bdk::wallet::{AddressIndex, Wallet};
use bdk::Error;
use bdk::SignOptions;
use bdk_reserves::reserves::*;
use regtestenv::RegTestEnv;
use rstest::rstest;

enum MultisigType {
Expand Down Expand Up @@ -45,22 +45,18 @@ fn construct_multisig_wallet(
desc
}) + &postfix;

let client = Client::new("ssl://electrum.blockstream.info:60002")?;
let wallet = Wallet::new(&desc, None, Network::Testnet, MemoryDatabase::default())?;

let blockchain = ElectrumBlockchain::from(client);
wallet.sync(&blockchain, SyncOptions::default())?;
let wallet = Wallet::new(&desc, None, Network::Regtest, MemoryDatabase::default())?;

Ok(wallet)
}

#[rstest]
#[case(
#[case::wsh(
MultisigType::Wsh,
"tb1qnmhmxkaqqz4lrruhew5mk6zqr0ezstn3stj6c3r2my6hgkescm0sg3qc0r"
"bcrt1qnmhmxkaqqz4lrruhew5mk6zqr0ezstn3stj6c3r2my6hgkescm0s9g276e"
)]
#[case(MultisigType::ShWsh, "2NDTiUegP4NwKMnxXm6KdCL1B1WHamhZHC1")]
#[case(MultisigType::P2sh, "2N7yrzYXgQzNQQuHNTjcP3iwpzFVsqe6non")]
#[case::shwsh(MultisigType::ShWsh, "2NDTiUegP4NwKMnxXm6KdCL1B1WHamhZHC1")]
#[case::p2sh(MultisigType::P2sh, "2N7yrzYXgQzNQQuHNTjcP3iwpzFVsqe6non")]
fn test_proof_multisig(
#[case] script_type: MultisigType,
#[case] expected_address: &'static str,
Expand All @@ -79,30 +75,38 @@ fn test_proof_multisig(
];
pubkeys.sort_by_key(|item| item.to_string());

let wallet1 = construct_multisig_wallet(&signer1, &pubkeys, &script_type)?;
let wallet2 = construct_multisig_wallet(&signer2, &pubkeys, &script_type)?;
let wallet3 = construct_multisig_wallet(&signer3, &pubkeys, &script_type)?;
assert_eq!(
wallet1.get_address(AddressIndex::New)?.to_string(),
expected_address
);
assert_eq!(
wallet2.get_address(AddressIndex::New)?.to_string(),
expected_address
);
assert_eq!(
wallet3.get_address(AddressIndex::New)?.to_string(),
expected_address
);
let balance = wallet1.get_balance()?;
assert!(
(410000..=420000).contains(&balance.confirmed),
"balance is {} but should be between 410000 and 420000",
balance
);
let wallets = [
construct_multisig_wallet(&signer1, &pubkeys, &script_type)?,
construct_multisig_wallet(&signer2, &pubkeys, &script_type)?,
construct_multisig_wallet(&signer3, &pubkeys, &script_type)?,
];

wallets.iter().enumerate().for_each(|(i, wallet)| {
let addr = wallet.get_address(AddressIndex::New).unwrap().to_string();
assert!(
addr == expected_address,
"Wallet {} address is {} instead of {}",
i,
addr,
expected_address
);
});

let regtestenv = RegTestEnv::new();
regtestenv.generate(&[&wallets[0], &wallets[1], &wallets[2]]);

wallets.iter().enumerate().for_each(|(i, wallet)| {
let balance = wallet.get_balance().unwrap();
assert!(
(4_999_999_256..=4_999_999_596).contains(&balance.confirmed),
"balance of wallet {} is {} but should be between 4'999'999'256 and 4'999'999'596",
i,
balance
);
});

let message = "All my precious coins";
let mut psbt = wallet1.create_proof(message)?;
let mut psbt = wallets[2].create_proof(message)?;
let num_inp = psbt.inputs.len();
assert!(
num_inp > 1,
Expand Down Expand Up @@ -131,33 +135,34 @@ fn test_proof_multisig(
remove_partial_sigs: false,
..Default::default()
};
let finalized = wallet1.sign(&mut psbt, signopts.clone())?;
let finalized = wallets[0].sign(&mut psbt, signopts.clone())?;
assert_eq!(count_signatures(&psbt), (num_inp - 1, 1, 0));
assert!(!finalized);

let finalized = wallet2.sign(&mut psbt, signopts.clone())?;
let finalized = wallets[1].sign(&mut psbt, signopts.clone())?;
assert_eq!(
count_signatures(&psbt),
((num_inp - 1) * 2, num_inp, num_inp - 1)
);
assert!(finalized);

// 2 signatures are enough. Just checking what happens...
let finalized = wallet3.sign(&mut psbt, signopts.clone())?;
let finalized = wallets[2].sign(&mut psbt, signopts.clone())?;
assert_eq!(
count_signatures(&psbt),
((num_inp - 1) * 2, num_inp, num_inp - 1)
);
assert!(finalized);

let finalized = wallet1.finalize_psbt(&mut psbt, signopts)?;
let finalized = wallets[0].finalize_psbt(&mut psbt, signopts)?;
assert_eq!(
count_signatures(&psbt),
((num_inp - 1) * 2, num_inp, num_inp - 1)
);
assert!(finalized);

let spendable = wallet1.verify_proof(&psbt, message, None)?;
let spendable = wallets[0].verify_proof(&psbt, message, None)?;
let balance = wallets[0].get_balance()?;
assert_eq!(spendable, balance.confirmed);

Ok(())
Expand Down
121 changes: 121 additions & 0 deletions tests/regtestenv.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
use bdk::blockchain::{electrum::ElectrumBlockchain, Blockchain};
use bdk::database::memory::MemoryDatabase;
use bdk::electrum_client::Client;
use bdk::electrum_client::ElectrumApi;
use bdk::wallet::{AddressIndex, SyncOptions, Wallet};
use bdk::SignOptions;
use electrsd::bitcoind::bitcoincore_rpc::{bitcoin::Address, RpcApi};
use electrsd::bitcoind::BitcoinD;
use electrsd::ElectrsD;
use std::str::FromStr;
use std::time::Duration;

/// The environment to run a single test, while many of them can run in parallel.
pub struct RegTestEnv {
/// Instance of the bitcoin core daemon
bitcoind: BitcoinD,
/// Instance of the electrs electrum server
electrsd: ElectrsD,
}

impl RegTestEnv {
/// set up local bitcoind and electrs instances in regtest mode
pub fn new() -> Self {
let mut bitcoind_conf = electrsd::bitcoind::Conf::default();
bitcoind_conf.p2p = electrsd::bitcoind::P2P::Yes;

let bitcoind_exe = electrsd::bitcoind::downloaded_exe_path()
.expect("We should always have downloaded path");
let bitcoind = BitcoinD::with_conf(bitcoind_exe, &bitcoind_conf).unwrap();

let mut elect_conf = electrsd::Conf::default();
elect_conf.view_stderr = false; // setting this to true will lead to very verbose logging
let elect_exe =
electrsd::downloaded_exe_path().expect("We should always have downloaded path");
let electrsd = ElectrsD::with_conf(elect_exe, &bitcoind, &elect_conf).unwrap();

RegTestEnv { bitcoind, electrsd }
}

/// returns the URL where a client can connect to the embedded electrum server
pub fn electrum_url(&self) -> &str {
&self.electrsd.electrum_url
}

/// generates some blocks to have some coins to test with
pub fn generate(&self, wallets: &[&Wallet<MemoryDatabase>]) {
let addr2 = wallets[0].get_address(AddressIndex::Peek(1)).unwrap();
let addr1 = wallets[0].get_address(AddressIndex::Peek(0)).unwrap();
const MY_FOREIGN_ADDR: &str = "mpSFfNURcFTz2yJxBzRY9NhnozxeJ2AUC8";
let foreign_addr = Address::from_str(MY_FOREIGN_ADDR).unwrap();

// generate to the first receiving address of the test wallet
self.generate_to_address(10, &addr2);
// make the newly mined coins spendable
self.generate_to_address(100, &foreign_addr);

let client = Client::new(self.electrum_url()).unwrap();
let blockchain = ElectrumBlockchain::from(client);
wallets.iter().enumerate().for_each(|(i, wallet)| {
wallet.sync(&blockchain, SyncOptions::default()).unwrap();
let balance = wallet.get_balance().unwrap();
assert!(
balance.confirmed == 5_000_000_000,
"balance of wallet {} is {} but should be 5'000'000'000",
i,
balance
);
});

let mut builder = wallets[0].build_tx();
builder
.add_recipient(addr1.script_pubkey(), 1_000_000)
.fee_rate(bdk::FeeRate::from_sat_per_vb(2.0));
let (mut psbt, _) = builder.finish().unwrap();
let signopts = SignOptions {
..Default::default()
};
let finalized = wallets
.iter()
.any(|wallet| wallet.sign(&mut psbt, signopts.clone()).unwrap());
assert!(finalized);
blockchain.broadcast(&psbt.extract_tx()).unwrap();

// make the newly moved coins spendable
self.generate_to_address(6, &foreign_addr);

wallets
.iter()
.for_each(|wallet| wallet.sync(&blockchain, SyncOptions::default()).unwrap());
}

fn generate_to_address(&self, blocks: usize, address: &Address) {
let old_height = self
.electrsd
.client
.block_headers_subscribe()
.unwrap()
.height;

self.bitcoind
.client
.generate_to_address(blocks as u64, address)
.unwrap();

let header = loop {
std::thread::sleep(Duration::from_secs(1));
let header = self.electrsd.client.block_headers_subscribe().unwrap();
if header.height >= old_height + blocks {
break header;
}
};

assert_eq!(header.height, old_height + blocks);
}
}

impl Default for RegTestEnv {
fn default() -> Self {
Self::new()
}
}