zebra_scanner/
main.rs

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
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
//! The zebra-scanner binary.
//!
//! The zebra-scanner binary is a standalone binary that scans the Zcash blockchain for transactions using the given sapling keys.
use color_eyre::eyre::eyre;
use lazy_static::lazy_static;
use structopt::StructOpt;
use tracing::*;

use zebra_chain::{block::Height, parameters::Network};
use zebra_state::SaplingScanningKey;

use core::net::SocketAddr;
use std::path::{Path, PathBuf};

/// A structure with sapling key and birthday height.
#[derive(Clone, Debug, Eq, PartialEq, serde::Deserialize)]
pub struct SaplingKey {
    key: SaplingScanningKey,
    #[serde(default = "min_height")]
    birthday_height: Height,
}

fn min_height() -> Height {
    Height(0)
}

impl std::str::FromStr for SaplingKey {
    type Err = Box<dyn std::error::Error>;
    fn from_str(value: &str) -> Result<Self, Self::Err> {
        Ok(serde_json::from_str(value)?)
    }
}

#[tokio::main]
/// Runs the zebra scanner binary with the given arguments.
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    // Display logs with `info` level by default.
    let tracing_filter: String = match std::env::var("RUST_LOG") {
        Ok(val) if !val.is_empty() => val,
        _ => "info".to_string(),
    };

    tracing_subscriber::fmt::fmt()
        .with_env_filter(tracing_filter)
        .init();

    // Parse command line arguments.
    let args = Args::from_args();

    let zebrad_cache_dir = args.zebrad_cache_dir;
    validate_dir(&zebrad_cache_dir)?;

    let scanning_cache_dir = args.scanning_cache_dir;
    let mut db_config = zebra_scan::Config::default().db_config;
    db_config.cache_dir = scanning_cache_dir;
    let network = args.network;
    let sapling_keys_to_scan = args
        .sapling_keys_to_scan
        .into_iter()
        .map(|key| (key.key, key.birthday_height.0))
        .collect();
    let listen_addr = args.listen_addr;

    // Create a state config with arguments.
    let state_config = zebra_state::Config {
        cache_dir: zebrad_cache_dir,
        ..zebra_state::Config::default()
    };

    // Create a scanner config with arguments.
    let scanner_config = zebra_scan::Config {
        sapling_keys_to_scan,
        listen_addr,
        db_config,
    };

    // Get a read-only state and the database.
    let (read_state, _latest_chain_tip, chain_tip_change, sync_task) =
        zebra_rpc::sync::init_read_state_with_syncer(
            state_config,
            &network,
            args.zebra_rpc_listen_addr,
        )
        .await?
        .map_err(|err| eyre!(err))?;

    // Spawn the scan task.
    let scan_task_handle =
        { zebra_scan::spawn_init(scanner_config, network, read_state, chain_tip_change) };

    // Pin the scan task handle.
    tokio::pin!(scan_task_handle);
    tokio::pin!(sync_task);

    // Wait for task to finish
    tokio::select! {
        scan_result = &mut scan_task_handle => scan_result
            .expect("unexpected panic in the scan task")
            .map(|_| info!("scan task exited"))
            .map_err(Into::into),
        sync_result = &mut sync_task => {
            sync_result.expect("unexpected panic in the scan task");
            Ok(())
        }
    }
}

// Default values for the zebra-scanner arguments.
lazy_static! {
    static ref DEFAULT_ZEBRAD_CACHE_DIR: String = zebra_state::Config::default()
        .cache_dir
        .to_str()
        .expect("default cache dir is valid")
        .to_string();
    static ref DEFAULT_SCANNER_CACHE_DIR: String = zebra_scan::Config::default()
        .db_config
        .cache_dir
        .to_str()
        .expect("default cache dir is valid")
        .to_string();
    static ref DEFAULT_NETWORK: String = Network::default().to_string();
}

/// zebra-scanner arguments
#[derive(Clone, Debug, Eq, PartialEq, StructOpt)]
pub struct Args {
    /// Path to zebrad state.
    #[structopt(default_value = &DEFAULT_ZEBRAD_CACHE_DIR, long)]
    pub zebrad_cache_dir: PathBuf,

    /// Path to scanning state.
    #[structopt(default_value = &DEFAULT_SCANNER_CACHE_DIR, long)]
    pub scanning_cache_dir: PathBuf,

    /// The Zcash network.
    #[structopt(default_value = &DEFAULT_NETWORK, long)]
    pub network: Network,

    /// The sapling keys to scan for.
    #[structopt(long)]
    pub sapling_keys_to_scan: Vec<SaplingKey>,

    /// The listen address of Zebra's RPC server used by the syncer to check for chain tip changes
    /// and get blocks in Zebra's non-finalized state.
    #[structopt(long)]
    pub zebra_rpc_listen_addr: SocketAddr,

    /// IP address and port for the gRPC server.
    #[structopt(long)]
    pub listen_addr: Option<SocketAddr>,
}

/// Create an error message is a given directory does not exist or we don't have access to it for whatever reason.
fn validate_dir(dir: &Path) -> Result<(), Box<dyn std::error::Error>> {
    match dir.try_exists() {
        Ok(true) => Ok(()),
        Ok(false) => {
            let err_msg = format!("directory {} does not exist.", dir.display());
            error!("{}", err_msg);
            Err(std::io::Error::new(std::io::ErrorKind::NotFound, err_msg).into())
        }
        Err(e) => {
            error!("directory {} could not be accessed: {:?}", dir.display(), e);
            Err(e.into())
        }
    }
}