main.rs 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388
  1. /* This file is part of DarkFi (https://dark.fi)
  2. *
  3. * Copyright (C) 2020-2025 Dyne.org foundation
  4. *
  5. * This program is free software: you can redistribute it and/or modify
  6. * it under the terms of the GNU Affero General Public License as
  7. * published by the Free Software Foundation, either version 3 of the
  8. * License, or (at your option) any later version.
  9. *
  10. * This program is distributed in the hope that it will be useful,
  11. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  12. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  13. * GNU Affero General Public License for more details.
  14. *
  15. * You should have received a copy of the GNU Affero General Public License
  16. * along with this program. If not, see <https://www.gnu.org/licenses/>.
  17. */
  18. use std::{
  19. collections::{HashMap, HashSet},
  20. str::FromStr,
  21. sync::Arc,
  22. };
  23. use lazy_static::lazy_static;
  24. use log::{debug, error, info};
  25. use rpc_blocks::subscribe_blocks;
  26. use sled_overlay::sled;
  27. use smol::{lock::Mutex, stream::StreamExt};
  28. use structopt_toml::{serde::Deserialize, structopt::StructOpt, StructOptToml};
  29. use url::Url;
  30. use darkfi::{
  31. async_daemonize,
  32. blockchain::{Blockchain, BlockchainOverlay},
  33. cli_desc,
  34. rpc::{
  35. client::RpcClient,
  36. server::{listen_and_serve, RequestHandler},
  37. settings::RpcSettingsOpt,
  38. },
  39. system::{StoppableTask, StoppableTaskPtr},
  40. util::path::expand_path,
  41. validator::utils::deploy_native_contracts,
  42. Error, Result,
  43. };
  44. use darkfi_sdk::crypto::{ContractId, DAO_CONTRACT_ID, DEPLOYOOOR_CONTRACT_ID, MONEY_CONTRACT_ID};
  45. use crate::{
  46. contract_meta_store::{ContractMetaData, ContractMetaStore},
  47. contracts::untar_source,
  48. metrics_store::MetricsStore,
  49. };
  50. /// Crate errors
  51. mod error;
  52. /// JSON-RPC requests handler and methods
  53. mod rpc;
  54. mod rpc_blocks;
  55. mod rpc_contracts;
  56. mod rpc_statistics;
  57. mod rpc_transactions;
  58. /// Service functionality related to blocks
  59. mod blocks;
  60. /// Service functionality related to transactions
  61. mod transactions;
  62. /// Service functionality related to statistics
  63. mod statistics;
  64. /// Service functionality related to contracts
  65. mod contracts;
  66. /// Test utilities used for unit and integration testing
  67. mod test_utils;
  68. /// Database store functionality related to metrics
  69. mod metrics_store;
  70. /// Database store functionality related to contract metadata
  71. mod contract_meta_store;
  72. const CONFIG_FILE: &str = "explorerd_config.toml";
  73. const CONFIG_FILE_CONTENTS: &str = include_str!("../explorerd_config.toml");
  74. // Load the contract source archives to bootstrap them on explorer startup
  75. lazy_static! {
  76. static ref NATIVE_CONTRACT_SOURCE_ARCHIVES: HashMap<String, &'static [u8]> = {
  77. let mut src_map = HashMap::new();
  78. src_map.insert(
  79. MONEY_CONTRACT_ID.to_string(),
  80. &include_bytes!("../native_contracts_src/money_contract_src.tar")[..],
  81. );
  82. src_map.insert(
  83. DAO_CONTRACT_ID.to_string(),
  84. &include_bytes!("../native_contracts_src/dao_contract_src.tar")[..],
  85. );
  86. src_map.insert(
  87. DEPLOYOOOR_CONTRACT_ID.to_string(),
  88. &include_bytes!("../native_contracts_src/deployooor_contract_src.tar")[..],
  89. );
  90. src_map
  91. };
  92. }
  93. #[derive(Clone, Debug, Deserialize, StructOpt, StructOptToml)]
  94. #[serde(default)]
  95. #[structopt(name = "explorerd", about = cli_desc!())]
  96. struct Args {
  97. #[structopt(short, long)]
  98. /// Configuration file to use
  99. config: Option<String>,
  100. #[structopt(flatten)]
  101. /// JSON-RPC settings
  102. rpc: RpcSettingsOpt,
  103. #[structopt(long, default_value = "~/.local/share/darkfi/explorerd/daemon.db")]
  104. /// Path to daemon database
  105. db_path: String,
  106. #[structopt(long)]
  107. /// Reset the database and start syncing from first block
  108. reset: bool,
  109. #[structopt(short, long, default_value = "tcp://127.0.0.1:8340")]
  110. /// darkfid JSON-RPC endpoint
  111. endpoint: Url,
  112. #[structopt(short, long)]
  113. /// Set log file to output into
  114. log: Option<String>,
  115. #[structopt(short, parse(from_occurrences))]
  116. /// Increase verbosity (-vvv supported)
  117. verbose: u8,
  118. }
  119. /// Represents the service layer for the Explorer application, bridging the RPC layer and the database.
  120. /// It encapsulates explorer business logic and provides a unified interface for core functionalities,
  121. /// providing a clear separation of concerns between RPC handling and data management layers.
  122. ///
  123. /// Core functionalities include:
  124. ///
  125. /// - Data Transformation: Converting database data into structured responses suitable for RPC callers.
  126. /// - Blocks: Synchronization, retrieval, counting, and management.
  127. /// - Contracts: Handling native and user contract data, source code, tar files, and metadata.
  128. /// - Metrics: Providing metric-related data over the life of the chain.
  129. /// - Transactions: Synchronization, calculating gas data, retrieval, counting, and related block information.
  130. pub struct ExplorerService {
  131. /// Explorer database instance
  132. db: ExplorerDb,
  133. }
  134. impl ExplorerService {
  135. /// Creates a new `ExplorerService` instance.
  136. pub fn new(db_path: String) -> Result<Self> {
  137. // Initialize explorer database
  138. let db = ExplorerDb::new(db_path)?;
  139. Ok(Self { db })
  140. }
  141. /// Initializes the explorer service by deploying native contracts and loading native contract
  142. /// source code and metadata required for its operation.
  143. pub async fn init(&self) -> Result<()> {
  144. self.deploy_native_contracts().await?;
  145. self.load_native_contract_sources()?;
  146. self.load_native_contract_metadata()?;
  147. Ok(())
  148. }
  149. /// Deploys native contracts required for gas calculation and retrieval.
  150. pub async fn deploy_native_contracts(&self) -> Result<()> {
  151. let overlay = BlockchainOverlay::new(&self.db.blockchain)?;
  152. deploy_native_contracts(&overlay, 10).await?;
  153. overlay.lock().unwrap().overlay.lock().unwrap().apply()?;
  154. Ok(())
  155. }
  156. /// Loads native contract source code into the explorer database by extracting it from tar archives
  157. /// created during the explorer build process. The extracted source code is associated with
  158. /// the corresponding [`ContractId`] for each loaded contract and stored.
  159. pub fn load_native_contract_sources(&self) -> Result<()> {
  160. // Iterate each native contract source archive
  161. for (contract_id_str, archive_bytes) in NATIVE_CONTRACT_SOURCE_ARCHIVES.iter() {
  162. // Untar the native contract source code
  163. let source_code = untar_source(archive_bytes)?;
  164. // Parse contract id into a contract id instance
  165. let contract_id = &ContractId::from_str(contract_id_str)?;
  166. // Add source code into the `ContractMetaStore`
  167. self.db.contract_meta_store.insert_source(contract_id, &source_code)?;
  168. info!(target: "explorerd: load_native_contract_sources", "Successfully loaded contract source for native contract {}", contract_id_str.to_string());
  169. }
  170. Ok(())
  171. }
  172. /// Loads [`ContractMetaData`] for deployed native contracts into the explorer database by adding descriptive
  173. /// information (e.g., name and description) used to display contract details.
  174. pub fn load_native_contract_metadata(&self) -> Result<()> {
  175. let contract_ids = [*MONEY_CONTRACT_ID, *DAO_CONTRACT_ID, *DEPLOYOOOR_CONTRACT_ID];
  176. // Create pre-defined native contract metadata
  177. let metadatas = [
  178. ContractMetaData::new(
  179. "Money".to_string(),
  180. "Facilitates money transfers, atomic swaps, minting, freezing, and staking of consensus tokens".to_string(),
  181. ),
  182. ContractMetaData::new(
  183. "DAO".to_string(),
  184. "Provides functionality for Anonymous DAOs".to_string(),
  185. ),
  186. ContractMetaData::new(
  187. "Deployoor".to_string(),
  188. "Handles non-native smart contract deployments".to_string(),
  189. ),
  190. ];
  191. // Load contract metadata into the `ContractMetaStore`
  192. self.db.contract_meta_store.insert_metadata(&contract_ids, &metadatas)?;
  193. info!(target: "explorerd: load_native_contract_metadata", "Successfully loaded metadat for native contracts");
  194. Ok(())
  195. }
  196. /// Resets the explorer state to the specified height. If a genesis block height is provided,
  197. /// all blocks and transactions are purged from the database. Otherwise, the state is reverted
  198. /// to the given height. The explorer metrics are updated to reflect the updated blocks and
  199. /// transactions up to the reset height, ensuring consistency. Returns a result indicating
  200. /// success or an error if the operation fails.
  201. pub fn reset_explorer_state(&self, height: u32) -> Result<()> {
  202. debug!(target: "explorerd::reset_explorer_state", "Resetting explorer state to height: {height}");
  203. // Check if a genesis block reset or to a specific height
  204. match height {
  205. // Reset for genesis height 0, purge blocks and transactions
  206. 0 => {
  207. self.reset_blocks()?;
  208. self.reset_transactions()?;
  209. debug!(target: "explorerd::reset_explorer_state", "Successfully reset explorer state to accept a new genesis block");
  210. }
  211. // Reset for all other heights
  212. _ => {
  213. self.reset_to_height(height)?;
  214. debug!(target: "explorerd::reset_explorer_state", "Successfully reset blocks to height: {height}");
  215. }
  216. }
  217. // Reset gas metrics to the specified height to reflect the updated blockchain state
  218. self.db.metrics_store.reset_gas_metrics(height)?;
  219. debug!(target: "explorerd::reset_explorer_state", "Successfully reset metrics store to height: {height}");
  220. Ok(())
  221. }
  222. }
  223. /// Represents the explorer database backed by a `sled` database connection, responsible for maintaining
  224. /// persistent state required for blockchain exploration. It serves as the core data layer for the Explorer application,
  225. /// storing and managing blockchain data, metrics, and contract-related information.
  226. pub struct ExplorerDb {
  227. /// The main `sled` database connection used for data storage and retrieval
  228. pub sled_db: sled::Db,
  229. /// Local copy of the Darkfi blockchain used for block synchronization and exploration
  230. pub blockchain: Blockchain,
  231. /// Store for tracking chain-related metrics
  232. pub metrics_store: MetricsStore,
  233. /// Store for managing contract metadata, source code, and related data
  234. pub contract_meta_store: ContractMetaStore,
  235. }
  236. impl ExplorerDb {
  237. /// Creates a new `ExplorerDb` instance
  238. pub fn new(db_path: String) -> Result<Self> {
  239. let db_path = expand_path(db_path.as_str())?;
  240. let sled_db = sled::open(&db_path)?;
  241. let blockchain = Blockchain::new(&sled_db)?;
  242. let metrics_store = MetricsStore::new(&sled_db)?;
  243. let contract_meta_store = ContractMetaStore::new(&sled_db)?;
  244. info!(target: "explorerd", "Initialized explorer database {}: block count: {}, tx count: {}", db_path.display(), blockchain.len(), blockchain.txs_len());
  245. Ok(Self { sled_db, blockchain, metrics_store, contract_meta_store })
  246. }
  247. }
  248. /// Defines a daemon structure responsible for handling incoming JSON-RPC requests and delegating them
  249. /// to the backend layer for processing. It provides a JSON-RPC interface for managing operations related to
  250. /// blocks, transactions, contracts, and metrics.
  251. ///
  252. /// Upon startup, the daemon initializes a background task to handle incoming JSON-RPC requests.
  253. /// This includes processing operations related to blocks, transactions, contracts, and metrics by
  254. /// delegating them to the backend and returning appropriate RPC responses. Additionally, the daemon
  255. /// synchronizes blocks from the `darkfid` daemon into the explorer database and subscribes
  256. /// to new blocks, ensuring that the local database remains updated in real-time.
  257. pub struct Explorerd {
  258. /// Explorer service instance
  259. pub service: ExplorerService,
  260. /// JSON-RPC connection tracker
  261. pub rpc_connections: Mutex<HashSet<StoppableTaskPtr>>,
  262. /// JSON-RPC client to execute requests to darkfid daemon
  263. pub rpc_client: RpcClient,
  264. }
  265. impl Explorerd {
  266. /// Creates a new `BlockchainExplorer` instance.
  267. async fn new(db_path: String, endpoint: Url, ex: Arc<smol::Executor<'static>>) -> Result<Self> {
  268. // Initialize rpc client
  269. let rpc_client = RpcClient::new(endpoint.clone(), ex).await?;
  270. info!(target: "explorerd", "Connected to Darkfi node: {}", endpoint.to_string().trim_end_matches('/'));
  271. // Create explorer service
  272. let service = ExplorerService::new(db_path)?;
  273. // Initialize the explorer service
  274. service.init().await?;
  275. Ok(Self { rpc_connections: Mutex::new(HashSet::new()), rpc_client, service })
  276. }
  277. }
  278. async_daemonize!(realmain);
  279. async fn realmain(args: Args, ex: Arc<smol::Executor<'static>>) -> Result<()> {
  280. info!(target: "explorerd", "Initializing DarkFi blockchain explorer node...");
  281. let explorer = Explorerd::new(args.db_path, args.endpoint.clone(), ex.clone()).await?;
  282. let explorer = Arc::new(explorer);
  283. info!(target: "explorerd", "Node initialized successfully!");
  284. // JSON-RPC server
  285. // Here we create a task variable so we can manually close the task later.
  286. let rpc_task = StoppableTask::new();
  287. let explorer_ = explorer.clone();
  288. rpc_task.clone().start(
  289. listen_and_serve(args.rpc.clone().into(), explorer.clone(), None, ex.clone()),
  290. |res| async move {
  291. match res {
  292. Ok(()) | Err(Error::RpcServerStopped) => explorer_.stop_connections().await,
  293. Err(e) => {
  294. error!(target: "explorerd", "Failed starting sync JSON-RPC server: {}", e)
  295. }
  296. }
  297. },
  298. Error::RpcServerStopped,
  299. ex.clone(),
  300. );
  301. info!(target: "explorerd", "Started JSON-RPC server: {}", args.rpc.rpc_listen.to_string().trim_end_matches("/"));
  302. // Sync blocks
  303. info!(target: "explorerd", "Syncing blocks from darkfid...");
  304. if let Err(e) = explorer.sync_blocks(args.reset).await {
  305. let error_message = format!("Error syncing blocks: {:?}", e);
  306. error!(target: "explorerd", "{error_message}");
  307. return Err(Error::DatabaseError(error_message));
  308. }
  309. // Subscribe blocks
  310. info!(target: "explorerd", "Subscribing to new blocks...");
  311. let (subscriber_task, listener_task) =
  312. match subscribe_blocks(explorer.clone(), args.endpoint, ex.clone()).await {
  313. Ok(pair) => pair,
  314. Err(e) => {
  315. let error_message = format!("Error setting up blocks subscriber: {:?}", e);
  316. error!(target: "explorerd", "{error_message}");
  317. return Err(Error::DatabaseError(error_message));
  318. }
  319. };
  320. // Signal handling for graceful termination.
  321. let (signals_handler, signals_task) = SignalHandler::new(ex)?;
  322. signals_handler.wait_termination(signals_task).await?;
  323. info!(target: "explorerd", "Caught termination signal, cleaning up and exiting...");
  324. info!(target: "explorerd", "Stopping JSON-RPC server...");
  325. rpc_task.stop().await;
  326. info!(target: "explorerd", "Stopping darkfid listener...");
  327. listener_task.stop().await;
  328. info!(target: "explorerd", "Stopping darkfid subscriber...");
  329. subscriber_task.stop().await;
  330. info!(target: "explorerd", "Stopping JSON-RPC client...");
  331. explorer.rpc_client.stop().await;
  332. Ok(())
  333. }