liana/
lib.rs

1mod bitcoin;
2pub mod commands;
3pub mod config;
4#[cfg(all(unix, feature = "daemon"))]
5mod daemonize;
6mod database;
7pub mod descriptors;
8#[cfg(feature = "daemon")]
9mod jsonrpc;
10pub mod random;
11pub mod signer;
12pub mod spend;
13#[cfg(test)]
14mod testutils;
15
16pub use bip39;
17pub use miniscript;
18
19pub use crate::bitcoin::d::{BitcoinD, BitcoindError, WalletError};
20#[cfg(feature = "daemon")]
21use crate::jsonrpc::server::{rpcserver_loop, rpcserver_setup};
22use crate::{
23    bitcoin::{poller, BitcoinInterface},
24    config::Config,
25    database::{
26        sqlite::{FreshDbOptions, SqliteDb, SqliteDbError},
27        DatabaseInterface,
28    },
29};
30
31use std::{
32    error, fmt, fs, io, path,
33    sync::{self, mpsc},
34    thread,
35};
36
37use miniscript::bitcoin::secp256k1;
38
39#[cfg(not(test))]
40use std::panic;
41// A panic in any thread should stop the main thread, and print the panic.
42#[cfg(not(test))]
43fn setup_panic_hook() {
44    panic::set_hook(Box::new(move |panic_info| {
45        let file = panic_info
46            .location()
47            .map(|l| l.file())
48            .unwrap_or_else(|| "'unknown'");
49        let line = panic_info
50            .location()
51            .map(|l| l.line().to_string())
52            .unwrap_or_else(|| "'unknown'".to_string());
53
54        let bt = backtrace::Backtrace::new();
55        let info = panic_info
56            .payload()
57            .downcast_ref::<&str>()
58            .map(|s| s.to_string())
59            .or_else(|| panic_info.payload().downcast_ref::<String>().cloned());
60        log::error!(
61            "panic occurred at line {} of file {}: {:?}\n{:?}",
62            line,
63            file,
64            info,
65            bt
66        );
67    }));
68}
69
70#[derive(Debug, Clone)]
71pub struct Version {
72    pub major: u32,
73    pub minor: u32,
74    pub patch: u32,
75}
76
77impl fmt::Display for Version {
78    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
79        write!(f, "{}.{}.{}", self.major, self.minor, self.patch)
80    }
81}
82
83pub const VERSION: Version = Version {
84    major: 5,
85    minor: 0,
86    patch: 0,
87};
88
89#[derive(Debug)]
90pub enum StartupError {
91    Io(io::Error),
92    DefaultDataDirNotFound,
93    DatadirCreation(path::PathBuf, io::Error),
94    MissingBitcoindConfig,
95    Database(SqliteDbError),
96    Bitcoind(BitcoindError),
97    #[cfg(unix)]
98    Daemonization(&'static str),
99    #[cfg(windows)]
100    NoWatchonlyInDatadir,
101}
102
103impl fmt::Display for StartupError {
104    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
105        match self {
106            Self::Io(e) => write!(f, "{}", e),
107            Self::DefaultDataDirNotFound => write!(
108                f,
109                "Not data directory was specified and a default path could not be determined for this platform."
110            ),
111            Self::DatadirCreation(dir_path, e) => write!(
112                f,
113                "Could not create data directory at '{}': '{}'", dir_path.display(), e
114            ),
115            Self::MissingBitcoindConfig => write!(
116                f,
117                "Our Bitcoin interface is bitcoind but we have no 'bitcoind_config' entry in the configuration."
118            ),
119            Self::Database(e) => write!(f, "Error initializing database: '{}'.", e),
120            Self::Bitcoind(e) => write!(f, "Error setting up bitcoind interface: '{}'.", e),
121            #[cfg(unix)]
122            Self::Daemonization(e) => write!(f, "Error when daemonizing: '{}'.", e),
123            #[cfg(windows)]
124            Self::NoWatchonlyInDatadir => {
125                write!(
126                    f,
127                    "A data directory exists with no watchonly wallet. Really old versions of Liana used to not \
128                     store the bitcoind watchonly wallet under their own datadir on Windows. A migration will be \
129                     necessary to be able to use such an old datadir with recent versions of Liana. The migration \
130                     is automatically performed by Liana version 4 and older. If you want to salvage this datadir \
131                     first run Liana v4 before running more recent Liana versions."
132                )
133            }
134        }
135    }
136}
137
138impl error::Error for StartupError {}
139
140impl From<io::Error> for StartupError {
141    fn from(e: io::Error) -> Self {
142        Self::Io(e)
143    }
144}
145
146impl From<SqliteDbError> for StartupError {
147    fn from(e: SqliteDbError) -> Self {
148        Self::Database(e)
149    }
150}
151
152impl From<BitcoindError> for StartupError {
153    fn from(e: BitcoindError) -> Self {
154        Self::Bitcoind(e)
155    }
156}
157
158fn create_datadir(datadir_path: &path::Path) -> Result<(), StartupError> {
159    #[cfg(unix)]
160    return {
161        use fs::DirBuilder;
162        use std::os::unix::fs::DirBuilderExt;
163
164        let mut builder = DirBuilder::new();
165        builder
166            .mode(0o700)
167            .recursive(true)
168            .create(datadir_path)
169            .map_err(|e| StartupError::DatadirCreation(datadir_path.to_path_buf(), e))
170    };
171
172    // TODO: permissions on Windows..
173    #[cfg(not(unix))]
174    return {
175        fs::create_dir_all(datadir_path)
176            .map_err(|e| StartupError::DatadirCreation(datadir_path.to_path_buf(), e))
177    };
178}
179
180// Connect to the SQLite database. Create it if starting fresh, and do some sanity checks.
181// If all went well, returns the interface to the SQLite database.
182fn setup_sqlite(
183    config: &Config,
184    data_dir: &path::Path,
185    fresh_data_dir: bool,
186    secp: &secp256k1::Secp256k1<secp256k1::VerifyOnly>,
187) -> Result<SqliteDb, StartupError> {
188    let db_path: path::PathBuf = [data_dir, path::Path::new("lianad.sqlite3")]
189        .iter()
190        .collect();
191    let options = if fresh_data_dir {
192        Some(FreshDbOptions::new(
193            config.bitcoin_config.network,
194            config.main_descriptor.clone(),
195        ))
196    } else {
197        None
198    };
199    let sqlite = SqliteDb::new(db_path, options, secp)?;
200    sqlite.sanity_check(config.bitcoin_config.network, &config.main_descriptor)?;
201    log::info!("Database initialized and checked.");
202
203    Ok(sqlite)
204}
205
206// Connect to bitcoind. Setup the watchonly wallet, and do some sanity checks.
207// If all went well, returns the interface to bitcoind.
208fn setup_bitcoind(
209    config: &Config,
210    data_dir: &path::Path,
211    fresh_data_dir: bool,
212) -> Result<BitcoinD, StartupError> {
213    let wo_path: path::PathBuf = [data_dir, path::Path::new("lianad_watchonly_wallet")]
214        .iter()
215        .collect();
216    let wo_path_str = wo_path.to_str().expect("Must be valid unicode").to_string();
217    // NOTE: On Windows, paths are canonicalized with a "\\?\" prefix to tell Windows to interpret
218    // the string "as is" and to ignore the maximum size of a path. HOWEVER this is not properly
219    // handled by most implementations of the C++ STL's std::filesystem. Therefore bitcoind would
220    // fail to find the wallet if we didn't strip this prefix. It's not ideal, but a lesser evil
221    // than other workarounds i could think about.
222    // See https://learn.microsoft.com/en-us/windows/win32/fileio/naming-a-file#win32-file-namespaces
223    // about the prefix.
224    // See https://stackoverflow.com/questions/71590689/how-to-properly-handle-windows-paths-with-the-long-path-prefix-with-stdfilesys
225    // for a discussion of how one C++ STL implementation handles this.
226    #[cfg(target_os = "windows")]
227    let wo_path_str = wo_path_str.replace("\\\\?\\", "").replace("\\\\?", "");
228
229    let bitcoind_config = config
230        .bitcoind_config
231        .as_ref()
232        .ok_or(StartupError::MissingBitcoindConfig)?;
233    let bitcoind = BitcoinD::new(bitcoind_config, wo_path_str)?;
234    bitcoind.node_sanity_checks(
235        config.bitcoin_config.network,
236        config.main_descriptor.is_taproot(),
237    )?;
238    if fresh_data_dir {
239        log::info!("Creating a new watchonly wallet on bitcoind.");
240        bitcoind.create_watchonly_wallet(&config.main_descriptor)?;
241        log::info!("Watchonly wallet created.");
242    } else {
243        #[cfg(windows)]
244        if !cfg!(test) && !wo_path.exists() {
245            return Err(StartupError::NoWatchonlyInDatadir);
246        }
247    }
248    log::info!("Loading our watchonly wallet on bitcoind.");
249    bitcoind.maybe_load_watchonly_wallet()?;
250    bitcoind.wallet_sanity_checks(&config.main_descriptor)?;
251    log::info!("Watchonly wallet loaded on bitcoind and sanity checked.");
252
253    Ok(bitcoind)
254}
255
256#[derive(Clone)]
257pub struct DaemonControl {
258    config: Config,
259    bitcoin: sync::Arc<sync::Mutex<dyn BitcoinInterface>>,
260    poller_sender: mpsc::SyncSender<poller::PollerMessage>,
261    // FIXME: Should we require Sync on DatabaseInterface rather than using a Mutex?
262    db: sync::Arc<sync::Mutex<dyn DatabaseInterface>>,
263    secp: secp256k1::Secp256k1<secp256k1::VerifyOnly>,
264}
265
266impl DaemonControl {
267    pub(crate) fn new(
268        config: Config,
269        bitcoin: sync::Arc<sync::Mutex<dyn BitcoinInterface>>,
270        poller_sender: mpsc::SyncSender<poller::PollerMessage>,
271        db: sync::Arc<sync::Mutex<dyn DatabaseInterface>>,
272        secp: secp256k1::Secp256k1<secp256k1::VerifyOnly>,
273    ) -> DaemonControl {
274        DaemonControl {
275            config,
276            bitcoin,
277            poller_sender,
278            db,
279            secp,
280        }
281    }
282
283    // Useful for unit test to directly mess up with the DB
284    #[cfg(test)]
285    pub fn db(&self) -> sync::Arc<sync::Mutex<dyn DatabaseInterface>> {
286        self.db.clone()
287    }
288}
289
290/// The handle to a Liana daemon. It might either be the handle for a daemon which exposes a
291/// JSONRPC server or one which exposes its API through a `DaemonControl`.
292pub enum DaemonHandle {
293    Controller {
294        poller_sender: mpsc::SyncSender<poller::PollerMessage>,
295        poller_handle: thread::JoinHandle<()>,
296        control: DaemonControl,
297    },
298    #[cfg(feature = "daemon")]
299    Server {
300        poller_sender: mpsc::SyncSender<poller::PollerMessage>,
301        poller_handle: thread::JoinHandle<()>,
302        rpcserver_shutdown: sync::Arc<sync::atomic::AtomicBool>,
303        rpcserver_handle: thread::JoinHandle<Result<(), io::Error>>,
304    },
305}
306
307impl DaemonHandle {
308    /// This starts the Liana daemon. A user of this interface should regularly poll the `is_alive`
309    /// method to check for internal errors. To shut down the daemon use the `stop` method.
310    ///
311    /// The `with_rpc_server` controls whether we should start a JSONRPC server to receive queries
312    /// or instead return a `DaemonControl` object for a caller to access the daemon's API.
313    ///
314    /// You may specify a custom Bitcoin interface through the `bitcoin` parameter. If `None`, the
315    /// default Bitcoin interface (`bitcoind` JSONRPC) will be used.
316    /// You may specify a custom Database interface through the `db` parameter. If `None`, the
317    /// default Database interface (SQLite) will be used.
318    pub fn start(
319        config: Config,
320        bitcoin: Option<impl BitcoinInterface + 'static>,
321        db: Option<impl DatabaseInterface + 'static>,
322        #[cfg(feature = "daemon")] with_rpc_server: bool,
323    ) -> Result<Self, StartupError> {
324        #[cfg(not(test))]
325        setup_panic_hook();
326
327        let secp = secp256k1::Secp256k1::verification_only();
328
329        // First, check the data directory
330        let mut data_dir = config
331            .data_dir()
332            .ok_or(StartupError::DefaultDataDirNotFound)?;
333        data_dir.push(config.bitcoin_config.network.to_string());
334        let fresh_data_dir = !data_dir.as_path().exists();
335        if fresh_data_dir {
336            create_datadir(&data_dir)?;
337            log::info!("Created a new data directory at '{}'", data_dir.display());
338        }
339
340        // Then set up the database
341        let db = match db {
342            Some(db) => sync::Arc::from(sync::Mutex::from(db)),
343            None => sync::Arc::from(sync::Mutex::from(setup_sqlite(
344                &config,
345                &data_dir,
346                fresh_data_dir,
347                &secp,
348            )?)) as sync::Arc<sync::Mutex<dyn DatabaseInterface>>,
349        };
350
351        // Now, set up the Bitcoin interface.
352        let bit = match bitcoin {
353            Some(bit) => sync::Arc::from(sync::Mutex::from(bit)),
354            None => sync::Arc::from(sync::Mutex::from(setup_bitcoind(
355                &config,
356                &data_dir,
357                fresh_data_dir,
358            )?)) as sync::Arc<sync::Mutex<dyn BitcoinInterface>>,
359        };
360
361        // If we are on a UNIX system and they told us to daemonize, do it now.
362        // NOTE: it's safe to daemonize now, as we don't carry any open DB connection
363        // https://www.sqlite.org/howtocorrupt.html#_carrying_an_open_database_connection_across_a_fork_
364        #[cfg(all(unix, feature = "daemon"))]
365        if config.daemon {
366            log::info!("Daemonizing");
367            let log_file = data_dir.as_path().join("log");
368            let pid_file = data_dir.as_path().join("lianad.pid");
369            unsafe {
370                daemonize::daemonize(&data_dir, &log_file, &pid_file)
371                    .map_err(StartupError::Daemonization)?;
372            }
373        }
374
375        // Start the poller thread. Keep the thread handle to be able to check if it crashed. Store
376        // an atomic to be able to stop it.
377        let bitcoin_poller =
378            poller::Poller::new(bit.clone(), db.clone(), config.main_descriptor.clone());
379        let (poller_sender, poller_receiver) = mpsc::sync_channel(0);
380        let poller_handle = thread::Builder::new()
381            .name("Bitcoin Network poller".to_string())
382            .spawn({
383                let poll_interval = config.bitcoin_config.poll_interval_secs;
384                move || {
385                    log::info!("Bitcoin poller started.");
386                    bitcoin_poller.poll_forever(poll_interval, poller_receiver);
387                    log::info!("Bitcoin poller stopped.");
388                }
389            })
390            .expect("Spawning the poller thread must never fail.");
391
392        // Create the API the external world will use to talk to us, either directly through the Rust
393        // structure or through the JSONRPC server we may setup below.
394        let control = DaemonControl::new(config, bit, poller_sender.clone(), db, secp);
395
396        #[cfg(feature = "daemon")]
397        if with_rpc_server {
398            let rpcserver_shutdown = sync::Arc::from(sync::atomic::AtomicBool::from(false));
399            let rpcserver_handle = thread::Builder::new()
400                .name("Bitcoin Network poller".to_string())
401                .spawn({
402                    let shutdown = rpcserver_shutdown.clone();
403                    move || {
404                        let mut rpc_socket = data_dir;
405                        rpc_socket.push("lianad_rpc");
406                        let listener = rpcserver_setup(&rpc_socket)?;
407                        log::info!("JSONRPC server started.");
408
409                        rpcserver_loop(listener, control, shutdown)?;
410                        log::info!("JSONRPC server stopped.");
411                        Ok(())
412                    }
413                })
414                .expect("Spawning the RPC server thread should never fail.");
415
416            return Ok(DaemonHandle::Server {
417                poller_sender,
418                poller_handle,
419                rpcserver_shutdown,
420                rpcserver_handle,
421            });
422        }
423
424        Ok(DaemonHandle::Controller {
425            poller_sender,
426            poller_handle,
427            control,
428        })
429    }
430
431    /// Start the Liana daemon with the default Bitcoin and database interfaces (`bitcoind` RPC
432    /// and SQLite).
433    pub fn start_default(
434        config: Config,
435        #[cfg(feature = "daemon")] with_rpc_server: bool,
436    ) -> Result<DaemonHandle, StartupError> {
437        Self::start(
438            config,
439            Option::<BitcoinD>::None,
440            Option::<SqliteDb>::None,
441            #[cfg(feature = "daemon")]
442            with_rpc_server,
443        )
444    }
445
446    /// Check whether the daemon is still up and running. This needs to be regularly polled to
447    /// check for internal errors. If this returns `false`, collect the error using the `stop`
448    /// method.
449    pub fn is_alive(&self) -> bool {
450        match self {
451            Self::Controller {
452                ref poller_handle, ..
453            } => !poller_handle.is_finished(),
454            #[cfg(feature = "daemon")]
455            Self::Server {
456                ref poller_handle,
457                ref rpcserver_handle,
458                ..
459            } => !poller_handle.is_finished() && !rpcserver_handle.is_finished(),
460        }
461    }
462
463    /// Stop the Liana daemon. This returns any error which may have occurred.
464    pub fn stop(self) -> Result<(), Box<dyn error::Error>> {
465        match self {
466            Self::Controller {
467                poller_sender,
468                poller_handle,
469                ..
470            } => {
471                poller_sender
472                    .send(poller::PollerMessage::Shutdown)
473                    .expect("The other end should never have hung up before this.");
474                poller_handle.join().expect("Poller thread must not panic");
475                Ok(())
476            }
477            #[cfg(feature = "daemon")]
478            Self::Server {
479                poller_sender,
480                poller_handle,
481                rpcserver_shutdown,
482                rpcserver_handle,
483            } => {
484                poller_sender
485                    .send(poller::PollerMessage::Shutdown)
486                    .expect("The other end should never have hung up before this.");
487                rpcserver_shutdown.store(true, sync::atomic::Ordering::Relaxed);
488                rpcserver_handle
489                    .join()
490                    .expect("Poller thread must not panic")?;
491                poller_handle.join().expect("Poller thread must not panic");
492                Ok(())
493            }
494        }
495    }
496}
497
498#[cfg(all(test, unix))]
499mod tests {
500    use super::*;
501    use crate::{
502        config::{BitcoinConfig, BitcoindConfig, BitcoindRpcAuth},
503        descriptors::LianaDescriptor,
504        testutils::*,
505    };
506
507    use miniscript::bitcoin;
508    use std::{
509        fs,
510        io::{BufRead, BufReader, Write},
511        net, path,
512        str::FromStr,
513        thread, time,
514    };
515
516    // Read all bytes from the socket until the end of a JSON object, good enough approximation.
517    fn read_til_json_end(stream: &mut net::TcpStream) {
518        stream
519            .set_read_timeout(Some(time::Duration::from_secs(5)))
520            .unwrap();
521        let mut reader = BufReader::new(stream);
522        loop {
523            let mut line = String::new();
524            reader.read_line(&mut line).unwrap();
525
526            if line.starts_with("Authorization") {
527                let mut buf = vec![0; 256];
528                reader.read_until(b'}', &mut buf).unwrap();
529                return;
530            }
531        }
532    }
533
534    // Respond to the two "echo" sent at startup to sanity check the connection
535    fn complete_sanity_check(server: &net::TcpListener) {
536        let echo_resp =
537            "HTTP/1.1 200\n\r\n{\"jsonrpc\":\"2.0\",\"id\":1,\"result\":[]}\n".as_bytes();
538
539        // Read the first echo, respond to it
540        {
541            let (mut stream, _) = server.accept().unwrap();
542            read_til_json_end(&mut stream);
543            stream.write_all(echo_resp).unwrap();
544            stream.flush().unwrap();
545        }
546
547        // Read the second echo, respond to it
548        let (mut stream, _) = server.accept().unwrap();
549        read_til_json_end(&mut stream);
550        stream.write_all(echo_resp).unwrap();
551        stream.flush().unwrap();
552    }
553
554    // Send them a pruned getblockchaininfo telling them we are at version 24.0
555    fn complete_version_check(server: &net::TcpListener) {
556        let net_resp =
557            "HTTP/1.1 200\n\r\n{\"jsonrpc\":\"2.0\",\"id\":1,\"result\":{\"version\":240000}}\n"
558                .as_bytes();
559        let (mut stream, _) = server.accept().unwrap();
560        read_til_json_end(&mut stream);
561        stream.write_all(net_resp).unwrap();
562        stream.flush().unwrap();
563    }
564
565    // Send them a pruned getblockchaininfo telling them we are on mainnet
566    fn complete_network_check(server: &net::TcpListener) {
567        let net_resp =
568            "HTTP/1.1 200\n\r\n{\"jsonrpc\":\"2.0\",\"id\":1,\"result\":{\"chain\":\"main\"}}\n"
569                .as_bytes();
570        let (mut stream, _) = server.accept().unwrap();
571        read_til_json_end(&mut stream);
572        stream.write_all(net_resp).unwrap();
573        stream.flush().unwrap();
574    }
575
576    // Send them responses for the calls involved when creating a fresh wallet
577    fn complete_wallet_creation(server: &net::TcpListener) {
578        {
579            let net_resp =
580                ["HTTP/1.1 200\n\r\n{\"jsonrpc\":\"2.0\",\"id\":1,\"result\":[]}\n".as_bytes()]
581                    .concat();
582            let (mut stream, _) = server.accept().unwrap();
583            read_til_json_end(&mut stream);
584            stream.write_all(&net_resp).unwrap();
585            stream.flush().unwrap();
586        }
587
588        {
589            let net_resp = [
590            "HTTP/1.1 200\n\r\n{\"jsonrpc\":\"2.0\",\"id\":1,\"result\":{\"name\":\"dummy\"}}\n"
591                .as_bytes(),
592            ]
593            .concat();
594            let (mut stream, _) = server.accept().unwrap();
595            read_til_json_end(&mut stream);
596            stream.write_all(&net_resp).unwrap();
597            stream.flush().unwrap();
598        }
599
600        let net_resp = [
601            "HTTP/1.1 200\n\r\n{\"jsonrpc\":\"2.0\",\"id\":1,\"result\":[{\"success\":true}]}\n"
602                .as_bytes(),
603        ]
604        .concat();
605        let (mut stream, _) = server.accept().unwrap();
606        read_til_json_end(&mut stream);
607        stream.write_all(&net_resp).unwrap();
608        stream.flush().unwrap();
609    }
610
611    // Send them a dummy result to loadwallet.
612    fn complete_wallet_loading(server: &net::TcpListener) {
613        {
614            let listwallets_resp =
615                "HTTP/1.1 200\n\r\n{\"jsonrpc\":\"2.0\",\"id\":1,\"result\":[]}\n".as_bytes();
616            let (mut stream, _) = server.accept().unwrap();
617            read_til_json_end(&mut stream);
618            stream.write_all(listwallets_resp).unwrap();
619            stream.flush().unwrap();
620        }
621
622        let loadwallet_resp =
623            "HTTP/1.1 200\n\r\n{\"jsonrpc\":\"2.0\",\"id\":1,\"result\":{\"name\":\"dummy\"}}\n"
624                .as_bytes();
625        let (mut stream, _) = server.accept().unwrap();
626        read_til_json_end(&mut stream);
627        stream.write_all(loadwallet_resp).unwrap();
628        stream.flush().unwrap();
629    }
630
631    // Send them a response to 'listwallets' with the watchonly wallet path
632    fn complete_wallet_check(server: &net::TcpListener, watchonly_wallet_path: &str) {
633        let net_resp = [
634            "HTTP/1.1 200\n\r\n{\"jsonrpc\":\"2.0\",\"id\":1,\"result\":[\"".as_bytes(),
635            watchonly_wallet_path.as_bytes(),
636            "\"]}\n".as_bytes(),
637        ]
638        .concat();
639        let (mut stream, _) = server.accept().unwrap();
640        read_til_json_end(&mut stream);
641        stream.write_all(&net_resp).unwrap();
642        stream.flush().unwrap();
643    }
644
645    // Send them a response to 'listdescriptors' with the receive and change descriptors
646    fn complete_desc_check(server: &net::TcpListener, receive_desc: &str, change_desc: &str) {
647        let net_resp = [
648            "HTTP/1.1 200\n\r\n{\"jsonrpc\":\"2.0\",\"id\":1,\"result\":{\"descriptors\":[{\"desc\":\"".as_bytes(),
649            receive_desc.as_bytes(),
650            "\",\"timestamp\":0},".as_bytes(),
651            "{\"desc\":\"".as_bytes(),
652            change_desc.as_bytes(),
653            "\",\"timestamp\":1}]}}\n".as_bytes(),
654        ]
655        .concat();
656        let (mut stream, _) = server.accept().unwrap();
657        read_til_json_end(&mut stream);
658        stream.write_all(&net_resp).unwrap();
659        stream.flush().unwrap();
660    }
661
662    // Send them a response to 'getblockhash' with the genesis block hash
663    fn complete_tip_init(server: &net::TcpListener) {
664        let net_resp = [
665            "HTTP/1.1 200\n\r\n{\"jsonrpc\":\"2.0\",\"id\":1,\"result\":\"000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f\"}\n".as_bytes(),
666        ]
667        .concat();
668        let (mut stream, _) = server.accept().unwrap();
669        read_til_json_end(&mut stream);
670        stream.write_all(&net_resp).unwrap();
671        stream.flush().unwrap();
672    }
673
674    // TODO: we could move the dummy bitcoind thread stuff to the bitcoind module to test the
675    // bitcoind interface, and use the DummyLiana from testutils to sanity check the startup.
676    // Note that startup as checked by this unit test is also tested in the functional test
677    // framework.
678    #[test]
679    fn daemon_startup() {
680        let tmp_dir = tmp_dir();
681        fs::create_dir_all(&tmp_dir).unwrap();
682        let data_dir: path::PathBuf = [tmp_dir.as_path(), path::Path::new("datadir")]
683            .iter()
684            .collect();
685        let wo_path: path::PathBuf = [
686            data_dir.as_path(),
687            path::Path::new("bitcoin"),
688            path::Path::new("lianad_watchonly_wallet"),
689        ]
690        .iter()
691        .collect();
692        let wo_path = wo_path.to_str().unwrap().to_string();
693
694        // Configure a dummy bitcoind
695        let network = bitcoin::Network::Bitcoin;
696        let cookie: path::PathBuf = [
697            tmp_dir.as_path(),
698            path::Path::new(&format!(
699                "dummy_bitcoind_{:?}.cookie",
700                thread::current().id()
701            )),
702        ]
703        .iter()
704        .collect();
705        fs::write(&cookie, [0; 32]).unwrap(); // Will overwrite should it exist already
706        let addr: net::SocketAddr =
707            net::SocketAddrV4::new(net::Ipv4Addr::new(127, 0, 0, 1), 0).into();
708        let server = net::TcpListener::bind(addr).unwrap();
709        let addr = server.local_addr().unwrap();
710        let bitcoin_config = BitcoinConfig {
711            network,
712            poll_interval_secs: time::Duration::from_secs(2),
713        };
714        let bitcoind_config = BitcoindConfig {
715            addr,
716            rpc_auth: BitcoindRpcAuth::CookieFile(cookie),
717        };
718
719        // Create a dummy config with this bitcoind
720        let desc_str = "wsh(andor(pk([aabbccdd]xpub68JJTXc1MWK8KLW4HGLXZBJknja7kDUJuFHnM424LbziEXsfkh1WQCiEjjHw4zLqSUm4rvhgyGkkuRowE9tCJSgt3TQB5J3SKAbZ2SdcKST/<0;1>/*),older(10000),pk([aabbccdd]xpub68JJTXc1MWK8PEQozKsRatrUHXKFNkD1Cb1BuQU9Xr5moCv87anqGyXLyUd4KpnDyZgo3gz4aN1r3NiaoweFW8UutBsBbgKHzaD5HkTkifK/<0;1>/*)))#3xh8xmhn";
721        let desc = LianaDescriptor::from_str(desc_str).unwrap();
722        let receive_desc = desc.receive_descriptor().clone();
723        let change_desc = desc.change_descriptor().clone();
724        let config = Config {
725            bitcoin_config,
726            bitcoind_config: Some(bitcoind_config),
727            data_dir: Some(data_dir),
728            #[cfg(unix)]
729            daemon: false,
730            log_level: log::LevelFilter::Debug,
731            main_descriptor: desc,
732        };
733
734        // Start the daemon in a new thread so the current one acts as the bitcoind server.
735        let t = thread::spawn({
736            let config = config.clone();
737            move || {
738                let handle = DaemonHandle::start_default(
739                    config,
740                    #[cfg(feature = "daemon")]
741                    false,
742                )
743                .unwrap();
744                handle.stop().unwrap();
745            }
746        });
747        complete_sanity_check(&server);
748        complete_version_check(&server);
749        complete_network_check(&server);
750        complete_wallet_creation(&server);
751        complete_wallet_loading(&server);
752        complete_wallet_check(&server, &wo_path);
753        complete_desc_check(&server, &receive_desc.to_string(), &change_desc.to_string());
754        complete_tip_init(&server);
755        // We don't have to complete the sync check as the poller checks whether it needs to stop
756        // before checking the bitcoind sync status.
757        t.join().unwrap();
758
759        // The datadir is created now, so if we restart it it won't create the wo wallet.
760        let t = thread::spawn({
761            let config = config.clone();
762            move || {
763                let handle = DaemonHandle::start_default(
764                    config,
765                    #[cfg(feature = "daemon")]
766                    false,
767                )
768                .unwrap();
769                handle.stop().unwrap();
770            }
771        });
772        complete_sanity_check(&server);
773        complete_version_check(&server);
774        complete_network_check(&server);
775        complete_wallet_loading(&server);
776        complete_wallet_check(&server, &wo_path);
777        complete_desc_check(&server, &receive_desc.to_string(), &change_desc.to_string());
778        // We don't have to complete the sync check as the poller checks whether it needs to stop
779        // before checking the bitcoind sync status.
780        t.join().unwrap();
781
782        fs::remove_dir_all(&tmp_dir).unwrap();
783    }
784}