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