1#![cfg_attr(docsrs, feature(doc_auto_cfg))]
2#![cfg_attr(docsrs, cfg_attr(all(), doc = include_str!("../README.md")))]
3
4pub extern crate corepc_client as client;
5
6#[rustfmt::skip]
7mod client_versions;
8mod versions;
9
10use std::ffi::OsStr;
11use std::net::{Ipv4Addr, SocketAddrV4, TcpListener};
12use std::path::{Path, PathBuf};
13use std::process::{Child, Command, ExitStatus, Stdio};
14use std::time::Duration;
15use std::{env, fmt, fs, thread};
16
17use anyhow::Context;
18use corepc_client::client_sync::{self, Auth};
19use tempfile::TempDir;
20pub use {anyhow, serde_json, tempfile, which};
21
22#[rustfmt::skip] #[doc(inline)]
24pub use self::{
25 client_versions::*,
27 versions::VERSION,
29 client::types::model as mtype, };
32
33#[derive(Debug)]
34pub struct Node {
36 process: Child,
38 pub client: Client,
40 work_dir: DataDir,
42
43 pub params: ConnectParams,
45}
46
47#[derive(Debug)]
48pub enum DataDir {
51 Persistent(PathBuf),
53 Temporary(TempDir),
55}
56
57impl DataDir {
58 fn path(&self) -> PathBuf {
60 match self {
61 Self::Persistent(path) => path.to_owned(),
62 Self::Temporary(tmp_dir) => tmp_dir.path().to_path_buf(),
63 }
64 }
65}
66
67#[derive(Debug, Clone)]
68pub struct ConnectParams {
70 pub cookie_file: PathBuf,
72 pub rpc_socket: SocketAddrV4,
74 pub p2p_socket: Option<SocketAddrV4>,
76 pub zmq_pub_raw_block_socket: Option<SocketAddrV4>,
78 pub zmq_pub_raw_tx_socket: Option<SocketAddrV4>,
80}
81
82pub struct CookieValues {
83 pub user: String,
84 pub password: String,
85}
86
87impl ConnectParams {
88 fn parse_cookie(content: String) -> Option<CookieValues> {
90 let values: Vec<_> = content.splitn(2, ':').collect();
91 let user = values.first()?.to_string();
92 let password = values.get(1)?.to_string();
93 Some(CookieValues { user, password })
94 }
95
96 pub fn get_cookie_values(&self) -> Result<Option<CookieValues>, std::io::Error> {
98 let cookie = std::fs::read_to_string(&self.cookie_file)?;
99 Ok(self::ConnectParams::parse_cookie(cookie))
100 }
101}
102
103#[derive(Debug, PartialEq, Eq, Clone)]
105pub enum P2P {
106 No,
108 Yes,
110 Connect(SocketAddrV4, bool),
114}
115
116pub enum Error {
118 Io(std::io::Error),
120 Rpc(client_sync::Error),
122 NoFeature,
124 NoEnvVar,
126 NoBitcoindExecutableFound,
129 EarlyExit(ExitStatus),
131 BothDirsSpecified,
133 RpcUserAndPasswordUsed,
136 SkipDownload,
138 NoBitcoindInstance(String),
141}
142
143impl fmt::Debug for Error {
144 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
145 use Error::*;
146
147 match self {
148 Io(_) => write!(f, "io::Error"), Rpc(_) => write!(f, "bitcoin_rpc::Error"),
150 NoFeature => write!(f, "Called a method requiring a feature to be set, but it's not"),
151 NoEnvVar => write!(f, "Called a method requiring env var `BITCOIND_EXE` to be set, but it's not"),
152 NoBitcoindExecutableFound => write!(f, "`bitcoind` executable is required, provide it with one of the following: set env var `BITCOIND_EXE` or use a feature like \"22_1\" or have `bitcoind` executable in the `PATH`"),
153 EarlyExit(e) => write!(f, "The bitcoind process terminated early with exit code {}", e),
154 BothDirsSpecified => write!(f, "tempdir and staticdir cannot be enabled at same time in configuration options"),
155 RpcUserAndPasswordUsed => write!(f, "`-rpcuser` and `-rpcpassword` cannot be used, it will be deprecated soon and it's recommended to use `-rpcauth` instead which works alongside with the default cookie authentication"),
156 SkipDownload => write!(f, "expecting an auto-downloaded executable but `BITCOIND_SKIP_DOWNLOAD` env var is set"),
157 NoBitcoindInstance(msg) => write!(f, "it appears that bitcoind is not reachable: {}", msg),
158 }
159 }
160}
161
162impl std::fmt::Display for Error {
163 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "{:?}", self) }
164}
165
166impl std::error::Error for Error {
167 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
168 use Error::*;
169
170 match *self {
171 Error::Io(ref e) => Some(e),
172 Error::Rpc(ref e) => Some(e),
173 NoFeature
174 | NoEnvVar
175 | NoBitcoindExecutableFound
176 | EarlyExit(_)
177 | BothDirsSpecified
178 | RpcUserAndPasswordUsed
179 | SkipDownload
180 | NoBitcoindInstance(_) => None,
181 }
182 }
183}
184
185const LOCAL_IP: Ipv4Addr = Ipv4Addr::new(127, 0, 0, 1);
186
187const INVALID_ARGS: [&str; 2] = ["-rpcuser", "-rpcpassword"];
188
189#[non_exhaustive]
210#[derive(Debug, PartialEq, Eq, Clone)]
211pub struct Conf<'a> {
212 pub args: Vec<&'a str>,
216
217 pub view_stdout: bool,
219
220 pub p2p: P2P,
222
223 pub network: &'a str,
226
227 pub tmpdir: Option<PathBuf>,
241
242 pub staticdir: Option<PathBuf>,
244
245 pub attempts: u8,
251
252 pub enable_zmq: bool,
254
255 pub wallet: Option<String>,
257}
258
259impl Default for Conf<'_> {
260 fn default() -> Self {
261 Conf {
262 args: vec!["-regtest", "-fallbackfee=0.0001"],
263 view_stdout: false,
264 p2p: P2P::No,
265 network: "regtest",
266 tmpdir: None,
267 staticdir: None,
268 attempts: 5,
269 enable_zmq: false,
270 wallet: Some("default".to_string()),
271 }
272 }
273}
274
275impl Node {
276 pub fn new<S: AsRef<OsStr>>(exe: S) -> anyhow::Result<Node> {
280 Node::with_conf(exe, &Conf::default())
281 }
282
283 pub fn with_conf<S: AsRef<OsStr>>(exe: S, conf: &Conf) -> anyhow::Result<Node> {
301 for attempt in 0..conf.attempts {
302 let work_dir = Self::init_work_dir(conf)?;
303 let cookie_file = work_dir.path().join(conf.network).join(".cookie");
304
305 let rpc_port = get_available_port()?;
306 let rpc_socket = SocketAddrV4::new(LOCAL_IP, rpc_port);
307 let rpc_url = format!("http://{}", rpc_socket);
308
309 let (p2p_args, p2p_socket) = Self::p2p_args(&conf.p2p)?;
310 let (zmq_args, zmq_pub_raw_tx_socket, zmq_pub_raw_block_socket) =
311 Self::zmq_args(conf.enable_zmq)?;
312
313 let stdout = if conf.view_stdout { Stdio::inherit() } else { Stdio::null() };
314
315 let datadir_arg = format!("-datadir={}", work_dir.path().display());
316 let rpc_arg = format!("-rpcport={}", rpc_port);
317 let default_args = [&datadir_arg, &rpc_arg];
318 let conf_args = validate_args(conf.args.clone())?;
319
320 let mut process = Command::new(exe.as_ref())
321 .args(default_args)
322 .args(&p2p_args)
323 .args(&conf_args)
324 .args(&zmq_args)
325 .stdout(stdout)
326 .spawn()
327 .with_context(|| format!("Error while executing {:?}", exe.as_ref()))?;
328 match process.try_wait() {
329 Ok(Some(_)) | Err(_) => {
330 let _ = process.kill();
332 continue;
333 }
334 Ok(None) => {
335 }
337 }
338
339 if Self::wait_for_cookie_file(cookie_file.as_path(), Duration::from_secs(5)).is_err() {
340 let _ = process.kill();
343 continue;
344 }
345 let auth = Auth::CookieFile(cookie_file.clone());
346
347 let client_base = Self::create_client_base(&rpc_url, &auth)?;
348 let client = match &conf.wallet {
349 Some(wallet) =>
350 match Self::create_client_wallet(&client_base, &rpc_url, &auth, wallet) {
351 Ok(client) => client,
352 Err(e) =>
353 if attempt == conf.attempts - 1 {
354 return Err(e);
355 } else {
356 let _ = process.kill();
359 continue;
360 },
361 },
362 None => client_base,
363 };
364 if Self::wait_for_client(&client, Duration::from_secs(5)).is_err() {
365 let _ = process.kill();
368 continue;
369 }
370
371 return Ok(Node {
372 process,
373 client,
374 work_dir,
375 params: ConnectParams {
376 cookie_file,
377 rpc_socket,
378 p2p_socket,
379 zmq_pub_raw_block_socket,
380 zmq_pub_raw_tx_socket,
381 },
382 });
383 }
384 Err(anyhow::anyhow!("Failed to start the node after {} attempts", conf.attempts))
385 }
386
387 fn init_work_dir(conf: &Conf) -> anyhow::Result<DataDir> {
395 let tmpdir =
396 conf.tmpdir.clone().or_else(|| env::var("TEMPDIR_ROOT").map(PathBuf::from).ok());
397 let work_dir = match (&tmpdir, &conf.staticdir) {
398 (Some(_), Some(_)) => return Err(Error::BothDirsSpecified.into()),
399 (Some(tmpdir), None) => DataDir::Temporary(TempDir::new_in(tmpdir)?),
400 (None, Some(workdir)) => {
401 fs::create_dir_all(workdir)?;
402 DataDir::Persistent(workdir.to_owned())
403 }
404 (None, None) => DataDir::Temporary(TempDir::new()?),
405 };
406 Ok(work_dir)
407 }
408
409 fn p2p_args(p2p: &P2P) -> anyhow::Result<(Vec<String>, Option<SocketAddrV4>)> {
411 match p2p {
412 P2P::No => Ok((vec!["-listen=0".to_string()], None)),
413 P2P::Yes => {
414 let p2p_port = get_available_port()?;
415 let p2p_socket = SocketAddrV4::new(LOCAL_IP, p2p_port);
416 let bind_arg = format!("-bind={}", p2p_socket);
417 let args = vec![bind_arg];
418 Ok((args, Some(p2p_socket)))
419 }
420 P2P::Connect(other_node_url, listen) => {
421 let p2p_port = get_available_port()?;
422 let p2p_socket = SocketAddrV4::new(LOCAL_IP, p2p_port);
423 let bind_arg = format!("-bind={}", p2p_socket);
424 let connect = format!("-connect={}", other_node_url);
425 let mut args = vec![bind_arg, connect];
426 if *listen {
427 args.push("-listen=1".to_string())
428 }
429 Ok((args, Some(p2p_socket)))
430 }
431 }
432 }
433
434 fn zmq_args(
441 enable_zmq: bool,
442 ) -> anyhow::Result<(Vec<String>, Option<SocketAddrV4>, Option<SocketAddrV4>)> {
443 if enable_zmq {
444 let zmq_pub_raw_tx_port = get_available_port()?;
445 let zmq_pub_raw_tx_socket = SocketAddrV4::new(LOCAL_IP, zmq_pub_raw_tx_port);
446 let zmq_pub_raw_block_port = get_available_port()?;
447 let zmq_pub_raw_block_socket = SocketAddrV4::new(LOCAL_IP, zmq_pub_raw_block_port);
448 let zmqpubrawblock_arg =
449 format!("-zmqpubrawblock=tcp://0.0.0.0:{}", zmq_pub_raw_block_port);
450 let zmqpubrawtx_arg = format!("-zmqpubrawtx=tcp://0.0.0.0:{}", zmq_pub_raw_tx_port);
451 Ok((
452 vec![zmqpubrawtx_arg, zmqpubrawblock_arg],
453 Some(zmq_pub_raw_tx_socket),
454 Some(zmq_pub_raw_block_socket),
455 ))
456 } else {
457 Ok((vec![], None, None))
458 }
459 }
460
461 fn wait_for_cookie_file(cookie_file: &Path, timeout: Duration) -> anyhow::Result<()> {
463 let start = std::time::Instant::now();
464 while start.elapsed() < timeout {
465 if cookie_file.exists() {
466 return Ok(());
467 }
468 thread::sleep(Duration::from_millis(200));
469 }
470 Err(anyhow::anyhow!("timeout waiting for cookie file: {}", cookie_file.display()))
471 }
472
473 fn wait_for_client(client: &Client, timeout: Duration) -> anyhow::Result<()> {
475 let start = std::time::Instant::now();
476 while start.elapsed() < timeout {
477 if client.call::<serde_json::Value>("getblockchaininfo", &[]).is_ok() {
479 return Ok(());
480 }
481 thread::sleep(Duration::from_millis(200));
482 }
483 Err(anyhow::anyhow!("timeout waiting for client to be ready"))
484 }
485
486 fn create_client_base(rpc_url: &str, auth: &Auth) -> anyhow::Result<Client> {
490 for _ in 0..10 {
491 if let Ok(client) = Client::new_with_auth(rpc_url, auth.clone()) {
492 return Ok(client);
493 }
494 thread::sleep(Duration::from_millis(200));
495 }
496 Client::new_with_auth(rpc_url, auth.clone())
497 .map_err(|e| Error::NoBitcoindInstance(e.to_string()).into())
498 }
499
500 fn create_client_wallet(
507 client_base: &Client,
508 rpc_url: &str,
509 auth: &Auth,
510 wallet: &str,
511 ) -> anyhow::Result<Client> {
512 for _ in 0..10 {
513 if client_base.create_wallet(wallet).is_ok() || client_base.load_wallet(wallet).is_ok()
515 {
516 let url = format!("{}/wallet/{}", rpc_url, wallet);
517 return Client::new_with_auth(&url, auth.clone())
518 .map_err(|e| Error::NoBitcoindInstance(e.to_string()).into());
519 }
520 thread::sleep(Duration::from_millis(200));
521 }
522 Err(Error::NoBitcoindInstance("Could not create or load wallet".to_string()).into())
523 }
524
525 pub fn rpc_url(&self) -> String { format!("http://{}", self.params.rpc_socket) }
527
528 pub fn rpc_url_with_wallet<T: AsRef<str>>(&self, wallet_name: T) -> String {
531 format!("http://{}/wallet/{}", self.params.rpc_socket, wallet_name.as_ref())
532 }
533
534 pub fn workdir(&self) -> PathBuf { self.work_dir.path() }
536
537 pub fn p2p_connect(&self, listen: bool) -> Option<P2P> {
539 self.params.p2p_socket.map(|s| P2P::Connect(s, listen))
540 }
541
542 pub fn stop(&mut self) -> anyhow::Result<ExitStatus> {
544 self.client.stop()?;
545 Ok(self.process.wait()?)
546 }
547
548 pub fn create_wallet<T: AsRef<str>>(&self, wallet: T) -> anyhow::Result<Client> {
551 let _ = self.client.create_wallet(wallet.as_ref())?;
552 Ok(Client::new_with_auth(
553 &self.rpc_url_with_wallet(wallet),
554 Auth::CookieFile(self.params.cookie_file.clone()),
555 )?)
556 }
557}
558
559#[cfg(feature = "download")]
560impl Node {
561 pub fn from_downloaded() -> anyhow::Result<Node> { Node::new(downloaded_exe_path()?) }
563
564 pub fn from_downloaded_with_conf(conf: &Conf) -> anyhow::Result<Node> {
566 Node::with_conf(downloaded_exe_path()?, conf)
567 }
568}
569
570impl Drop for Node {
571 fn drop(&mut self) {
572 if let DataDir::Persistent(_) = self.work_dir {
573 let _ = self.stop();
574 }
575 let _ = self.process.kill();
576 }
577}
578
579pub fn get_available_port() -> anyhow::Result<u16> {
583 let t = TcpListener::bind(("127.0.0.1", 0))?; Ok(t.local_addr().map(|s| s.port())?)
586}
587
588impl From<std::io::Error> for Error {
589 fn from(e: std::io::Error) -> Self { Error::Io(e) }
590}
591
592impl From<client_sync::Error> for Error {
593 fn from(e: client_sync::Error) -> Self { Error::Rpc(e) }
594}
595
596#[cfg(not(feature = "download"))]
598pub fn downloaded_exe_path() -> anyhow::Result<String> { Err(Error::NoFeature.into()) }
599
600#[cfg(feature = "download")]
602pub fn downloaded_exe_path() -> anyhow::Result<String> {
603 if std::env::var_os("BITCOIND_SKIP_DOWNLOAD").is_some() {
604 return Err(Error::SkipDownload.into());
605 }
606
607 let mut path: PathBuf = env!("OUT_DIR").into();
608 path.push("bitcoin");
609 path.push(format!("bitcoin-{}", VERSION));
610 path.push("bin");
611
612 if cfg!(target_os = "windows") {
613 path.push("bitcoind.exe");
614 } else {
615 path.push("bitcoind");
616 }
617
618 let path = format!("{}", path.display());
619 Ok(path)
620}
621
622pub fn exe_path() -> anyhow::Result<String> {
629 if let Ok(path) = std::env::var("BITCOIND_EXE") {
630 return Ok(path);
631 }
632 if let Ok(path) = downloaded_exe_path() {
633 return Ok(path);
634 }
635 which::which("bitcoind")
636 .map_err(|_| Error::NoBitcoindExecutableFound.into())
637 .map(|p| p.display().to_string())
638}
639
640pub fn validate_args(args: Vec<&str>) -> anyhow::Result<Vec<&str>> {
642 args.iter().try_for_each(|arg| {
643 if INVALID_ARGS.iter().any(|x| arg.starts_with(x)) {
645 return Err(Error::RpcUserAndPasswordUsed);
646 }
647 Ok(())
648 })?;
649
650 Ok(args)
651}
652
653#[cfg(test)]
654mod test {
655 use std::net::SocketAddrV4;
656
657 use tempfile::TempDir;
658
659 use super::*;
660 use crate::{exe_path, get_available_port, Conf, Node, LOCAL_IP, P2P};
661
662 #[test]
663 fn test_local_ip() {
664 assert_eq!("127.0.0.1", format!("{}", LOCAL_IP));
665 let port = get_available_port().unwrap();
666 let socket = SocketAddrV4::new(LOCAL_IP, port);
667 assert_eq!(format!("127.0.0.1:{}", port), format!("{}", socket));
668 }
669
670 #[test]
671 fn test_node_get_blockchain_info() {
672 let exe = init();
673 let node = Node::new(exe).unwrap();
674 let info = node.client.get_blockchain_info().unwrap();
675 assert_eq!(0, info.blocks);
676 }
677
678 #[test]
679 fn test_node() {
680 let exe = init();
681 let node = Node::new(exe).unwrap();
682 let info = node.client.get_blockchain_info().unwrap();
683
684 assert_eq!(0, info.blocks);
685 let address = node.client.new_address().unwrap();
686 let _ = node.client.generate_to_address(1, &address).unwrap();
687 let info = node.client.get_blockchain_info().unwrap();
688 assert_eq!(1, info.blocks);
689 }
690
691 #[test]
692 #[cfg(feature = "0_21_2")]
693 fn test_getindexinfo() {
694 let exe = init();
695 let mut conf = Conf::default();
696 conf.args.push("-txindex");
697 let node = Node::with_conf(&exe, &conf).unwrap();
698 assert!(
699 node.client.server_version().unwrap() >= 210_000,
700 "getindexinfo requires bitcoin >0.21"
701 );
702 let info: std::collections::HashMap<String, serde_json::Value> =
703 node.client.call("getindexinfo", &[]).unwrap();
704 assert!(info.contains_key("txindex"));
705 assert!(node.client.server_version().unwrap() >= 210_000);
706 }
707
708 #[test]
709 fn test_p2p() {
710 let exe = init();
711
712 let conf = Conf::<'_> { p2p: P2P::Yes, ..Default::default() };
713 let node = Node::with_conf(&exe, &conf).unwrap();
714 assert_eq!(peers_connected(&node.client), 0);
715
716 let other_conf = Conf::<'_> { p2p: node.p2p_connect(false).unwrap(), ..Default::default() };
717 let other_node = Node::with_conf(&exe, &other_conf).unwrap();
718
719 assert_eq!(peers_connected(&node.client), 1);
720 assert_eq!(peers_connected(&other_node.client), 1);
721 }
722
723 #[cfg(not(target_os = "windows"))] #[test]
725 fn test_data_persistence() {
726 let mut conf = Conf::default();
728 let datadir = TempDir::new().unwrap();
729 conf.staticdir = Some(datadir.path().to_path_buf());
730
731 let node = Node::with_conf(exe_path().unwrap(), &conf).unwrap();
735 let core_addrs = node.client.new_address().unwrap();
736 node.client.generate_to_address(101, &core_addrs).unwrap();
737 let wallet_balance_1 = node.client.get_balance().unwrap();
738 let best_block_1 = node.client.get_best_block_hash().unwrap();
739
740 drop(node);
741
742 let node = Node::with_conf(exe_path().unwrap(), &conf).unwrap();
744
745 let wallet_balance_2 = node.client.get_balance().unwrap();
746 let best_block_2 = node.client.get_best_block_hash().unwrap();
747
748 assert_eq!(best_block_1, best_block_2);
750
751 assert_eq!(wallet_balance_1, wallet_balance_2);
753 }
754
755 #[test]
756 fn test_multi_p2p() {
757 let exe = init();
758
759 let conf_node1 = Conf::<'_> { p2p: P2P::Yes, ..Default::default() };
760 let node1 = Node::with_conf(&exe, &conf_node1).unwrap();
761 assert_eq!(peers_connected(&node1.client), 0);
762
763 let conf_node2 = Conf::<'_> { p2p: node1.p2p_connect(true).unwrap(), ..Default::default() };
765 let node2 = Node::with_conf(&exe, &conf_node2).unwrap();
766
767 let conf_node3 =
769 Conf::<'_> { p2p: node2.p2p_connect(false).unwrap(), ..Default::default() };
770 let node3 = Node::with_conf(exe_path().unwrap(), &conf_node3).unwrap();
771
772 let node1_peers = peers_connected(&node1.client);
774 let node2_peers = peers_connected(&node2.client);
775 let node3_peers = peers_connected(&node3.client);
776
777 assert!(node1_peers >= 1);
779 assert!(node2_peers >= 1);
780 assert_eq!(node3_peers, 1, "listen false but more than 1 peer");
781 }
782
783 #[cfg(feature = "0_19_1")]
784 #[test]
785 fn test_multi_wallet() {
786 use corepc_client::bitcoin::Amount;
787
788 let exe = init();
789 let node = Node::new(exe).unwrap();
790 let alice = node.create_wallet("alice").unwrap();
791 let alice_address = alice.new_address().unwrap();
792 let bob = node.create_wallet("bob").unwrap();
793 let bob_address = bob.new_address().unwrap();
794 node.client.generate_to_address(1, &alice_address).unwrap();
795 node.client.generate_to_address(101, &bob_address).unwrap();
796
797 let balances = alice.get_balances().unwrap();
798 let alice_balances: vtype::GetBalances = balances;
799
800 let balances = bob.get_balances().unwrap();
801 let bob_balances: vtype::GetBalances = balances;
802
803 assert_eq!(
804 Amount::from_btc(50.0).unwrap(),
805 Amount::from_btc(alice_balances.mine.trusted).unwrap()
806 );
807 assert_eq!(
808 Amount::from_btc(50.0).unwrap(),
809 Amount::from_btc(bob_balances.mine.trusted).unwrap()
810 );
811 assert_eq!(
812 Amount::from_btc(5000.0).unwrap(),
813 Amount::from_btc(bob_balances.mine.immature).unwrap()
814 );
815 let _txid = alice.send_to_address(&bob_address, Amount::from_btc(1.0).unwrap()).unwrap();
816
817 let balances = alice.get_balances().unwrap();
818 let alice_balances: vtype::GetBalances = balances;
819
820 assert!(
821 Amount::from_btc(alice_balances.mine.trusted).unwrap()
822 < Amount::from_btc(49.0).unwrap()
823 && Amount::from_btc(alice_balances.mine.trusted).unwrap()
824 > Amount::from_btc(48.9).unwrap()
825 );
826
827 for _ in 0..30 {
829 let balances = bob.get_balances().unwrap();
830 let bob_balances: vtype::GetBalances = balances;
831
832 if Amount::from_btc(bob_balances.mine.untrusted_pending).unwrap().to_sat() > 0 {
833 break;
834 }
835 std::thread::sleep(std::time::Duration::from_millis(100));
836 }
837 let balances = bob.get_balances().unwrap();
838 let bob_balances: vtype::GetBalances = balances;
839
840 assert_eq!(
841 Amount::from_btc(1.0).unwrap(),
842 Amount::from_btc(bob_balances.mine.untrusted_pending).unwrap()
843 );
844 assert!(node.create_wallet("bob").is_err(), "wallet already exist");
845 }
846
847 #[test]
848 fn test_node_rpcuser_and_rpcpassword() {
849 let exe = init();
850
851 let mut conf = Conf::default();
852 conf.args.push("-rpcuser=bitcoind");
853 conf.args.push("-rpcpassword=bitcoind");
854
855 let node = Node::with_conf(exe, &conf);
856
857 assert!(node.is_err());
858 }
859
860 #[test]
861 fn test_node_rpcauth() {
862 let exe = init();
863
864 let mut conf = Conf::default();
865 conf.args.push("-rpcauth=bitcoind:cccd5d7fd36e55c1b8576b8077dc1b83$60b5676a09f8518dcb4574838fb86f37700cd690d99bd2fdc2ea2bf2ab80ead6");
868
869 let node = Node::with_conf(exe, &conf).unwrap();
870
871 let auth = Auth::UserPass("bitcoind".to_string(), "bitcoind".to_string());
872 let client = Client::new_with_auth(
873 format!("{}/wallet/default", node.rpc_url().as_str()).as_str(),
874 auth,
875 )
876 .unwrap();
877 let info = client.get_blockchain_info().unwrap();
878 assert_eq!(0, info.blocks);
879
880 let address = client.new_address().unwrap();
881 let _ = client.generate_to_address(1, &address).unwrap();
882 let info = node.client.get_blockchain_info().unwrap();
883 assert_eq!(1, info.blocks);
884 }
885
886 #[test]
887 fn test_get_cookie_user_and_pass() {
888 let exe = init();
889 let node = Node::new(exe).unwrap();
890
891 let user: &str = "bitcoind_user";
892 let password: &str = "bitcoind_password";
893
894 std::fs::write(&node.params.cookie_file, format!("{}:{}", user, password)).unwrap();
895
896 let result_values = node.params.get_cookie_values().unwrap().unwrap();
897
898 assert_eq!(user, result_values.user);
899 assert_eq!(password, result_values.password);
900 }
901
902 #[test]
903 fn zmq_interface_enabled() {
904 let conf = Conf::<'_> { enable_zmq: true, ..Default::default() };
905 let node = Node::with_conf(exe_path().unwrap(), &conf).unwrap();
906
907 assert!(node.params.zmq_pub_raw_tx_socket.is_some());
908 assert!(node.params.zmq_pub_raw_block_socket.is_some());
909 }
910
911 #[test]
912 fn zmq_interface_disabled() {
913 let exe = init();
914 let node = Node::new(exe).unwrap();
915
916 assert!(node.params.zmq_pub_raw_tx_socket.is_none());
917 assert!(node.params.zmq_pub_raw_block_socket.is_none());
918 }
919
920 fn peers_connected(client: &Client) -> usize {
921 let json = client.get_peer_info().expect("get_peer_info");
922 json.0.len()
923 }
924
925 fn init() -> String {
926 let _ = env_logger::try_init();
927 exe_path().unwrap()
928 }
929}