1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
#![warn(missing_docs)]
use bitcoincore_rpc::{Auth, Client, RpcApi};
use std::ffi::OsStr;
use std::net::TcpListener;
use std::path::PathBuf;
use std::process::{Child, Command, ExitStatus};
use std::thread;
use std::time::Duration;
use tempfile::TempDir;
pub struct BitcoinD {
process: Child,
pub client: Client,
_work_dir: TempDir,
pub cookie_file: PathBuf,
pub url: String,
}
#[derive(Debug)]
pub enum Error {
PortUnavailable,
Io(std::io::Error),
Rpc(bitcoincore_rpc::Error),
}
impl BitcoinD {
pub fn new<S: AsRef<OsStr>>(exe: S) -> Result<BitcoinD, Error> {
let _work_dir = TempDir::new()?;
let cookie_file = _work_dir.path().join("regtest").join(".cookie");
let rpc_port = get_available_port().ok_or(Error::PortUnavailable)?;
let url = format!("http://127.0.0.1:{}", rpc_port);
let process = Command::new(exe)
.arg(format!("-datadir={}", _work_dir.path().display()))
.arg(format!("-rpcport={}", rpc_port))
.arg("-regtest")
.arg("-listen=0")
.arg("-fallbackfee=0.0001")
.spawn()?;
let node_url_default = format!("{}/wallet/default", url);
let client = loop {
thread::sleep(Duration::from_millis(500));
assert!(process.stderr.is_none());
let client_result = Client::new(url.clone(), Auth::CookieFile(cookie_file.clone()));
if let Ok(client_base) = client_result {
if client_base.get_blockchain_info().is_ok() {
client_base
.create_wallet("default", None, None, None, None)
.unwrap();
break Client::new(node_url_default, Auth::CookieFile(cookie_file.clone()))
.unwrap();
}
}
};
Ok(BitcoinD {
process,
client,
_work_dir,
cookie_file,
url,
})
}
pub fn stop(&mut self) -> Result<ExitStatus, Error> {
self.client.stop()?;
Ok(self.process.wait()?)
}
}
impl Drop for BitcoinD {
fn drop(&mut self) {
let _ = self.process.kill();
}
}
fn get_available_port() -> Option<u16> {
let t = TcpListener::bind(("127.0.0.1", 0)).ok()?;
t.local_addr().ok().map(|s| s.port())
}
impl From<std::io::Error> for Error {
fn from(e: std::io::Error) -> Self {
Error::Io(e)
}
}
impl From<bitcoincore_rpc::Error> for Error {
fn from(e: bitcoincore_rpc::Error) -> Self {
Error::Rpc(e)
}
}
#[cfg(test)]
mod test {
use crate::BitcoinD;
use bitcoincore_rpc::RpcApi;
use std::env;
#[test]
fn test_bitcoind() {
let exe = env::var("BITCOIND_EXE").expect("BITCOIND_EXE env var must be set");
let bitcoind = BitcoinD::new(exe).unwrap();
let info = bitcoind.client.get_blockchain_info().unwrap();
assert_eq!(0, info.blocks);
let address = bitcoind.client.get_new_address(None, None).unwrap();
let _ = bitcoind.client.generate_to_address(1, &address).unwrap();
let info = bitcoind.client.get_blockchain_info().unwrap();
assert_eq!(1, info.blocks);
}
}