Skip to content

Commit 7e05242

Browse files
Allow configurable work directory
This allows the node work directory to be either temporary or persisting on the disk. This can be configured using the `tmpdir` and `staticdir` `Conf` options in different combinations. Usage information in the `Conf` doc.
1 parent a010fd4 commit 7e05242

File tree

1 file changed

+67
-22
lines changed

1 file changed

+67
-22
lines changed

src/lib.rs

Lines changed: 67 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ use std::net::{Ipv4Addr, SocketAddrV4, TcpListener};
2121
use std::path::PathBuf;
2222
use std::process::{Child, Command, ExitStatus, Stdio};
2323
use std::time::Duration;
24-
use std::{env, fmt, thread};
24+
use std::{env, fmt, fs, thread};
2525
use tempfile::TempDir;
2626

2727
pub extern crate bitcoincore_rpc;
@@ -33,14 +33,32 @@ pub struct BitcoinD {
3333
process: Child,
3434
/// Rpc client linked to this bitcoind process
3535
pub client: Client,
36-
/// Work directory, where the node store blocks and other stuff. It is kept in the struct so that
37-
/// directory is deleted only when this struct is dropped
38-
_work_dir: TempDir,
36+
/// Work directory, where the node store blocks and other stuff.
37+
work_dir: DataDir,
3938

4039
/// Contains information to connect to this node
4140
pub params: ConnectParams,
4241
}
4342

43+
/// The DataDir struct defining the kind of data directory the node
44+
/// will contain. Data directory can be either persistent, or temporary.
45+
pub enum DataDir {
46+
/// Persistent Data Directory
47+
Persistent(PathBuf),
48+
/// Temporary Data Directory
49+
Temporary(TempDir),
50+
}
51+
52+
impl DataDir {
53+
/// Return the data directory path
54+
fn path(&self) -> PathBuf {
55+
match self {
56+
Self::Persistent(path) => path.to_owned(),
57+
Self::Temporary(tmp_dir) => tmp_dir.path().to_path_buf(),
58+
}
59+
}
60+
}
61+
4462
#[derive(Debug, Clone)]
4563
/// Contains all the information to connect to this node
4664
pub struct ConnectParams {
@@ -83,6 +101,8 @@ pub enum Error {
83101
BothFeatureAndEnvVar,
84102
/// Wrapper of early exit status
85103
EarlyExit(ExitStatus),
104+
/// Returned when both tmpdir and staticdir is specified in `Conf` options
105+
BothDirsSpecified,
86106
}
87107

88108
impl fmt::Debug for Error {
@@ -95,6 +115,7 @@ impl fmt::Debug for Error {
95115
Error::NeitherFeatureNorEnvVar => write!(f, "Called a method requiring env var `BITCOIND_EXE` or a feature to be set, but neither are set"),
96116
Error::BothFeatureAndEnvVar => write!(f, "Called a method requiring env var `BITCOIND_EXE` or a feature to be set, but both are set"),
97117
Error::EarlyExit(e) => write!(f, "The bitcoind process terminated early with exit code {}", e),
118+
Error::BothDirsSpecified => write!(f, "tempdir and staticdir cannot be enabled at same time in configuration options")
98119
}
99120
}
100121
}
@@ -123,6 +144,7 @@ const LOCAL_IP: Ipv4Addr = Ipv4Addr::new(127, 0, 0, 1);
123144
/// conf.p2p = bitcoind::P2P::No;
124145
/// conf.network = "regtest";
125146
/// conf.tmpdir = None;
147+
/// conf.staticdir = None;
126148
/// conf.attempts = 3;
127149
/// assert_eq!(conf, bitcoind::Conf::default());
128150
/// ```
@@ -145,13 +167,24 @@ pub struct Conf<'a> {
145167
/// directory with different/esoteric networks
146168
pub network: &'a str,
147169

148-
/// Optionally specify the root of where the temporary directories will be created.
149-
/// If none and the env var `TEMPDIR_ROOT` is set, the env var is used.
150-
/// If none and the env var `TEMPDIR_ROOT` is not set, the default temp dir of the OS is used.
151-
/// It may be useful for example to set to a ramdisk so that bitcoin nodes spawn very fast
152-
/// because their datadirs are in RAM
170+
/// Optionally specify a temporary or persistent working directory for the node.
171+
/// The following two parameters can be configured to simulate desired working directory configuration.
172+
///
173+
/// tmpdir is Some() && staticdir is Some() : Error. Cannot be enabled at same time.
174+
/// tmpdir is Some(temp_path) && staticdir is None : Create temporary directory at `tmpdir` path.
175+
/// tmpdir is None && staticdir is Some(work_path) : Create persistent directory at `staticdir` path.
176+
/// tmpdir is None && staticdir is None: Creates a temporary directory in OS default temporary directory (eg /tmp) or `TEMPDIR_ROOT` env variable path.
177+
///
178+
/// It may be useful for example to set to a ramdisk via `TEMPDIR_ROOT` env option so that
179+
/// bitcoin nodes spawn very fast because their datadirs are in RAM. Should not be enabled with persistent
180+
/// mode, as it cause memory overflows.
181+
182+
/// Temporary directory path
153183
pub tmpdir: Option<PathBuf>,
154184

