zebra_scanner/
main.rs

1//! The zebra-scanner binary.
2//!
3//! The zebra-scanner binary is a standalone binary that scans the Zcash blockchain for transactions using the given sapling keys.
4use color_eyre::eyre::eyre;
5use lazy_static::lazy_static;
6use structopt::StructOpt;
7use tracing::*;
8
9use zebra_chain::{block::Height, parameters::Network};
10use zebra_state::SaplingScanningKey;
11
12use core::net::SocketAddr;
13use std::path::{Path, PathBuf};
14
15/// A structure with sapling key and birthday height.
16#[derive(Clone, Debug, Eq, PartialEq, serde::Deserialize)]
17pub struct SaplingKey {
18    key: SaplingScanningKey,
19    #[serde(default = "min_height")]
20    birthday_height: Height,
21}
22
23fn min_height() -> Height {
24    Height(0)
25}
26
27impl std::str::FromStr for SaplingKey {
28    type Err = Box<dyn std::error::Error>;
29    fn from_str(value: &str) -> Result<Self, Self::Err> {
30        Ok(serde_json::from_str(value)?)
31    }
32}
33
34#[tokio::main]
35/// Runs the zebra scanner binary with the given arguments.
36async fn main() -> Result<(), Box<dyn std::error::Error>> {
37    // Display logs with `info` level by default.
38    let tracing_filter: String = match std::env::var("RUST_LOG") {
39        Ok(val) if !val.is_empty() => val,
40        _ => "info".to_string(),
41    };
42
43    tracing_subscriber::fmt::fmt()
44        .with_env_filter(tracing_filter)
45        .init();
46
47    // Parse command line arguments.
48    let args = Args::from_args();
49
50    let zebrad_cache_dir = args.zebrad_cache_dir;
51    validate_dir(&zebrad_cache_dir)?;
52
53    let scanning_cache_dir = args.scanning_cache_dir;
54    let mut db_config = zebra_scan::Config::default().db_config;
55    db_config.cache_dir = scanning_cache_dir;
56    let network = args.network;
57    let sapling_keys_to_scan = args
58        .sapling_keys_to_scan
59        .into_iter()
60        .map(|key| (key.key, key.birthday_height.0))
61        .collect();
62    let listen_addr = args.listen_addr;
63
64    // Create a state config with arguments.
65    let state_config = zebra_state::Config {
66        cache_dir: zebrad_cache_dir,
67        ..zebra_state::Config::default()
68    };
69
70    // Create a scanner config with arguments.
71    let scanner_config = zebra_scan::Config {
72        sapling_keys_to_scan,
73        listen_addr,
74        db_config,
75    };
76
77    // Get a read-only state and the database.
78    let (read_state, _latest_chain_tip, chain_tip_change, sync_task) =
79        zebra_rpc::sync::init_read_state_with_syncer(
80            state_config,
81            &network,
82            args.zebra_rpc_listen_addr,
83        )
84        .await?
85        .map_err(|err| eyre!(err))?;
86
87    // Spawn the scan task.
88    let scan_task_handle =
89        { zebra_scan::spawn_init(scanner_config, network, read_state, chain_tip_change) };
90
91    // Pin the scan task handle.
92    tokio::pin!(scan_task_handle);
93    tokio::pin!(sync_task);
94
95    // Wait for task to finish
96    tokio::select! {
97        scan_result = &mut scan_task_handle => scan_result
98            .expect("unexpected panic in the scan task")
99            .map(|_| info!("scan task exited"))
100            .map_err(Into::into),
101        sync_result = &mut sync_task => {
102            sync_result.expect("unexpected panic in the scan task");
103            Ok(())
104        }
105    }
106}
107
108// Default values for the zebra-scanner arguments.
109lazy_static! {
110    static ref DEFAULT_ZEBRAD_CACHE_DIR: String = zebra_state::Config::default()
111        .cache_dir
112        .to_str()
113        .expect("default cache dir is valid")
114        .to_string();
115    static ref DEFAULT_SCANNER_CACHE_DIR: String = zebra_scan::Config::default()
116        .db_config
117        .cache_dir
118        .to_str()
119        .expect("default cache dir is valid")
120        .to_string();
121    static ref DEFAULT_NETWORK: String = Network::default().to_string();
122}
123
124/// zebra-scanner arguments
125#[derive(Clone, Debug, Eq, PartialEq, StructOpt)]
126pub struct Args {
127    /// Path to zebrad state.
128    #[structopt(default_value = &DEFAULT_ZEBRAD_CACHE_DIR, long)]
129    pub zebrad_cache_dir: PathBuf,
130
131    /// Path to scanning state.
132    #[structopt(default_value = &DEFAULT_SCANNER_CACHE_DIR, long)]
133    pub scanning_cache_dir: PathBuf,
134
135    /// The Zcash network.
136    #[structopt(default_value = &DEFAULT_NETWORK, long)]
137    pub network: Network,
138
139    /// The sapling keys to scan for.
140    #[structopt(long)]
141    pub sapling_keys_to_scan: Vec<SaplingKey>,
142
143    /// The listen address of Zebra's RPC server used by the syncer to check for chain tip changes
144    /// and get blocks in Zebra's non-finalized state.
145    #[structopt(long)]
146    pub zebra_rpc_listen_addr: SocketAddr,
147
148    /// IP address and port for the gRPC server.
149    #[structopt(long)]
150    pub listen_addr: Option<SocketAddr>,
151}
152
153/// Create an error message is a given directory does not exist or we don't have access to it for whatever reason.
154fn validate_dir(dir: &Path) -> Result<(), Box<dyn std::error::Error>> {
155    match dir.try_exists() {
156        Ok(true) => Ok(()),
157        Ok(false) => {
158            let err_msg = format!("directory {} does not exist.", dir.display());
159            error!("{}", err_msg);
160            Err(std::io::Error::new(std::io::ErrorKind::NotFound, err_msg).into())
161        }
162        Err(e) => {
163            error!("directory {} could not be accessed: {:?}", dir.display(), e);
164            Err(e.into())
165        }
166    }
167}