1use std::{collections::HashSet, path::Path, sync::Arc};
20
21use log::{error, info};
22use smol::{lock::Mutex, stream::StreamExt};
23use structopt_toml::{serde::Deserialize, structopt::StructOpt, StructOptToml};
24use url::Url;
25
26use darkfi::{
27 async_daemonize, cli_desc,
28 rpc::server::{listen_and_serve, RequestHandler},
29 system::{StoppableTask, StoppableTaskPtr},
30 util::path::get_config_path,
31 Error, Result,
32};
33
34use crate::{
35 config::ExplorerNetworkConfig,
36 rpc::DarkfidRpcClient,
37 service::{sync::subscribe_sync_blocks, ExplorerService},
38};
39
40mod config;
42
43mod rpc;
45
46mod service;
49
50mod store;
52
53mod error;
55
56#[cfg(test)]
58mod test_utils;
59
60const CONFIG_FILE: &str = "explorerd_config.toml";
61const CONFIG_FILE_CONTENTS: &str = include_str!("../explorerd_config.toml");
62
63#[derive(Clone, Debug, Deserialize, StructOpt, StructOptToml)]
64#[serde(default)]
65#[structopt(name = "explorerd", about = cli_desc!())]
66struct Args {
67 #[structopt(short, long)]
68 config: Option<String>,
70
71 #[structopt(short, long, default_value = "testnet")]
72 network: String,
74
75 #[structopt(long)]
76 reset: bool,
78
79 #[structopt(short, long)]
80 log: Option<String>,
82
83 #[structopt(short, parse(from_occurrences))]
84 verbose: u8,
86
87 #[structopt(short, long)]
88 no_sync: bool,
92}
93
94pub struct Explorerd {
104 pub service: ExplorerService,
106 pub rpc_connections: Mutex<HashSet<StoppableTaskPtr>>,
108 pub darkfid_client: Arc<DarkfidRpcClient>,
110 darkfid_endpoint: Url,
112 executor: Arc<smol::Executor<'static>>,
114}
115
116impl Explorerd {
117 async fn new(
119 db_path: String,
120 darkfid_endpoint: Url,
121 ex: Arc<smol::Executor<'static>>,
122 ) -> Result<Self> {
123 let darkfid_client = Arc::new(DarkfidRpcClient::new());
125
126 let service = ExplorerService::new(db_path, darkfid_client.clone())?;
128
129 service.init().await?;
131
132 Ok(Self {
133 service,
134 rpc_connections: Mutex::new(HashSet::new()),
135 darkfid_client,
136 darkfid_endpoint,
137 executor: ex,
138 })
139 }
140
141 async fn connect(&self) -> Result<()> {
144 self.darkfid_client.connect(self.darkfid_endpoint.clone(), self.executor.clone()).await
145 }
146}
147
148async_daemonize!(realmain);
149async fn realmain(args: Args, ex: Arc<smol::Executor<'static>>) -> Result<()> {
150 info!(target: "explorerd", "Initializing DarkFi blockchain explorer node...");
151
152 let config_path = get_config_path(args.config.clone(), CONFIG_FILE)?;
154
155 let config: ExplorerNetworkConfig = (&config_path, &args.network).try_into()?;
157
158 let explorer =
160 Explorerd::new(config.database.clone(), config.endpoint.clone(), ex.clone()).await?;
161 let explorer = Arc::new(explorer);
162 info!(target: "explorerd", "Node initialized successfully!");
163
164 let rpc_task = StoppableTask::new();
167 let explorer_ = explorer.clone();
168 rpc_task.clone().start(
169 listen_and_serve(config.rpc.clone().into(), explorer.clone(), None, ex.clone()),
170 |res| async move {
171 match res {
172 Ok(()) | Err(Error::RpcServerStopped) => explorer_.stop_connections().await,
173 Err(e) => {
174 error!(target: "explorerd", "Failed starting sync JSON-RPC server: {}", e)
175 }
176 }
177 },
178 Error::RpcServerStopped,
179 ex.clone(),
180 );
181 info!(target: "explorerd", "Started JSON-RPC server: {}", config.rpc.rpc_listen.to_string().trim_end_matches("/"));
182
183 let mut subscriber_task = None;
185 let mut listener_task = None;
186
187 if !args.no_sync {
189 explorer.connect().await?;
190
191 info!(target: "explorerd", "Syncing blocks from darkfid...");
193 if let Err(e) = explorer.service.sync_blocks(args.reset).await {
194 let error_message = format!("Error syncing blocks: {:?}", e);
195 error!(target: "explorerd", "{error_message}");
196 return Err(Error::DatabaseError(error_message));
197 }
198
199 info!(target: "explorerd", "Subscribing to new blocks...");
201 match subscribe_sync_blocks(explorer.clone(), config.endpoint.clone(), ex.clone()).await {
202 Ok((sub_task, lst_task)) => {
203 subscriber_task = Some(sub_task);
204 listener_task = Some(lst_task);
205 }
206 Err(e) => {
207 let error_message = format!("Error setting up blocks subscriber: {:?}", e);
208 error!(target: "explorerd", "{error_message}");
209 return Err(Error::DatabaseError(error_message));
210 }
211 };
212 }
213
214 log_started_banner(explorer.clone(), &config, &args, &config_path, args.no_sync);
215 info!(target: "explorerd::", "All is good. Waiting for block notifications...");
216
217 let (signals_handler, signals_task) = SignalHandler::new(ex)?;
219 signals_handler.wait_termination(signals_task).await?;
220 info!(target: "explorerd", "Caught termination signal, cleaning up and exiting...");
221
222 info!(target: "explorerd", "Stopping JSON-RPC server...");
223 rpc_task.stop().await;
224
225 if let Some(task) = listener_task {
227 info!(target: "explorerd", "Stopping darkfid listener...");
228 task.stop().await;
229 }
230
231 if let Some(task) = subscriber_task {
233 info!(target: "explorerd", "Stopping darkfid subscriber...");
234 task.stop().await;
235 }
236
237 info!(target: "explorerd", "Stopping JSON-RPC client...");
238 let _ = explorer.darkfid_client.stop().await;
239
240 Ok(())
241}
242
243fn log_started_banner(
245 explorer: Arc<Explorerd>,
246 config: &ExplorerNetworkConfig,
247 args: &Args,
248 config_path: &Path,
249 no_sync: bool,
250) {
251 let connected_node = if no_sync {
253 "Not connected".to_string()
254 } else {
255 config.endpoint.to_string().trim_end_matches('/').to_string()
256 };
257
258 info!(target: "explorerd", "========================================================================================");
260 info!(target: "explorerd", " Started DarkFi Explorer Node{} ",
261 if no_sync { " (No-Sync Mode)" } else { "" });
262 info!(target: "explorerd", "========================================================================================");
263 info!(target: "explorerd", " - Network: {}", args.network);
264 info!(target: "explorerd", " - JSON-RPC Endpoint: {}", config.rpc.rpc_listen.to_string().trim_end_matches('/'));
265 info!(target: "explorerd", " - Database: {}", config.database);
266 info!(target: "explorerd", " - Configuration: {}", config_path.to_str().unwrap_or("Error: configuration path not found!"));
267 info!(target: "explorerd", " - Reset Blocks: {}", if args.reset { "Yes" } else { "No" });
268 info!(target: "explorerd", "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~");
269 info!(target: "explorerd", " - Synced Blocks: {}", explorer.service.db.blockchain.len());
270 info!(target: "explorerd", " - Synced Transactions: {}", explorer.service.db.blockchain.len());
271 info!(target: "explorerd", " - Connected Darkfi Node: {}", connected_node);
272 info!(target: "explorerd", "========================================================================================");
273}