185+
/// Persistent directory path
186+
pub staticdir: Option<PathBuf>,
187+
155188
/// Try to spawn the process `attempt` time
156189
///
157190
/// The OS is giving available ports to use, however, they aren't booked, so it could rarely
@@ -168,6 +201,7 @@ impl Default for Conf<'_> {
168201
p2p: P2P::No,
169202
network: "regtest",
170203
tmpdir: None,
204+
staticdir: None,
171205
attempts: 3,
172206
}
173207
}
@@ -183,16 +217,19 @@ impl BitcoinD {
183217

184218
/// Launch the bitcoind process from the given `exe` executable with given [Conf] param
185219
pub fn with_conf<S: AsRef<OsStr>>(exe: S, conf: &Conf) -> Result<BitcoinD, Error> {
186-
let work_dir = match &conf.tmpdir {
187-
Some(path) => TempDir::new_in(path),
188-
None => match env::var("TEMPDIR_ROOT") {
189-
Ok(env_path) => TempDir::new_in(env_path),
190-
Err(_) => TempDir::new(),
191-
},
192-
}?;
193-
debug!("work_dir: {:?}", work_dir);
194-
let datadir = work_dir.path().to_path_buf();
195-
let cookie_file = datadir.join(conf.network).join(".cookie");
220+
let work_dir = match (&conf.tmpdir, &conf.staticdir) {
221+
(Some(_), Some(_)) => return Err(Error::BothDirsSpecified),
222+
(Some(tmpdir), None) => DataDir::Temporary(TempDir::new_in(tmpdir)?),
223+
(None, Some(workdir)) => {
224+
fs::create_dir_all(workdir)?;
225+
DataDir::Persistent(workdir.to_owned())
226+
}
227+
(None, None) => DataDir::Temporary(TempDir::new()?),
228+
};
229+
230+
let work_dir_path = work_dir.path();
231+
debug!("work_dir: {:?}", work_dir_path);
232+
let cookie_file = work_dir_path.join(conf.network).join(".cookie");
196233
let rpc_port = get_available_port()?;
197234
let rpc_socket = SocketAddrV4::new(LOCAL_IP, rpc_port);
198235
let rpc_url = format!("http://{}", rpc_socket);
@@ -223,7 +260,7 @@ impl BitcoinD {
223260
Stdio::null()
224261
};
225262

226-
let datadir_arg = format!("-datadir={}", datadir.display());
263+
let datadir_arg = format!("-datadir={}", work_dir_path.display());
227264
let rpc_arg = format!("-rpcport={}", rpc_port);
228265
let default_args = [&datadir_arg, &rpc_arg];
229266

@@ -274,9 +311,9 @@ impl BitcoinD {
274311
Ok(BitcoinD {
275312
process,
276313
client,
277-
_work_dir: work_dir,
314+
work_dir,
278315
params: ConnectParams {
279-
datadir,
316+
datadir: work_dir_path,
280317
cookie_file,
281318
rpc_socket,
282319
p2p_socket,
@@ -300,6 +337,11 @@ impl BitcoinD {
300337
)
301338
}
302339

340+
/// Return the current workdir path of the running node
341+
pub fn workdir(&self) -> PathBuf {
342+
self.work_dir.path()
343+
}
344+
303345
/// Returns the [P2P] enum to connect to this node p2p port
304346
pub fn p2p_connect(&self, listen: bool) -> Option<P2P> {
305347
self.params.p2p_socket.map(|s| P2P::Connect(s, listen))
@@ -327,6 +369,9 @@ impl BitcoinD {
327369

328370
impl Drop for BitcoinD {
329371
fn drop(&mut self) {
372+
if let DataDir::Persistent(_) = self.work_dir {
373+
let _ = self.client.stop();
374+
}
330375
let _ = self.process.kill();
331376
}
332377
}

0 commit comments

Comments
 (0)