1#![cfg_attr(docsrs, feature(doc_auto_cfg))]
2#![cfg_attr(feature = "doc", cfg_attr(all(), doc = include_str!("../README.md")))]
3
4pub extern crate bitcoind_json_rpc_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::PathBuf;
13use std::process::{Child, Command, ExitStatus, Stdio};
14use std::time::Duration;
15use std::{env, fmt, fs, thread};
16
17use anyhow::Context;
18use bitcoind_json_rpc_client::client_sync::{self, Auth};
19use log::{debug, error, warn};
20use tempfile::TempDir;
21pub use {anyhow, tempfile, which};
22
23#[rustfmt::skip] #[doc(inline)]
25pub use self::{
26 client_versions::{json, Client, AddressType},
27 versions::VERSION,
28};
29
30#[derive(Debug)]
31pub struct BitcoinD {
33 process: Child,
35 pub client: Client,
37 work_dir: DataDir,
39
40 pub params: ConnectParams,
42}
43
44#[derive(Debug)]
45pub enum DataDir {
48 Persistent(PathBuf),
50 Temporary(TempDir),
52}
53
54impl DataDir {
55 fn path(&self) -> PathBuf {
57 match self {
58 Self::Persistent(path) => path.to_owned(),
59 Self::Temporary(tmp_dir) => tmp_dir.path().to_path_buf(),
60 }
61 }
62}
63
64#[derive(Debug, Clone)]
65pub struct ConnectParams {
67 pub cookie_file: PathBuf,
69 pub rpc_socket: SocketAddrV4,
71 pub p2p_socket: Option<SocketAddrV4>,
73 pub zmq_pub_raw_block_socket: Option<SocketAddrV4>,
75 pub zmq_pub_raw_tx_socket: Option<SocketAddrV4>,
77}
78
79pub struct CookieValues {
80 pub user: String,
81 pub password: String,
82}
83
84impl ConnectParams {
85 fn parse_cookie(content: String) -> Option<CookieValues> {
87 let values: Vec<_> = content.splitn(2, ':').collect();
88 let user = values.first()?.to_string();
89 let password = values.get(1)?.to_string();
90 Some(CookieValues { user, password })
91 }
92
93 pub fn get_cookie_values(&self) -> Result<Option<CookieValues>, std::io::Error> {
95 let cookie = std::fs::read_to_string(&self.cookie_file)?;
96 Ok(self::ConnectParams::parse_cookie(cookie))
97 }
98}
99
100#[derive(Debug, PartialEq, Eq, Clone)]
102pub enum P2P {
103 No,
105 Yes,
107 Connect(SocketAddrV4, bool),
111}
112
113pub enum Error {
115 Io(std::io::Error),
117 Rpc(client_sync::Error),
119 NoFeature,
121 NoEnvVar,
123 NoBitcoindExecutableFound,
126 EarlyExit(ExitStatus),
128 BothDirsSpecified,
130 RpcUserAndPasswordUsed,
133 SkipDownload,
135 NoBitcoindInstance,
137}
138
139impl fmt::Debug for Error {
140 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
141 use Error::*;
142
143 match self {
144 Io(_) => write!(f, "io::Error"), Rpc(_) => write!(f, "bitcoin_rpc::Error"),
146 NoFeature => write!(f, "Called a method requiring a feature to be set, but it's not"),
147 NoEnvVar => write!(f, "Called a method requiring env var `BITCOIND_EXE` to be set, but it's not"),
148 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`"),
149 EarlyExit(e) => write!(f, "The bitcoind process terminated early with exit code {}", e),
150 BothDirsSpecified => write!(f, "tempdir and staticdir cannot be enabled at same time in configuration options"),
151 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"),
152 SkipDownload => write!(f, "expecting an auto-downloaded executable but `BITCOIND_SKIP_DOWNLOAD` env var is set"),
153 NoBitcoindInstance => write!(f, "it appears that bitcoind is not reachable"),
154 }
155 }
156}
157
158impl std::fmt::Display for Error {
159 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "{:?}", self) }
160}
161
162impl std::error::Error for Error {
163 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
164 use Error::*;
165
166 match *self {
167 Error::Io(ref e) => Some(e),
168 Error::Rpc(ref e) => Some(e),
169 NoFeature
170 | NoEnvVar
171 | NoBitcoindExecutableFound
172 | EarlyExit(_)
173 | BothDirsSpecified
174 | RpcUserAndPasswordUsed
175 | SkipDownload
176 | NoBitcoindInstance => None,
177 }
178 }
179}
180
181const LOCAL_IP: Ipv4Addr = Ipv4Addr::new(127, 0, 0, 1);
182
183const INVALID_ARGS: [&str; 2] = ["-rpcuser", "-rpcpassword"];
184
185#[non_exhaustive]
206#[derive(Debug, PartialEq, Eq, Clone)]
207pub struct Conf<'a> {
208 pub args: Vec<&'a str>,
212
213 pub view_stdout: bool,
215
216 pub p2p: P2P,
218
219 pub network: &'a str,
222
223 pub tmpdir: Option<PathBuf>,
237
238 pub staticdir: Option<PathBuf>,
240
241 pub attempts: u8,
247
248 pub enable_zmq: bool,
250
251 pub wallet: Option<String>,
253}
254
255impl Default for Conf<'_> {
256 fn default() -> Self {
257 Conf {
258 args: vec!["-regtest", "-fallbackfee=0.0001"],
259 view_stdout: false,
260 p2p: P2P::No,
261 network: "regtest",
262 tmpdir: None,
263 staticdir: None,
264 attempts: 3,
265 enable_zmq: false,
266 wallet: Some("default".to_string()),
267 }
268 }
269}
270
271impl BitcoinD {
272 pub fn new<S: AsRef<OsStr>>(exe: S) -> anyhow::Result<BitcoinD> {
276 BitcoinD::with_conf(exe, &Conf::default())
277 }
278
279 pub fn with_conf<S: AsRef<OsStr>>(exe: S, conf: &Conf) -> anyhow::Result<BitcoinD> {
281 let tmpdir =
282 conf.tmpdir.clone().or_else(|| env::var("TEMPDIR_ROOT").map(PathBuf::from).ok());
283 let work_dir = match (&tmpdir, &conf.staticdir) {
284 (Some(_), Some(_)) => return Err(Error::BothDirsSpecified.into()),
285 (Some(tmpdir), None) => DataDir::Temporary(TempDir::new_in(tmpdir)?),
286 (None, Some(workdir)) => {
287 fs::create_dir_all(workdir)?;
288 DataDir::Persistent(workdir.to_owned())
289 }
290 (None, None) => DataDir::Temporary(TempDir::new()?),
291 };
292
293 let work_dir_path = work_dir.path();
294 if !work_dir_path.exists() {
295 panic!("work dir does not exist");
296 }
297 debug!("work_dir: {:?}", work_dir_path);
298
299 let cookie_file = work_dir_path.join(conf.network).join(".cookie");
300 let rpc_port = get_available_port()?;
301 let rpc_socket = SocketAddrV4::new(LOCAL_IP, rpc_port);
302 let rpc_url = format!("http://{}", rpc_socket);
303 debug!("rpc_url: {}", rpc_url);
304
305 let (p2p_args, p2p_socket) = match conf.p2p {
306 P2P::No => (vec!["-listen=0".to_string()], None),
307 P2P::Yes => {
308 let p2p_port = get_available_port()?;
309 let p2p_socket = SocketAddrV4::new(LOCAL_IP, p2p_port);
310 let p2p_arg = format!("-port={}", p2p_port);
311 let args = vec![p2p_arg];
312 (args, Some(p2p_socket))
313 }
314 P2P::Connect(other_node_url, listen) => {
315 let p2p_port = get_available_port()?;
316 let p2p_socket = SocketAddrV4::new(LOCAL_IP, p2p_port);
317 let p2p_arg = format!("-port={}", p2p_port);
318 let connect = format!("-connect={}", other_node_url);
319 let mut args = vec![p2p_arg, connect];
320 if listen {
321 args.push("-listen=1".to_string())
322 }
323 (args, Some(p2p_socket))
324 }
325 };
326
327 let (zmq_args, zmq_pub_raw_tx_socket, zmq_pub_raw_block_socket) = match conf.enable_zmq {
328 true => {
329 let zmq_pub_raw_tx_port = get_available_port()?;
330 let zmq_pub_raw_tx_socket = SocketAddrV4::new(LOCAL_IP, zmq_pub_raw_tx_port);
331 let zmq_pub_raw_block_port = get_available_port()?;
332 let zmq_pub_raw_block_socket = SocketAddrV4::new(LOCAL_IP, zmq_pub_raw_block_port);
333 let zmqpubrawblock_arg =
334 format!("-zmqpubrawblock=tcp://0.0.0.0:{}", zmq_pub_raw_block_port);
335 let zmqpubrawtx_arg = format!("-zmqpubrawtx=tcp://0.0.0.0:{}", zmq_pub_raw_tx_port);
336 (
337 vec![zmqpubrawtx_arg, zmqpubrawblock_arg],
338 Some(zmq_pub_raw_tx_socket),
339 Some(zmq_pub_raw_block_socket),
340 )
341 }
342 false => (vec![], None, None),
343 };
344
345 let stdout = if conf.view_stdout { Stdio::inherit() } else { Stdio::null() };
346
347 let datadir_arg = format!("-datadir={}", work_dir_path.display());
348 let rpc_arg = format!("-rpcport={}", rpc_port);
349 let default_args = [&datadir_arg, &rpc_arg];
350 let conf_args = validate_args(conf.args.clone())?;
351
352 debug!(
353 "launching {:?} with args: {:?} {:?} AND custom args: {:?}",
354 exe.as_ref(),
355 default_args,
356 p2p_args,
357 conf_args
358 );
359
360 let mut process = Command::new(exe.as_ref())
361 .args(default_args)
362 .args(&p2p_args)
363 .args(&conf_args)
364 .args(&zmq_args)
365 .stdout(stdout)
366 .spawn()
367 .with_context(|| format!("Error while executing {:?}", exe.as_ref()))?;
368
369 debug!("cookie file: {}", cookie_file.display());
370
371 if let Some(status) = process.try_wait()? {
372 if conf.attempts > 0 {
373 warn!("early exit with: {:?}. Trying to launch again ({} attempts remaining), maybe some other process used our available port", status, conf.attempts);
374 let mut conf = conf.clone();
375 conf.attempts -= 1;
376 return Self::with_conf(exe, &conf)
377 .with_context(|| format!("Remaining attempts {}", conf.attempts));
378 } else {
379 error!("early exit with: {:?}", status);
380 return Err(Error::EarlyExit(status).into());
381 }
382 }
383 thread::sleep(Duration::from_millis(1000));
384 assert!(process.stderr.is_none());
385
386 let mut i = 0;
387 let auth = Auth::CookieFile(cookie_file.clone());
388 let client_base =
389 Client::new_with_auth(&rpc_url, auth.clone()).expect("failed to create client");
390
391 let client = loop {
392 let client_result: Result<serde_json::Value, _> =
394 client_base.call("getblockchaininfo", &[]);
395
396 if client_result.is_ok() {
397 let url = match &conf.wallet {
398 Some(wallet) => {
399 debug!("trying to create/load wallet: {}", wallet);
400 match client_base.create_wallet(wallet) {
402 Ok(json) => {
403 debug!("created wallet: {}", json.name());
404 },
405 Err(e) => {
406 debug!("initial create_wallet unsuccessful, try loading instead: {:?}", e);
407 let wallet = client_base.load_wallet(wallet)?.name();
408 debug!("loaded wallet: {}", wallet);
409 }
410 }
411 format!("{}/wallet/{}", rpc_url, wallet)
412 }
413 None => rpc_url,
414 };
415 debug!("creating client with url: {}", url);
416 break Client::new_with_auth(&url, auth)?;
417 }
418
419 thread::sleep(Duration::from_millis(1000));
420
421 i += 1;
422 if i > 10 {
423 error!("failed to get a response from bitcoind");
424 return Err(Error::NoBitcoindInstance.into());
425 }
426 };
427
428 Ok(BitcoinD {
429 process,
430 client,
431 work_dir,
432 params: ConnectParams {
433 cookie_file,
434 rpc_socket,
435 p2p_socket,
436 zmq_pub_raw_block_socket,
437 zmq_pub_raw_tx_socket,
438 },
439 })
440 }
441
442 pub fn rpc_url(&self) -> String { format!("http://{}", self.params.rpc_socket) }
444
445 #[cfg(any(feature = "0_19_1", not(feature = "download")))]
446 pub fn rpc_url_with_wallet<T: AsRef<str>>(&self, wallet_name: T) -> String {
449 format!("http://{}/wallet/{}", self.params.rpc_socket, wallet_name.as_ref())
450 }
451
452 pub fn workdir(&self) -> PathBuf { self.work_dir.path() }
454
455 pub fn p2p_connect(&self, listen: bool) -> Option<P2P> {
457 self.params.p2p_socket.map(|s| P2P::Connect(s, listen))
458 }
459
460 pub fn stop(&mut self) -> anyhow::Result<ExitStatus> {
462 self.client.stop()?;
463 Ok(self.process.wait()?)
464 }
465
466 #[cfg(any(feature = "0_19_1", not(feature = "download")))]
467 pub fn create_wallet<T: AsRef<str>>(&self, wallet: T) -> anyhow::Result<Client> {
470 let _ = self.client.create_wallet(wallet.as_ref())?;
471 Ok(Client::new_with_auth(
472 &self.rpc_url_with_wallet(wallet),
473 Auth::CookieFile(self.params.cookie_file.clone()),
474 )?)
475 }
476}
477
478#[cfg(feature = "download")]
479impl BitcoinD {
480 pub fn from_downloaded() -> anyhow::Result<BitcoinD> { BitcoinD::new(downloaded_exe_path()?) }
482
483 pub fn from_downloaded_with_conf(conf: &Conf) -> anyhow::Result<BitcoinD> {
485 BitcoinD::with_conf(downloaded_exe_path()?, conf)
486 }
487}
488
489impl Drop for BitcoinD {
490 fn drop(&mut self) {
491 if let DataDir::Persistent(_) = self.work_dir {
492 let _ = self.stop();
493 }
494 let _ = self.process.kill();
495 }
496}
497
498pub fn get_available_port() -> anyhow::Result<u16> {
502 let t = TcpListener::bind(("127.0.0.1", 0))?; Ok(t.local_addr().map(|s| s.port())?)
505}
506
507impl From<std::io::Error> for Error {
508 fn from(e: std::io::Error) -> Self { Error::Io(e) }
509}
510
511impl From<client_sync::Error> for Error {
512 fn from(e: client_sync::Error) -> Self { Error::Rpc(e) }
513}
514
515#[cfg(not(feature = "download"))]
517pub fn downloaded_exe_path() -> anyhow::Result<String> { Err(Error::NoFeature.into()) }
518
519#[cfg(feature = "download")]
521pub fn downloaded_exe_path() -> anyhow::Result<String> {
522 if std::env::var_os("BITCOIND_SKIP_DOWNLOAD").is_some() {
523 return Err(Error::SkipDownload.into());
524 }
525
526 let mut path: PathBuf = env!("OUT_DIR").into();
527 path.push("bitcoin");
528 path.push(format!("bitcoin-{}", VERSION));
529 path.push("bin");
530
531 if cfg!(target_os = "windows") {
532 path.push("bitcoind.exe");
533 } else {
534 path.push("bitcoind");
535 }
536
537 Ok(format!("{}", path.display()))
538}
539
540pub fn exe_path() -> anyhow::Result<String> {
547 if let Ok(path) = std::env::var("BITCOIND_EXE") {
548 return Ok(path);
549 }
550 if let Ok(path) = downloaded_exe_path() {
551 return Ok(path);
552 }
553 which::which("bitcoind")
554 .map_err(|_| Error::NoBitcoindExecutableFound.into())
555 .map(|p| p.display().to_string())
556}
557
558pub fn validate_args(args: Vec<&str>) -> anyhow::Result<Vec<&str>> {
560 args.iter().try_for_each(|arg| {
561 if INVALID_ARGS.iter().any(|x| arg.starts_with(x)) {
563 return Err(Error::RpcUserAndPasswordUsed);
564 }
565 Ok(())
566 })?;
567
568 Ok(args)
569}
570
571#[cfg(test)]
572mod test {
573 use std::net::SocketAddrV4;
574
575 use tempfile::TempDir;
576
577 use super::*;
578 use crate::{exe_path, get_available_port, BitcoinD, Conf, LOCAL_IP, P2P};
579
580 #[test]
581 fn test_local_ip() {
582 assert_eq!("127.0.0.1", format!("{}", LOCAL_IP));
583 let port = get_available_port().unwrap();
584 let socket = SocketAddrV4::new(LOCAL_IP, port);
585 assert_eq!(format!("127.0.0.1:{}", port), format!("{}", socket));
586 }
587
588 #[test]
589 fn test_bitcoind_get_blockchain_info() {
590 let exe = init();
591 let bitcoind = BitcoinD::new(exe).unwrap();
592 let info = bitcoind.client.get_blockchain_info().unwrap();
593 assert_eq!(0, info.blocks);
594 }
595
596 #[test]
597 fn test_bitcoind() {
598 let exe = init();
599 let bitcoind = BitcoinD::new(exe).unwrap();
600 let info = bitcoind.client.get_blockchain_info().unwrap();
601 assert_eq!(0, info.blocks);
602 let address = bitcoind.client.new_address().unwrap();
603 let _ = bitcoind.client.generate_to_address(1, &address).unwrap();
604 let info = bitcoind.client.get_blockchain_info().unwrap();
605 assert_eq!(1, info.blocks);
606 }
607
608 #[test]
609 #[cfg(feature = "0_21_2")]
610 fn test_getindexinfo() {
611 let exe = init();
612 let mut conf = Conf::default();
613 conf.args.push("-txindex");
614 let bitcoind = BitcoinD::with_conf(&exe, &conf).unwrap();
615 assert!(
616 bitcoind.client.server_version().unwrap() >= 210_000,
617 "getindexinfo requires bitcoin >0.21"
618 );
619 let info: std::collections::HashMap<String, serde_json::Value> =
620 bitcoind.client.call("getindexinfo", &[]).unwrap();
621 assert!(info.contains_key("txindex"));
622 assert!(bitcoind.client.server_version().unwrap() >= 210_000);
623 }
624
625 #[test]
626 fn test_p2p() {
627 let exe = init();
628 let mut conf = Conf::<'_> { p2p: P2P::Yes, ..Default::default() };
629 conf.p2p = P2P::Yes;
630
631 let bitcoind = BitcoinD::with_conf(&exe, &conf).unwrap();
632 assert_eq!(peers_connected(&bitcoind.client), 0);
633 let mut other_conf = Conf::<'_> { p2p: bitcoind.p2p_connect(false).unwrap(), ..Default::default() };
634 other_conf.p2p = bitcoind.p2p_connect(false).unwrap();
635
636 let other_bitcoind = BitcoinD::with_conf(&exe, &other_conf).unwrap();
637 assert_eq!(peers_connected(&bitcoind.client), 1);
638 assert_eq!(peers_connected(&other_bitcoind.client), 1);
639 }
640
641 #[cfg(not(target_os = "windows"))] #[test]
643 fn test_data_persistence() {
644 let mut conf = Conf::default();
646 let datadir = TempDir::new().unwrap();
647 conf.staticdir = Some(datadir.path().to_path_buf());
648
649 let bitcoind = BitcoinD::with_conf(exe_path().unwrap(), &conf).unwrap();
653 let core_addrs = bitcoind.client.new_address().unwrap();
654 bitcoind.client.generate_to_address(101, &core_addrs).unwrap();
655 let wallet_balance_1 = bitcoind.client.get_balance().unwrap();
656 let best_block_1 = bitcoind.client.get_best_block_hash().unwrap();
657
658 drop(bitcoind);
659
660 let bitcoind = BitcoinD::with_conf(exe_path().unwrap(), &conf).unwrap();
662
663 let wallet_balance_2 = bitcoind.client.get_balance().unwrap();
664 let best_block_2 = bitcoind.client.get_best_block_hash().unwrap();
665
666 assert_eq!(best_block_1, best_block_2);
668
669 assert_eq!(wallet_balance_1, wallet_balance_2);
671 }
672
673 #[test]
674 fn test_multi_p2p() {
675 let _ = env_logger::try_init();
676 let conf_node1 = Conf::<'_> { p2p: P2P::Yes, ..Default::default() };
677 let node1 = BitcoinD::with_conf(exe_path().unwrap(), &conf_node1).unwrap();
678
679 let conf_node2 = Conf::<'_> { p2p: node1.p2p_connect(true).unwrap(), ..Default::default() };
681 let node2 = BitcoinD::with_conf(exe_path().unwrap(), &conf_node2).unwrap();
682
683 let conf_node3 = Conf::<'_> { p2p: node2.p2p_connect(false).unwrap(), ..Default::default() };
685 let node3 = BitcoinD::with_conf(exe_path().unwrap(), &conf_node3).unwrap();
686
687 let node1_peers = peers_connected(&node1.client);
689 let node2_peers = peers_connected(&node2.client);
690 let node3_peers = peers_connected(&node3.client);
691
692 assert!(node1_peers >= 1);
694 assert!(node2_peers >= 1);
695 assert_eq!(node3_peers, 1, "listen false but more than 1 peer");
696 }
697
698 #[cfg(any(feature = "0_19_1", not(feature = "download")))]
699 #[test]
700 fn test_multi_wallet() {
701 use bitcoind_json_rpc_client::bitcoin::Amount;
702
703 let exe = init();
704 let bitcoind = BitcoinD::new(exe).unwrap();
705 let alice = bitcoind.create_wallet("alice").unwrap();
706 let alice_address = alice.new_address().unwrap();
707 let bob = bitcoind.create_wallet("bob").unwrap();
708 let bob_address = bob.new_address().unwrap();
709 bitcoind.client.generate_to_address(1, &alice_address).unwrap();
710 bitcoind.client.generate_to_address(101, &bob_address).unwrap();
711
712 let balances = alice.get_balances().unwrap();
713 let alice_balances: json::GetBalances = balances;
714
715 let balances = bob.get_balances().unwrap();
716 let bob_balances: json::GetBalances = balances;
717
718 assert_eq!(
719 Amount::from_btc(50.0).unwrap(),
720 Amount::from_btc(alice_balances.mine.trusted).unwrap()
721 );
722 assert_eq!(
723 Amount::from_btc(50.0).unwrap(),
724 Amount::from_btc(bob_balances.mine.trusted).unwrap()
725 );
726 assert_eq!(
727 Amount::from_btc(5000.0).unwrap(),
728 Amount::from_btc(bob_balances.mine.immature).unwrap()
729 );
730 let _txid = alice.send_to_address(&bob_address, Amount::from_btc(1.0).unwrap()).unwrap();
731
732 let balances = alice.get_balances().unwrap();
733 let alice_balances: json::GetBalances = balances;
734
735 assert!(
736 Amount::from_btc(alice_balances.mine.trusted).unwrap()
737 < Amount::from_btc(49.0).unwrap()
738 && Amount::from_btc(alice_balances.mine.trusted).unwrap()
739 > Amount::from_btc(48.9).unwrap()
740 );
741
742 for _ in 0..30 {
744 let balances = bob.get_balances().unwrap();
745 let bob_balances: json::GetBalances = balances;
746
747 if Amount::from_btc(bob_balances.mine.untrusted_pending).unwrap().to_sat() > 0 {
748 break;
749 }
750 std::thread::sleep(std::time::Duration::from_millis(100));
751 }
752 let balances = bob.get_balances().unwrap();
753 let bob_balances: json::GetBalances = balances;
754
755 assert_eq!(
756 Amount::from_btc(1.0).unwrap(),
757 Amount::from_btc(bob_balances.mine.untrusted_pending).unwrap()
758 );
759 assert!(bitcoind.create_wallet("bob").is_err(), "wallet already exist");
760 }
761
762 #[test]
763 fn test_bitcoind_rpcuser_and_rpcpassword() {
764 let exe = init();
765
766 let mut conf = Conf::default();
767 conf.args.push("-rpcuser=bitcoind");
768 conf.args.push("-rpcpassword=bitcoind");
769
770 let bitcoind = BitcoinD::with_conf(exe, &conf);
771
772 assert!(bitcoind.is_err());
773 }
774
775 #[test]
776 fn test_bitcoind_rpcauth() {
777 let exe = init();
778
779 let mut conf = Conf::default();
780 conf.args.push("-rpcauth=bitcoind:cccd5d7fd36e55c1b8576b8077dc1b83$60b5676a09f8518dcb4574838fb86f37700cd690d99bd2fdc2ea2bf2ab80ead6");
783
784 let bitcoind = BitcoinD::with_conf(exe, &conf).unwrap();
785
786 let auth = Auth::UserPass("bitcoind".to_string(), "bitcoind".to_string());
787 let client = Client::new_with_auth(
788 format!("{}/wallet/default", bitcoind.rpc_url().as_str()).as_str(),
789 auth,
790 )
791 .unwrap();
792 let info = client.get_blockchain_info().unwrap();
793 assert_eq!(0, info.blocks);
794
795 let address = client.new_address().unwrap();
796 let _ = client.generate_to_address(1, &address).unwrap();
797 let info = bitcoind.client.get_blockchain_info().unwrap();
798 assert_eq!(1, info.blocks);
799 }
800
801 #[test]
802 fn test_get_cookie_user_and_pass() {
803 let exe = init();
804 let bitcoind = BitcoinD::new(exe).unwrap();
805
806 let user: &str = "bitcoind_user";
807 let password: &str = "bitcoind_password";
808
809 std::fs::write(&bitcoind.params.cookie_file, format!("{}:{}", user, password)).unwrap();
810
811 let result_values = bitcoind.params.get_cookie_values().unwrap().unwrap();
812
813 assert_eq!(user, result_values.user);
814 assert_eq!(password, result_values.password);
815 }
816
817 #[test]
818 fn zmq_interface_enabled() {
819 let conf = Conf::<'_> { enable_zmq: true, ..Default::default() };
820 let bitcoind = BitcoinD::with_conf(exe_path().unwrap(), &conf).unwrap();
821
822 assert!(bitcoind.params.zmq_pub_raw_tx_socket.is_some());
823 assert!(bitcoind.params.zmq_pub_raw_block_socket.is_some());
824 }
825
826 #[test]
827 fn zmq_interface_disabled() {
828 let exe = init();
829 let bitcoind = BitcoinD::new(exe).unwrap();
830
831 assert!(bitcoind.params.zmq_pub_raw_tx_socket.is_none());
832 assert!(bitcoind.params.zmq_pub_raw_block_socket.is_none());
833 }
834
835 fn peers_connected(client: &Client) -> usize {
836 let result: Vec<serde_json::Value> = client.call("getpeerinfo", &[]).unwrap();
839 result.len()
840 }
841
842 fn init() -> String {
843 let _ = env_logger::try_init();
844 exe_path().unwrap()
845 }
846}