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::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 log::{debug, error, warn};
20use tempfile::TempDir;
21pub use {anyhow, serde_json, tempfile, which};
22
23#[rustfmt::skip] #[doc(inline)]
25pub use self::{
26 client_versions::*,
28 versions::VERSION,
30 client::types::model as mtype, };
33
34#[derive(Debug)]
35pub struct Node {
37 process: Child,
39 pub client: Client,
41 work_dir: DataDir,
43
44 pub params: ConnectParams,
46}
47
48#[derive(Debug)]
49pub enum DataDir {
52 Persistent(PathBuf),
54 Temporary(TempDir),
56}
57
58impl DataDir {
59 fn path(&self) -> PathBuf {
61 match self {
62 Self::Persistent(path) => path.to_owned(),
63 Self::Temporary(tmp_dir) => tmp_dir.path().to_path_buf(),
64 }
65 }
66}
67
68#[derive(Debug, Clone)]
69pub struct ConnectParams {
71 pub cookie_file: PathBuf,
73 pub rpc_socket: SocketAddrV4,
75 pub p2p_socket: Option<SocketAddrV4>,
77 pub zmq_pub_raw_block_socket: Option<SocketAddrV4>,
79 pub zmq_pub_raw_tx_socket: Option<SocketAddrV4>,
81}
82
83pub struct CookieValues {
84 pub user: String,
85 pub password: String,
86}
87
88impl ConnectParams {
89 fn parse_cookie(content: String) -> Option<CookieValues> {
91 let values: Vec<_> = content.splitn(2, ':').collect();
92 let user = values.first()?.to_string();
93 let password = values.get(1)?.to_string();
94 Some(CookieValues { user, password })
95 }
96
97 pub fn get_cookie_values(&self) -> Result<Option<CookieValues>, std::io::Error> {
99 let cookie = std::fs::read_to_string(&self.cookie_file)?;
100 Ok(self::ConnectParams::parse_cookie(cookie))
101 }
102}
103
104#[derive(Debug, PartialEq, Eq, Clone)]
106pub enum P2P {
107 No,
109 Yes,
111 Connect(SocketAddrV4, bool),
115}
116
117pub enum Error {
119 Io(std::io::Error),
121 Rpc(client_sync::Error),
123 NoFeature,
125 NoEnvVar,
127 NoBitcoindExecutableFound,
130 EarlyExit(ExitStatus),
132 BothDirsSpecified,
134 RpcUserAndPasswordUsed,
137 SkipDownload,
139 NoBitcoindInstance,
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 => write!(f, "it appears that bitcoind is not reachable"),
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: 3,
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> {
285 let tmpdir =
286 conf.tmpdir.clone().or_else(|| env::var("TEMPDIR_ROOT").map(PathBuf::from).ok());
287 let work_dir = match (&tmpdir, &conf.staticdir) {
288 (Some(_), Some(_)) => return Err(Error::BothDirsSpecified.into()),
289 (Some(tmpdir), None) => DataDir::Temporary(TempDir::new_in(tmpdir)?),
290 (None, Some(workdir)) => {
291 fs::create_dir_all(workdir)?;
292 DataDir::Persistent(workdir.to_owned())
293 }
294 (None, None) => DataDir::Temporary(TempDir::new()?),
295 };
296
297 let work_dir_path = work_dir.path();
298 if !work_dir_path.exists() {
299 panic!("work dir does not exist");
300 }
301 debug!("work_dir: {:?}", work_dir_path);
302
303 let cookie_file = work_dir_path.join(conf.network).join(".cookie");
304 let rpc_port = get_available_port()?;
305 let rpc_socket = SocketAddrV4::new(LOCAL_IP, rpc_port);
306 let rpc_url = format!("http://{}", rpc_socket);
307 debug!("rpc_url: {}", rpc_url);
308
309 let (p2p_args, p2p_socket) = match conf.p2p {
310 P2P::No => (vec!["-listen=0".to_string()], None),
311 P2P::Yes => {
312 let p2p_port = get_available_port()?;
313 let p2p_socket = SocketAddrV4::new(LOCAL_IP, p2p_port);
314 let bind_arg = format!("-bind={}", p2p_socket);
315 let args = vec![bind_arg];
316 (args, Some(p2p_socket))
317 }
318 P2P::Connect(other_node_url, listen) => {
319 let p2p_port = get_available_port()?;
320 let p2p_socket = SocketAddrV4::new(LOCAL_IP, p2p_port);
321 let bind_arg = format!("-bind={}", p2p_socket);
322 let connect = format!("-connect={}", other_node_url);
323 let mut args = vec![bind_arg, connect];
324 if listen {
325 args.push("-listen=1".to_string())
326 }
327 (args, Some(p2p_socket))
328 }
329 };
330
331 let (zmq_args, zmq_pub_raw_tx_socket, zmq_pub_raw_block_socket) = match conf.enable_zmq {
332 true => {
333 let zmq_pub_raw_tx_port = get_available_port()?;
334 let zmq_pub_raw_tx_socket = SocketAddrV4::new(LOCAL_IP, zmq_pub_raw_tx_port);
335 let zmq_pub_raw_block_port = get_available_port()?;
336 let zmq_pub_raw_block_socket = SocketAddrV4::new(LOCAL_IP, zmq_pub_raw_block_port);
337 let zmqpubrawblock_arg =
338 format!("-zmqpubrawblock=tcp://0.0.0.0:{}", zmq_pub_raw_block_port);
339 let zmqpubrawtx_arg = format!("-zmqpubrawtx=tcp://0.0.0.0:{}", zmq_pub_raw_tx_port);
340 (
341 vec![zmqpubrawtx_arg, zmqpubrawblock_arg],
342 Some(zmq_pub_raw_tx_socket),
343 Some(zmq_pub_raw_block_socket),
344 )
345 }
346 false => (vec![], None, None),
347 };
348
349 let stdout = if conf.view_stdout { Stdio::inherit() } else { Stdio::null() };
350
351 let datadir_arg = format!("-datadir={}", work_dir_path.display());
352 let rpc_arg = format!("-rpcport={}", rpc_port);
353 let default_args = [&datadir_arg, &rpc_arg];
354 let conf_args = validate_args(conf.args.clone())?;
355
356 debug!(
357 "launching {:?} with args: {:?} {:?} AND custom args: {:?}",
358 exe.as_ref(),
359 default_args,
360 p2p_args,
361 conf_args
362 );
363
364 let mut process = Command::new(exe.as_ref())
365 .args(default_args)
366 .args(&p2p_args)
367 .args(&conf_args)
368 .args(&zmq_args)
369 .stdout(stdout)
370 .spawn()
371 .with_context(|| format!("Error while executing {:?}", exe.as_ref()))?;
372
373 debug!("cookie file: {}", cookie_file.display());
374
375 if let Some(status) = process.try_wait()? {
376 if conf.attempts > 0 {
377 warn!("early exit with: {:?}. Trying to launch again ({} attempts remaining), maybe some other process used our available port", status, conf.attempts);
378 let mut conf = conf.clone();
379 conf.attempts -= 1;
380 return Self::with_conf(exe, &conf)
381 .with_context(|| format!("Remaining attempts {}", conf.attempts));
382 } else {
383 error!("early exit with: {:?}", status);
384 return Err(Error::EarlyExit(status).into());
385 }
386 }
387 thread::sleep(Duration::from_millis(1000));
388 assert!(process.stderr.is_none());
389
390 let mut tries = 0;
391 let auth = Auth::CookieFile(cookie_file.clone());
392
393 let client = loop {
394 tries += 1;
395
396 if tries > 10 {
397 error!("failed to get a response from bitcoind");
398 return Err(Error::NoBitcoindInstance.into());
399 }
400
401 let client_base = match Client::new_with_auth(&rpc_url, auth.clone()) {
402 Ok(client) => client,
403 Err(e) => {
404 error!("failed to create client: {}. Retrying!", e);
405 thread::sleep(Duration::from_millis(1000));
406 continue;
407 }
408 };
409
410 let client_result: Result<serde_json::Value, _> =
412 client_base.call("getblockchaininfo", &[]);
413
414 match client_result {
415 Ok(_) => {
416 let url = match &conf.wallet {
417 Some(wallet) => {
418 debug!("trying to create/load wallet: {}", wallet);
419 match client_base.create_wallet(wallet) {
421 Ok(json) => {
422 debug!("created wallet: {}", json.name());
423 }
424 Err(e) => {
425 debug!(
426 "initial create_wallet failed, try load instead: {:?}",
427 e
428 );
429 let wallet = client_base.load_wallet(wallet)?.name();
430 debug!("loaded wallet: {}", wallet);
431 }
432 }
433 format!("{}/wallet/{}", rpc_url, wallet)
434 }
435 None => rpc_url,
436 };
437 debug!("creating client with url: {}", url);
438 break Client::new_with_auth(&url, auth)?;
439 }
440 Err(e) => {
441 error!("failed to get a response from bitcoind: {}. Retrying!", e);
442 thread::sleep(Duration::from_millis(1000));
443 continue;
444 }
445 }
446 };
447
448 Ok(Node {
449 process,
450 client,
451 work_dir,
452 params: ConnectParams {
453 cookie_file,
454 rpc_socket,
455 p2p_socket,
456 zmq_pub_raw_block_socket,
457 zmq_pub_raw_tx_socket,
458 },
459 })
460 }
461
462 pub fn rpc_url(&self) -> String { format!("http://{}", self.params.rpc_socket) }
464
465 #[cfg(any(feature = "0_19_1", not(feature = "download")))]
466 pub fn rpc_url_with_wallet<T: AsRef<str>>(&self, wallet_name: T) -> String {
469 format!("http://{}/wallet/{}", self.params.rpc_socket, wallet_name.as_ref())
470 }
471
472 pub fn workdir(&self) -> PathBuf { self.work_dir.path() }
474
475 pub fn p2p_connect(&self, listen: bool) -> Option<P2P> {
477 self.params.p2p_socket.map(|s| P2P::Connect(s, listen))
478 }
479
480 pub fn stop(&mut self) -> anyhow::Result<ExitStatus> {
482 self.client.stop()?;
483 Ok(self.process.wait()?)
484 }
485
486 #[cfg(any(feature = "0_19_1", not(feature = "download")))]
487 pub fn create_wallet<T: AsRef<str>>(&self, wallet: T) -> anyhow::Result<Client> {
490 let _ = self.client.create_wallet(wallet.as_ref())?;
491 Ok(Client::new_with_auth(
492 &self.rpc_url_with_wallet(wallet),
493 Auth::CookieFile(self.params.cookie_file.clone()),
494 )?)
495 }
496}
497
498#[cfg(feature = "download")]
499impl Node {
500 pub fn from_downloaded() -> anyhow::Result<Node> { Node::new(downloaded_exe_path()?) }
502
503 pub fn from_downloaded_with_conf(conf: &Conf) -> anyhow::Result<Node> {
505 Node::with_conf(downloaded_exe_path()?, conf)
506 }
507}
508
509impl Drop for Node {
510 fn drop(&mut self) {
511 if let DataDir::Persistent(_) = self.work_dir {
512 let _ = self.stop();
513 }
514 let _ = self.process.kill();
515 }
516}
517
518pub fn get_available_port() -> anyhow::Result<u16> {
522 let t = TcpListener::bind(("127.0.0.1", 0))?; Ok(t.local_addr().map(|s| s.port())?)
525}
526
527impl From<std::io::Error> for Error {
528 fn from(e: std::io::Error) -> Self { Error::Io(e) }
529}
530
531impl From<client_sync::Error> for Error {
532 fn from(e: client_sync::Error) -> Self { Error::Rpc(e) }
533}
534
535#[cfg(not(feature = "download"))]
537pub fn downloaded_exe_path() -> anyhow::Result<String> { Err(Error::NoFeature.into()) }
538
539#[cfg(feature = "download")]
541pub fn downloaded_exe_path() -> anyhow::Result<String> {
542 if std::env::var_os("BITCOIND_SKIP_DOWNLOAD").is_some() {
543 return Err(Error::SkipDownload.into());
544 }
545
546 let mut path: PathBuf = env!("OUT_DIR").into();
547 path.push("bitcoin");
548 path.push(format!("bitcoin-{}", VERSION));
549 path.push("bin");
550
551 if cfg!(target_os = "windows") {
552 path.push("bitcoind.exe");
553 } else {
554 path.push("bitcoind");
555 }
556
557 let path = format!("{}", path.display());
558 debug!("path: {}", path);
559 Ok(path)
560}
561
562pub fn exe_path() -> anyhow::Result<String> {
569 if let Ok(path) = std::env::var("BITCOIND_EXE") {
570 return Ok(path);
571 }
572 if let Ok(path) = downloaded_exe_path() {
573 return Ok(path);
574 }
575 which::which("bitcoind")
576 .map_err(|_| Error::NoBitcoindExecutableFound.into())
577 .map(|p| p.display().to_string())
578}
579
580pub fn validate_args(args: Vec<&str>) -> anyhow::Result<Vec<&str>> {
582 args.iter().try_for_each(|arg| {
583 if INVALID_ARGS.iter().any(|x| arg.starts_with(x)) {
585 return Err(Error::RpcUserAndPasswordUsed);
586 }
587 Ok(())
588 })?;
589
590 Ok(args)
591}
592
593#[cfg(test)]
594mod test {
595 use std::net::SocketAddrV4;
596
597 use tempfile::TempDir;
598
599 use super::*;
600 use crate::{exe_path, get_available_port, Conf, Node, LOCAL_IP, P2P};
601
602 #[test]
603 fn test_local_ip() {
604 assert_eq!("127.0.0.1", format!("{}", LOCAL_IP));
605 let port = get_available_port().unwrap();
606 let socket = SocketAddrV4::new(LOCAL_IP, port);
607 assert_eq!(format!("127.0.0.1:{}", port), format!("{}", socket));
608 }
609
610 #[test]
611 fn test_node_get_blockchain_info() {
612 let exe = init();
613 let node = Node::new(exe).unwrap();
614 let info = node.client.get_blockchain_info().unwrap();
615 assert_eq!(0, info.blocks);
616 }
617
618 #[test]
619 fn test_node() {
620 let exe = init();
621 let node = Node::new(exe).unwrap();
622 let info = node.client.get_blockchain_info().unwrap();
623
624 assert_eq!(0, info.blocks);
625 let address = node.client.new_address().unwrap();
626 let _ = node.client.generate_to_address(1, &address).unwrap();
627 let info = node.client.get_blockchain_info().unwrap();
628 assert_eq!(1, info.blocks);
629 }
630
631 #[test]
632 #[cfg(feature = "0_21_2")]
633 fn test_getindexinfo() {
634 let exe = init();
635 let mut conf = Conf::default();
636 conf.args.push("-txindex");
637 let node = Node::with_conf(&exe, &conf).unwrap();
638 assert!(
639 node.client.server_version().unwrap() >= 210_000,
640 "getindexinfo requires bitcoin >0.21"
641 );
642 let info: std::collections::HashMap<String, serde_json::Value> =
643 node.client.call("getindexinfo", &[]).unwrap();
644 assert!(info.contains_key("txindex"));
645 assert!(node.client.server_version().unwrap() >= 210_000);
646 }
647
648 #[test]
649 fn test_p2p() {
650 let exe = init();
651
652 let conf = Conf::<'_> { p2p: P2P::Yes, ..Default::default() };
653 let node = Node::with_conf(&exe, &conf).unwrap();
654 assert_eq!(peers_connected(&node.client), 0);
655
656 let other_conf = Conf::<'_> { p2p: node.p2p_connect(false).unwrap(), ..Default::default() };
657 let other_node = Node::with_conf(&exe, &other_conf).unwrap();
658
659 assert_eq!(peers_connected(&node.client), 1);
660 assert_eq!(peers_connected(&other_node.client), 1);
661 }
662
663 #[cfg(not(target_os = "windows"))] #[test]
665 fn test_data_persistence() {
666 let mut conf = Conf::default();
668 let datadir = TempDir::new().unwrap();
669 conf.staticdir = Some(datadir.path().to_path_buf());
670
671 let node = Node::with_conf(exe_path().unwrap(), &conf).unwrap();
675 let core_addrs = node.client.new_address().unwrap();
676 node.client.generate_to_address(101, &core_addrs).unwrap();
677 let wallet_balance_1 = node.client.get_balance().unwrap();
678 let best_block_1 = node.client.get_best_block_hash().unwrap();
679
680 drop(node);
681
682 let node = Node::with_conf(exe_path().unwrap(), &conf).unwrap();
684
685 let wallet_balance_2 = node.client.get_balance().unwrap();
686 let best_block_2 = node.client.get_best_block_hash().unwrap();
687
688 assert_eq!(best_block_1, best_block_2);
690
691 assert_eq!(wallet_balance_1, wallet_balance_2);
693 }
694
695 #[test]
696 fn test_multi_p2p() {
697 let exe = init();
698
699 let conf_node1 = Conf::<'_> { p2p: P2P::Yes, ..Default::default() };
700 let node1 = Node::with_conf(&exe, &conf_node1).unwrap();
701 assert_eq!(peers_connected(&node1.client), 0);
702
703 let conf_node2 = Conf::<'_> { p2p: node1.p2p_connect(true).unwrap(), ..Default::default() };
705 let node2 = Node::with_conf(&exe, &conf_node2).unwrap();
706
707 let conf_node3 =
709 Conf::<'_> { p2p: node2.p2p_connect(false).unwrap(), ..Default::default() };
710 let node3 = Node::with_conf(exe_path().unwrap(), &conf_node3).unwrap();
711
712 let node1_peers = peers_connected(&node1.client);
714 let node2_peers = peers_connected(&node2.client);
715 let node3_peers = peers_connected(&node3.client);
716
717 assert!(node1_peers >= 1);
719 assert!(node2_peers >= 1);
720 assert_eq!(node3_peers, 1, "listen false but more than 1 peer");
721 }
722
723 #[cfg(feature = "0_19_1")]
724 #[test]
725 fn test_multi_wallet() {
726 use corepc_client::bitcoin::Amount;
727
728 let exe = init();
729 let node = Node::new(exe).unwrap();
730 let alice = node.create_wallet("alice").unwrap();
731 let alice_address = alice.new_address().unwrap();
732 let bob = node.create_wallet("bob").unwrap();
733 let bob_address = bob.new_address().unwrap();
734 node.client.generate_to_address(1, &alice_address).unwrap();
735 node.client.generate_to_address(101, &bob_address).unwrap();
736
737 let balances = alice.get_balances().unwrap();
738 let alice_balances: vtype::GetBalances = balances;
739
740 let balances = bob.get_balances().unwrap();
741 let bob_balances: vtype::GetBalances = balances;
742
743 assert_eq!(
744 Amount::from_btc(50.0).unwrap(),
745 Amount::from_btc(alice_balances.mine.trusted).unwrap()
746 );
747 assert_eq!(
748 Amount::from_btc(50.0).unwrap(),
749 Amount::from_btc(bob_balances.mine.trusted).unwrap()
750 );
751 assert_eq!(
752 Amount::from_btc(5000.0).unwrap(),
753 Amount::from_btc(bob_balances.mine.immature).unwrap()
754 );
755 let _txid = alice.send_to_address(&bob_address, Amount::from_btc(1.0).unwrap()).unwrap();
756
757 let balances = alice.get_balances().unwrap();
758 let alice_balances: vtype::GetBalances = balances;
759
760 assert!(
761 Amount::from_btc(alice_balances.mine.trusted).unwrap()
762 < Amount::from_btc(49.0).unwrap()
763 && Amount::from_btc(alice_balances.mine.trusted).unwrap()
764 > Amount::from_btc(48.9).unwrap()
765 );
766
767 for _ in 0..30 {
769 let balances = bob.get_balances().unwrap();
770 let bob_balances: vtype::GetBalances = balances;
771
772 if Amount::from_btc(bob_balances.mine.untrusted_pending).unwrap().to_sat() > 0 {
773 break;
774 }
775 std::thread::sleep(std::time::Duration::from_millis(100));
776 }
777 let balances = bob.get_balances().unwrap();
778 let bob_balances: vtype::GetBalances = balances;
779
780 assert_eq!(
781 Amount::from_btc(1.0).unwrap(),
782 Amount::from_btc(bob_balances.mine.untrusted_pending).unwrap()
783 );
784 assert!(node.create_wallet("bob").is_err(), "wallet already exist");
785 }
786
787 #[test]
788 fn test_node_rpcuser_and_rpcpassword() {
789 let exe = init();
790
791 let mut conf = Conf::default();
792 conf.args.push("-rpcuser=bitcoind");
793 conf.args.push("-rpcpassword=bitcoind");
794
795 let node = Node::with_conf(exe, &conf);
796
797 assert!(node.is_err());
798 }
799
800 #[test]
801 fn test_node_rpcauth() {
802 let exe = init();
803
804 let mut conf = Conf::default();
805 conf.args.push("-rpcauth=bitcoind:cccd5d7fd36e55c1b8576b8077dc1b83$60b5676a09f8518dcb4574838fb86f37700cd690d99bd2fdc2ea2bf2ab80ead6");
808
809 let node = Node::with_conf(exe, &conf).unwrap();
810
811 let auth = Auth::UserPass("bitcoind".to_string(), "bitcoind".to_string());
812 let client = Client::new_with_auth(
813 format!("{}/wallet/default", node.rpc_url().as_str()).as_str(),
814 auth,
815 )
816 .unwrap();
817 let info = client.get_blockchain_info().unwrap();
818 assert_eq!(0, info.blocks);
819
820 let address = client.new_address().unwrap();
821 let _ = client.generate_to_address(1, &address).unwrap();
822 let info = node.client.get_blockchain_info().unwrap();
823 assert_eq!(1, info.blocks);
824 }
825
826 #[test]
827 fn test_get_cookie_user_and_pass() {
828 let exe = init();
829 let node = Node::new(exe).unwrap();
830
831 let user: &str = "bitcoind_user";
832 let password: &str = "bitcoind_password";
833
834 std::fs::write(&node.params.cookie_file, format!("{}:{}", user, password)).unwrap();
835
836 let result_values = node.params.get_cookie_values().unwrap().unwrap();
837
838 assert_eq!(user, result_values.user);
839 assert_eq!(password, result_values.password);
840 }
841
842 #[test]
843 fn zmq_interface_enabled() {
844 let conf = Conf::<'_> { enable_zmq: true, ..Default::default() };
845 let node = Node::with_conf(exe_path().unwrap(), &conf).unwrap();
846
847 assert!(node.params.zmq_pub_raw_tx_socket.is_some());
848 assert!(node.params.zmq_pub_raw_block_socket.is_some());
849 }
850
851 #[test]
852 fn zmq_interface_disabled() {
853 let exe = init();
854 let node = Node::new(exe).unwrap();
855
856 assert!(node.params.zmq_pub_raw_tx_socket.is_none());
857 assert!(node.params.zmq_pub_raw_block_socket.is_none());
858 }
859
860 fn peers_connected(client: &Client) -> usize {
861 let result: Vec<serde_json::Value> = client.call("getpeerinfo", &[]).unwrap();
864 result.len()
865 }
866
867 fn init() -> String {
868 let _ = env_logger::try_init();
869 exe_path().unwrap()
870 }
871}