darkfid/
lib.rs

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
19use std::{
20    collections::{HashMap, HashSet},
21    sync::Arc,
22};
23
24use smol::lock::Mutex;
25use tracing::{debug, error, info};
26
27use darkfi::{
28    blockchain::BlockInfo,
29    net::settings::Settings,
30    rpc::{
31        jsonrpc::JsonSubscriber,
32        server::{listen_and_serve, RequestHandler},
33        settings::RpcSettings,
34    },
35    system::{ExecutorPtr, StoppableTask, StoppableTaskPtr},
36    validator::{Validator, ValidatorConfig, ValidatorPtr},
37    zk::{empty_witnesses, ProvingKey, ZkCircuit},
38    zkas::ZkBinary,
39    Error, Result,
40};
41use darkfi_money_contract::MONEY_CONTRACT_ZKAS_MINT_NS_V1;
42use darkfi_sdk::crypto::{
43    keypair::{Network, SecretKey},
44    MONEY_CONTRACT_ID,
45};
46
47#[cfg(test)]
48mod tests;
49
50mod error;
51use error::{server_error, RpcError};
52
53/// JSON-RPC requests handler and methods
54mod rpc;
55use rpc::{DefaultRpcHandler, MmRpcHandler};
56mod rpc_blockchain;
57mod rpc_miner;
58mod rpc_tx;
59use rpc_miner::BlockTemplate;
60mod rpc_xmr;
61
62/// Validator async tasks
63pub mod task;
64use task::{consensus::ConsensusInitTaskConfig, consensus_init_task};
65
66/// P2P net protocols
67mod proto;
68use proto::{DarkfidP2pHandler, DarkfidP2pHandlerPtr};
69
70/// Atomic pointer to the DarkFi node
71pub type DarkfiNodePtr = Arc<DarkfiNode>;
72
73/// Structure representing a DarkFi node
74pub struct DarkfiNode {
75    /// Blockchain network
76    network: Network,
77    /// P2P network protocols handler.
78    p2p_handler: DarkfidP2pHandlerPtr,
79    /// Validator(node) pointer
80    validator: ValidatorPtr,
81    /// Garbage collection task transactions batch size
82    txs_batch_size: usize,
83    /// A map of various subscribers exporting live info from the blockchain
84    subscribers: HashMap<&'static str, JsonSubscriber>,
85    /// Native mining block templates
86    blocktemplates: Mutex<HashMap<Vec<u8>, BlockTemplate>>,
87    /// Merge mining block templates
88    mm_blocktemplates: Mutex<HashMap<Vec<u8>, (BlockInfo, f64, SecretKey)>>,
89    /// JSON-RPC connection tracker
90    rpc_connections: Mutex<HashSet<StoppableTaskPtr>>,
91    /// HTTP JSON-RPC connection tracker
92    mm_rpc_connections: Mutex<HashSet<StoppableTaskPtr>>,
93    /// PowRewardV1 ZK data
94    powrewardv1_zk: PowRewardV1Zk,
95}
96
97impl DarkfiNode {
98    pub async fn new(
99        network: Network,
100        p2p_handler: DarkfidP2pHandlerPtr,
101        validator: ValidatorPtr,
102        txs_batch_size: usize,
103        subscribers: HashMap<&'static str, JsonSubscriber>,
104    ) -> Result<DarkfiNodePtr> {
105        let powrewardv1_zk = PowRewardV1Zk::new(validator.clone())?;
106
107        Ok(Arc::new(Self {
108            network,
109            p2p_handler,
110            validator,
111            txs_batch_size,
112            subscribers,
113            blocktemplates: Mutex::new(HashMap::new()),
114            mm_blocktemplates: Mutex::new(HashMap::new()),
115            rpc_connections: Mutex::new(HashSet::new()),
116            mm_rpc_connections: Mutex::new(HashSet::new()),
117            powrewardv1_zk,
118        }))
119    }
120}
121
122/// ZK data used to generate the "coinbase" transaction in a block
123pub(crate) struct PowRewardV1Zk {
124    pub zkbin: ZkBinary,
125    pub provingkey: ProvingKey,
126}
127
128impl PowRewardV1Zk {
129    pub fn new(validator: ValidatorPtr) -> Result<Self> {
130        info!(
131            target: "darkfid::PowRewardV1Zk::new",
132            "Generating PowRewardV1 ZkCircuit and ProvingKey...",
133        );
134
135        let (zkbin, _) = validator.blockchain.contracts.get_zkas(
136            &validator.blockchain.sled_db,
137            &MONEY_CONTRACT_ID,
138            MONEY_CONTRACT_ZKAS_MINT_NS_V1,
139        )?;
140
141        let circuit = ZkCircuit::new(empty_witnesses(&zkbin)?, &zkbin);
142        let provingkey = ProvingKey::build(zkbin.k, &circuit);
143
144        Ok(Self { zkbin, provingkey })
145    }
146}
147
148/// Atomic pointer to the DarkFi daemon
149pub type DarkfidPtr = Arc<Darkfid>;
150
151/// Structure representing a DarkFi daemon
152pub struct Darkfid {
153    /// Darkfi node instance
154    node: DarkfiNodePtr,
155    /// `dnet` background task
156    dnet_task: StoppableTaskPtr,
157    /// JSON-RPC background task
158    rpc_task: StoppableTaskPtr,
159    /// HTTP JSON-RPC background task
160    mm_rpc_task: StoppableTaskPtr,
161    /// Consensus protocol background task
162    consensus_task: StoppableTaskPtr,
163}
164
165impl Darkfid {
166    /// Initialize a DarkFi daemon.
167    ///
168    /// Generates a new `DarkfiNode` for provided configuration,
169    /// along with all the corresponding background tasks.
170    pub async fn init(
171        network: Network,
172        sled_db: &sled_overlay::sled::Db,
173        config: &ValidatorConfig,
174        net_settings: &Settings,
175        txs_batch_size: &Option<usize>,
176        ex: &ExecutorPtr,
177    ) -> Result<DarkfidPtr> {
178        info!(target: "darkfid::Darkfid::init", "Initializing a Darkfi daemon...");
179        // Initialize validator
180        let validator = Validator::new(sled_db, config).await?;
181
182        // Initialize P2P network
183        let p2p_handler = DarkfidP2pHandler::init(net_settings, ex).await?;
184
185        // Grab blockchain network configured transactions batch size for garbage collection
186        let txs_batch_size = match txs_batch_size {
187            Some(b) => {
188                if *b > 0 {
189                    *b
190                } else {
191                    50
192                }
193            }
194            None => 50,
195        };
196
197        // Here we initialize various subscribers that can export live blockchain/consensus data.
198        let mut subscribers = HashMap::new();
199        subscribers.insert("blocks", JsonSubscriber::new("blockchain.subscribe_blocks"));
200        subscribers.insert("txs", JsonSubscriber::new("blockchain.subscribe_txs"));
201        subscribers.insert("proposals", JsonSubscriber::new("blockchain.subscribe_proposals"));
202        subscribers.insert("dnet", JsonSubscriber::new("dnet.subscribe_events"));
203
204        // Initialize node
205        let node =
206            DarkfiNode::new(network, p2p_handler, validator, txs_batch_size, subscribers).await?;
207
208        // Generate the background tasks
209        let dnet_task = StoppableTask::new();
210        let rpc_task = StoppableTask::new();
211        let mm_rpc_task = StoppableTask::new();
212        let consensus_task = StoppableTask::new();
213
214        info!(target: "darkfid::Darkfid::init", "Darkfi daemon initialized successfully!");
215
216        Ok(Arc::new(Self { node, dnet_task, rpc_task, mm_rpc_task, consensus_task }))
217    }
218
219    /// Start the DarkFi daemon in the given executor, using the provided JSON-RPC listen url
220    /// and consensus initialization configuration.
221    pub async fn start(
222        &self,
223        executor: &ExecutorPtr,
224        rpc_settings: &RpcSettings,
225        mm_rpc_settings: &Option<RpcSettings>,
226        config: &ConsensusInitTaskConfig,
227    ) -> Result<()> {
228        info!(target: "darkfid::Darkfid::start", "Starting Darkfi daemon...");
229
230        // Start the `dnet` task
231        info!(target: "darkfid::Darkfid::start", "Starting dnet subs task");
232        let dnet_sub_ = self.node.subscribers.get("dnet").unwrap().clone();
233        let p2p_ = self.node.p2p_handler.p2p.clone();
234        self.dnet_task.clone().start(
235            async move {
236                let dnet_sub = p2p_.dnet_subscribe().await;
237                loop {
238                    let event = dnet_sub.receive().await;
239                    debug!(target: "darkfid::Darkfid::dnet_task", "Got dnet event: {event:?}");
240                    dnet_sub_.notify(vec![event.into()].into()).await;
241                }
242            },
243            |res| async {
244                match res {
245                    Ok(()) | Err(Error::DetachedTaskStopped) => { /* Do nothing */ }
246                    Err(e) => error!(target: "darkfid::Darkfid::start", "Failed starting dnet subs task: {e}"),
247                }
248            },
249            Error::DetachedTaskStopped,
250            executor.clone(),
251        );
252
253        // Start the JSON-RPC task
254        info!(target: "darkfid::Darkfid::start", "Starting JSON-RPC server");
255        let node_ = self.node.clone();
256        self.rpc_task.clone().start(
257            listen_and_serve::<DefaultRpcHandler>(rpc_settings.clone(), self.node.clone(), None, executor.clone()),
258            |res| async move {
259                match res {
260                    Ok(()) | Err(Error::RpcServerStopped) => <DarkfiNode as RequestHandler<DefaultRpcHandler>>::stop_connections(&node_).await,
261                    Err(e) => error!(target: "darkfid::Darkfid::start", "Failed starting JSON-RPC server: {e}"),
262                }
263            },
264            Error::RpcServerStopped,
265            executor.clone(),
266        );
267
268        // Start the HTTP JSON-RPC task
269        if let Some(mm_rpc) = mm_rpc_settings {
270            info!(target: "darkfid::Darkfid::start", "Starting HTTP JSON-RPC server");
271            let node_ = self.node.clone();
272            self.mm_rpc_task.clone().start(
273                listen_and_serve::<MmRpcHandler>(mm_rpc.clone(), self.node.clone(), None, executor.clone()),
274                |res| async move {
275                    match res {
276                        Ok(()) | Err(Error::RpcServerStopped) => <DarkfiNode as RequestHandler<MmRpcHandler>>::stop_connections(&node_).await,
277                        Err(e) => error!(target: "darkfid::Darkfid::start", "Failed starting HTTP JSON-RPC server: {e}"),
278                    }
279                },
280                Error::RpcServerStopped,
281                executor.clone(),
282            );
283        } else {
284            // Create a dummy task
285            self.mm_rpc_task.clone().start(
286                async { Ok(()) },
287                |_| async { /* Do nothing */ },
288                Error::RpcServerStopped,
289                executor.clone(),
290            );
291        }
292
293        // Start the P2P network
294        info!(target: "darkfid::Darkfid::start", "Starting P2P network");
295        self.node
296            .p2p_handler
297            .clone()
298            .start(executor, &self.node.validator, &self.node.subscribers)
299            .await?;
300
301        // Start the consensus protocol
302        info!(target: "darkfid::Darkfid::start", "Starting consensus protocol task");
303        self.consensus_task.clone().start(
304            consensus_init_task(
305                self.node.clone(),
306                config.clone(),
307                executor.clone(),
308            ),
309            |res| async move {
310                match res {
311                    Ok(()) | Err(Error::ConsensusTaskStopped) | Err(Error::MinerTaskStopped) => { /* Do nothing */ }
312                    Err(e) => error!(target: "darkfid::Darkfid::start", "Failed starting consensus initialization task: {e}"),
313                }
314            },
315            Error::ConsensusTaskStopped,
316            executor.clone(),
317        );
318
319        info!(target: "darkfid::Darkfid::start", "Darkfi daemon started successfully!");
320        Ok(())
321    }
322
323    /// Stop the DarkFi daemon.
324    pub async fn stop(&self) -> Result<()> {
325        info!(target: "darkfid::Darkfid::stop", "Terminating Darkfi daemon...");
326
327        // Stop the `dnet` node
328        info!(target: "darkfid::Darkfid::stop", "Stopping dnet subs task...");
329        self.dnet_task.stop().await;
330
331        // Stop the JSON-RPC task
332        info!(target: "darkfid::Darkfid::stop", "Stopping JSON-RPC server...");
333        self.rpc_task.stop().await;
334
335        // Stop the HTTP JSON-RPC task
336        info!(target: "darkfid::Darkfid::stop", "Stopping HTTP JSON-RPC server...");
337        self.rpc_task.stop().await;
338
339        // Stop the P2P network
340        info!(target: "darkfid::Darkfid::stop", "Stopping P2P network protocols handler...");
341        self.node.p2p_handler.stop().await;
342
343        // Stop the consensus task
344        info!(target: "darkfid::Darkfid::stop", "Stopping consensus task...");
345        self.consensus_task.stop().await;
346
347        // Flush sled database data
348        info!(target: "darkfid::Darkfid::stop", "Flushing sled database...");
349        let flushed_bytes = self.node.validator.blockchain.sled_db.flush_async().await?;
350        info!(target: "darkfid::Darkfid::stop", "Flushed {flushed_bytes} bytes");
351
352        info!(target: "darkfid::Darkfid::stop", "Darkfi daemon terminated successfully!");
353        Ok(())
354    }
355}