Browse Source

darkfid: programmatic control daemon

skoupidi 1 năm trước cách đây
mục cha
commit
a2d68cc914

+ 2 - 11
bin/darkfid/darkfid_config.toml

@@ -29,9 +29,6 @@ pow_target = 10
 # Optional fixed PoW difficulty, used for testing
 pow_fixed_difficulty = 1
 
-# Participate in block production
-miner = true
-
 # Wallet address to receive mining rewards.
 # This is a dummy one so the miner can start,
 # replace with your own one.
@@ -130,14 +127,11 @@ database = "~/.local/darkfi/darkfid/testnet"
 threshold = 6
 
 # minerd JSON-RPC endpoint
-minerd_endpoint = "tcp://127.0.0.1:28467"
+#minerd_endpoint = "tcp://127.0.0.1:28467"
 
 # PoW block production target, in seconds
 pow_target = 90
 
-# Participate in block production
-miner = false
-
 # Wallet address to receive mining rewards.
 #recipient = "YOUR_WALLET_ADDRESS_HERE"
 
@@ -238,14 +232,11 @@ database = "~/.local/darkfi/darkfid/mainnet"
 threshold = 11
 
 # minerd JSON-RPC endpoint
-minerd_endpoint = "tcp://127.0.0.1:28467"
+#minerd_endpoint = "tcp://127.0.0.1:28467"
 
 # PoW block production target, in seconds
 pow_target = 90
 
-# Participate in block production
-miner = false
-
 # Wallet address to receive mining rewards.
 #recipient = "YOUR_WALLET_ADDRESS_HERE"
 

+ 312 - 0
bin/darkfid/src/lib.rs

@@ -0,0 +1,312 @@
+/* This file is part of DarkFi (https://dark.fi)
+ *
+ * Copyright (C) 2020-2024 Dyne.org foundation
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <https://www.gnu.org/licenses/>.
+ */
+
+use std::{
+    collections::{HashMap, HashSet},
+    sync::Arc,
+};
+
+use log::{debug, error, info};
+use smol::lock::Mutex;
+use url::Url;
+
+use darkfi::{
+    net::settings::Settings,
+    rpc::{
+        client::RpcChadClient,
+        jsonrpc::JsonSubscriber,
+        server::{listen_and_serve, RequestHandler},
+    },
+    system::{ExecutorPtr, StoppableTask, StoppableTaskPtr},
+    validator::{Validator, ValidatorConfig, ValidatorPtr},
+    Error, Result,
+};
+
+#[cfg(test)]
+mod tests;
+
+mod error;
+use error::{server_error, RpcError};
+
+/// JSON-RPC requests handler and methods
+mod rpc;
+mod rpc_blockchain;
+mod rpc_tx;
+
+/// Validator async tasks
+pub mod task;
+use task::{consensus::ConsensusInitTaskConfig, consensus_init_task};
+
+/// P2P net protocols
+mod proto;
+use proto::{DarkfidP2pHandler, DarkfidP2pHandlerPtr};
+
+/// Structure to hold a JSON-RPC client and its config,
+/// so we can recreate it in case of an error.
+pub struct MinerRpcClient {
+    endpoint: Url,
+    ex: ExecutorPtr,
+    client: RpcChadClient,
+}
+
+impl MinerRpcClient {
+    pub async fn new(endpoint: Url, ex: ExecutorPtr) -> Result<Self> {
+        let client = RpcChadClient::new(endpoint.clone(), ex.clone()).await?;
+        Ok(Self { endpoint, ex, client })
+    }
+}
+
+/// Atomic pointer to the DarkFi node
+pub type DarkfiNodePtr = Arc<DarkfiNode>;
+
+/// Structure representing a DarkFi node
+pub struct DarkfiNode {
+    /// P2P network protocols handler.
+    p2p_handler: DarkfidP2pHandlerPtr,
+    /// Validator(node) pointer
+    validator: ValidatorPtr,
+    /// Garbage collection task transactions batch size
+    txs_batch_size: usize,
+    /// A map of various subscribers exporting live info from the blockchain
+    subscribers: HashMap<&'static str, JsonSubscriber>,
+    /// JSON-RPC connection tracker
+    rpc_connections: Mutex<HashSet<StoppableTaskPtr>>,
+    /// JSON-RPC client to execute requests to the miner daemon
+    rpc_client: Option<Mutex<MinerRpcClient>>,
+}
+
+impl DarkfiNode {
+    pub async fn new(
+        p2p_handler: DarkfidP2pHandlerPtr,
+        validator: ValidatorPtr,
+        txs_batch_size: usize,
+        subscribers: HashMap<&'static str, JsonSubscriber>,
+        rpc_client: Option<Mutex<MinerRpcClient>>,
+    ) -> DarkfiNodePtr {
+        Arc::new(Self {
+            p2p_handler,
+            validator,
+            txs_batch_size,
+            subscribers,
+            rpc_connections: Mutex::new(HashSet::new()),
+            rpc_client,
+        })
+    }
+}
+
+/// Atomic pointer to the DarkFi daemon
+pub type DarkfidPtr = Arc<Darkfid>;
+
+/// Structure representing a DarkFi daemon
+pub struct Darkfid {
+    /// Darkfi node instance
+    node: DarkfiNodePtr,
+    /// `dnet` background task
+    dnet_task: StoppableTaskPtr,
+    /// JSON-RPC background task
+    rpc_task: StoppableTaskPtr,
+    /// Consensus protocol background task
+    consensus_task: StoppableTaskPtr,
+}
+
+impl Darkfid {
+    /// Initialize a DarkFi daemon.
+    ///
+    /// Generates a new `DarkfiNode` for provided configuration,
+    /// along with all the corresponding background tasks.
+    pub async fn init(
+        sled_db: &sled_overlay::sled::Db,
+        config: &ValidatorConfig,
+        net_settings: &Settings,
+        minerd_endpoint: &Option<Url>,
+        txs_batch_size: &Option<usize>,
+        ex: &ExecutorPtr,
+    ) -> Result<DarkfidPtr> {
+        info!(target: "darkfid::Darkfid::init", "Initializing a Darkfi daemon...");
+        // Initialize validator
+        let validator = Validator::new(&sled_db, config).await?;
+
+        // Initialize P2P network
+        let p2p_handler = DarkfidP2pHandler::init(net_settings, ex).await?;
+
+        // Grab blockchain network configured transactions batch size for garbage collection
+        let txs_batch_size = match txs_batch_size {
+            Some(b) => {
+                if *b > 0 {
+                    *b
+                } else {
+                    50
+                }
+            }
+            None => 50,
+        };
+
+        // Here we initialize various subscribers that can export live blockchain/consensus data.
+        let mut subscribers = HashMap::new();
+        subscribers.insert("blocks", JsonSubscriber::new("blockchain.subscribe_blocks"));
+        subscribers.insert("txs", JsonSubscriber::new("blockchain.subscribe_txs"));
+        subscribers.insert("proposals", JsonSubscriber::new("blockchain.subscribe_proposals"));
+        subscribers.insert("dnet", JsonSubscriber::new("dnet.subscribe_events"));
+
+        // Initialize JSON-RPC client to perform requests to minerd
+        let rpc_client = match minerd_endpoint {
+            Some(endpoint) => {
+                let Ok(rpc_client) = MinerRpcClient::new(endpoint.clone(), ex.clone()).await else {
+                    error!(target: "darkfid::Darkfid::init", "Failed to initialize miner daemon rpc client, check if minerd is running");
+                    return Err(Error::RpcClientStopped)
+                };
+                Some(Mutex::new(rpc_client))
+            }
+            None => None,
+        };
+
+        // Initialize node
+        let node =
+            DarkfiNode::new(p2p_handler, validator, txs_batch_size, subscribers, rpc_client).await;
+
+        // Generate the background tasks
+        let dnet_task = StoppableTask::new();
+        let rpc_task = StoppableTask::new();
+        let consensus_task = StoppableTask::new();
+
+        info!(target: "darkfid::Darkfid::init", "Darkfi daemon initialized successfully!");
+
+        Ok(Arc::new(Self { node, dnet_task, rpc_task, consensus_task }))
+    }
+
+    /// Start the DarkFi daemon in the given executor, using the provided JSON-RPC listen url
+    /// and consensus initialization configuration.
+    pub async fn start(
+        &self,
+        executor: &ExecutorPtr,
+        rpc_listen: &Url,
+        config: &ConsensusInitTaskConfig,
+    ) -> Result<()> {
+        info!(target: "darkfid::Darkfid::start", "Starting Darkfi daemon...");
+
+        // Pinging minerd daemon to verify it listens
+        if self.node.rpc_client.is_some() {
+            if let Err(e) = self.node.ping_miner_daemon().await {
+                error!(target: "darkfid::Darkfid::start", "Failed to ping miner daemon: {}", e);
+                return Err(Error::RpcClientStopped)
+            }
+        }
+
+        // Start the `dnet` task
+        info!(target: "darkfid::Darkfid::start", "Starting dnet subs task");
+        let dnet_sub_ = self.node.subscribers.get("dnet").unwrap().clone();
+        let p2p_ = self.node.p2p_handler.p2p.clone();
+        self.dnet_task.clone().start(
+            async move {
+                let dnet_sub = p2p_.dnet_subscribe().await;
+                loop {
+                    let event = dnet_sub.receive().await;
+                    debug!(target: "darkfid::Darkfid::dnet_task", "Got dnet event: {:?}", event);
+                    dnet_sub_.notify(vec![event.into()].into()).await;
+                }
+            },
+            |res| async {
+                match res {
+                    Ok(()) | Err(Error::DetachedTaskStopped) => { /* Do nothing */ }
+                    Err(e) => error!(target: "darkfid::Darkfid::start", "Failed starting dnet subs task: {}", e),
+                }
+            },
+            Error::DetachedTaskStopped,
+            executor.clone(),
+        );
+
+        // Start the JSON-RPC task
+        info!(target: "darkfid::Darkfid::start", "Starting JSON-RPC server");
+        let node_ = self.node.clone();
+        self.rpc_task.clone().start(
+            listen_and_serve(rpc_listen.clone(), self.node.clone(), None, executor.clone()),
+            |res| async move {
+                match res {
+                    Ok(()) | Err(Error::RpcServerStopped) => node_.stop_connections().await,
+                    Err(e) => error!(target: "darkfid::Darkfid::start", "Failed starting JSON-RPC server: {}", e),
+                }
+            },
+            Error::RpcServerStopped,
+            executor.clone(),
+        );
+
+        // Start the P2P network
+        info!(target: "darkfid::Darkfid::start", "Starting P2P network");
+        self.node
+            .p2p_handler
+            .clone()
+            .start(executor, &self.node.validator, &self.node.subscribers)
+            .await?;
+
+        // Start the consensus protocol
+        info!(target: "darkfid::Darkfid::start", "Starting consensus protocol task");
+        self.consensus_task.clone().start(
+            consensus_init_task(
+                self.node.clone(),
+                config.clone(),
+                executor.clone(),
+            ),
+            |res| async move {
+                match res {
+                    Ok(()) | Err(Error::ConsensusTaskStopped) | Err(Error::MinerTaskStopped) => { /* Do nothing */ }
+                    Err(e) => error!(target: "darkfid::Darkfid::start", "Failed starting consensus initialization task: {}", e),
+                }
+            },
+            Error::ConsensusTaskStopped,
+            executor.clone(),
+        );
+
+        info!(target: "darkfid::Darkfid::start", "Darkfi daemon started successfully!");
+        Ok(())
+    }
+
+    /// Stop the DarkFi daemon.
+    pub async fn stop(&self) -> Result<()> {
+        info!(target: "darkfid::Darkfid::stop", "Terminating Darkfi daemon...");
+
+        // Stop the `dnet` node
+        info!(target: "darkfid::Darkfid::stop", "Stopping dnet subs task...");
+        self.dnet_task.stop().await;
+
+        // Stop the JSON-RPC task
+        info!(target: "darkfid::Darkfid::stop", "Stopping JSON-RPC server...");
+        self.rpc_task.stop().await;
+
+        // Stop the P2P network
+        info!(target: "darkfid::Darkfid::stop", "Stopping P2P network protocols handler...");
+        self.node.p2p_handler.stop().await;
+
+        // Stop the consensus task
+        info!(target: "darkfid::Darkfid::stop", "Stopping consensus task...");
+        self.consensus_task.stop().await;
+
+        // Flush sled database data
+        info!(target: "darkfid::Darkfid::stop", "Flushing sled database...");
+        let flushed_bytes = self.node.validator.blockchain.sled_db.flush_async().await?;
+        info!(target: "darkfid::Darkfid::stop", "Flushed {} bytes", flushed_bytes);
+
+        // Close the JSON-RPC client, if it was initialized
+        if let Some(ref rpc_client) = self.node.rpc_client {
+            info!(target: "darkfid::Darkfid::stop", "Stopping JSON-RPC client...");
+            rpc_client.lock().await.client.stop().await;
+        };
+
+        info!(target: "darkfid::Darkfid::stop", "Darkfi daemon terminated successfully!");
+        Ok(())
+    }
+}

+ 90 - 261
bin/darkfid/src/main.rs

@@ -16,13 +16,10 @@
  * along with this program.  If not, see <https://www.gnu.org/licenses/>.
  */
 
-use std::{
-    collections::{HashMap, HashSet},
-    sync::Arc,
-};
+use std::sync::Arc;
 
 use log::{debug, error, info};
-use smol::{lock::Mutex, stream::StreamExt};
+use smol::{fs::read_to_string, stream::StreamExt};
 use structopt_toml::{serde::Deserialize, structopt::StructOpt, StructOptToml};
 use url::Url;
 
@@ -31,40 +28,16 @@ use darkfi::{
     blockchain::BlockInfo,
     cli_desc,
     net::settings::SettingsOpt,
-    rpc::{
-        client::RpcChadClient,
-        jsonrpc::JsonSubscriber,
-        server::{listen_and_serve, RequestHandler},
+    util::{
+        encoding::base64,
+        path::{expand_path, get_config_path},
     },
-    system::{StoppableTask, StoppableTaskPtr},
-    util::{encoding::base64, path::expand_path},
-    validator::{Validator, ValidatorConfig, ValidatorPtr},
+    validator::ValidatorConfig,
     Error, Result,
 };
 use darkfi_serial::deserialize_async;
 
-#[cfg(test)]
-mod tests;
-
-mod error;
-use error::{server_error, RpcError};
-
-/// JSON-RPC requests handler and methods
-mod rpc;
-mod rpc_blockchain;
-mod rpc_tx;
-
-/// Validator async tasks
-mod task;
-use task::{consensus::ConsensusInitTaskConfig, consensus_init_task};
-
-/// P2P net protocols
-mod proto;
-use proto::{DarkfidP2pHandler, DarkfidP2pHandlerPtr};
-
-/// Utility functions
-mod utils;
-use utils::parse_blockchain_config;
+use darkfid::{task::consensus::ConsensusInitTaskConfig, Darkfid};
 
 const CONFIG_FILE: &str = "darkfid_config.toml";
 const CONFIG_FILE_CONTENTS: &str = include_str!("../darkfid_config.toml");
@@ -103,129 +76,67 @@ struct Args {
 pub struct BlockchainNetwork {
     #[structopt(short, long, default_value = "tcp://127.0.0.1:8240")]
     /// JSON-RPC listen URL
-    pub rpc_listen: Url,
+    rpc_listen: Url,
 
     #[structopt(long, default_value = "~/.local/darkfi/darkfid/localnet")]
     /// Path to blockchain database
-    pub database: String,
+    database: String,
 
     #[structopt(long, default_value = "3")]
     /// Finalization threshold, denominated by number of blocks
-    pub threshold: usize,
+    threshold: usize,
 
-    #[structopt(long, default_value = "tcp://127.0.0.1:28467")]
+    #[structopt(long)]
     /// minerd JSON-RPC endpoint
-    pub minerd_endpoint: Url,
+    minerd_endpoint: Option<Url>,
 
     #[structopt(long, default_value = "10")]
     /// PoW block production target, in seconds
-    pub pow_target: u32,
+    pow_target: u32,
 
     #[structopt(long)]
     /// Optional fixed PoW difficulty, used for testing
-    pub pow_fixed_difficulty: Option<usize>,
-
-    #[structopt(long)]
-    /// Participate in block production
-    pub miner: bool,
+    pow_fixed_difficulty: Option<usize>,
 
     #[structopt(long)]
     /// Wallet address to receive mining rewards
-    pub recipient: Option<String>,
+    recipient: Option<String>,
 
     #[structopt(long)]
     /// Optional contract spend hook to use in the mining reward
-    pub spend_hook: Option<String>,
+    spend_hook: Option<String>,
 
     #[structopt(long)]
     /// Optional user data to use in the mining reward
-    pub user_data: Option<String>,
+    user_data: Option<String>,
 
     #[structopt(long)]
     /// Skip syncing process and start node right away
-    pub skip_sync: bool,
+    skip_sync: bool,
 
     #[structopt(long)]
     /// Disable transaction's fee verification, used for testing
-    pub skip_fees: bool,
+    skip_fees: bool,
 
     #[structopt(long)]
     /// Optional sync checkpoint height
-    pub checkpoint_height: Option<u32>,
+    checkpoint_height: Option<u32>,
 
     #[structopt(long)]
     /// Optional sync checkpoint hash
-    pub checkpoint: Option<String>,
+    checkpoint: Option<String>,
 
     #[structopt(long)]
     /// Optional bootstrap timestamp
-    pub bootstrap: Option<u64>,
+    bootstrap: Option<u64>,
 
     #[structopt(long)]
     /// Garbage collection task transactions batch size
-    pub txs_batch_size: Option<usize>,
+    txs_batch_size: Option<usize>,
 
     /// P2P network settings
     #[structopt(flatten)]
-    pub net: SettingsOpt,
-}
-
-/// Structure to hold a JSON-RPC client and its config,
-/// so we can recreate it in case of an error.
-pub struct MinerRpcCLient {
-    endpoint: Url,
-    ex: Arc<smol::Executor<'static>>,
-    client: RpcChadClient,
-}
-
-impl MinerRpcCLient {
-    pub async fn new(endpoint: Url, ex: Arc<smol::Executor<'static>>) -> Result<Self> {
-        let client = RpcChadClient::new(endpoint.clone(), ex.clone()).await?;
-        Ok(Self { endpoint, ex, client })
-    }
-}
-
-/// Daemon structure
-pub struct Darkfid {
-    /// P2P network protocols handler.
-    p2p_handler: DarkfidP2pHandlerPtr,
-    /// Validator(node) pointer
-    validator: ValidatorPtr,
-    /// Flag to specify node is a miner
-    miner: bool,
-    /// Garbage collection task transactions batch size
-    txs_batch_size: usize,
-    /// A map of various subscribers exporting live info from the blockchain
-    subscribers: HashMap<&'static str, JsonSubscriber>,
-    /// JSON-RPC connection tracker
-    rpc_connections: Mutex<HashSet<StoppableTaskPtr>>,
-    /// JSON-RPC client to execute requests to the miner daemon
-    rpc_client: Option<Mutex<MinerRpcCLient>>,
-    /// dnet JSON-RPC subscriber
-    dnet_sub: JsonSubscriber,
-}
-
-impl Darkfid {
-    pub async fn new(
-        p2p_handler: DarkfidP2pHandlerPtr,
-        validator: ValidatorPtr,
-        miner: bool,
-        txs_batch_size: usize,
-        subscribers: HashMap<&'static str, JsonSubscriber>,
-        rpc_client: Option<Mutex<MinerRpcCLient>>,
-        dnet_sub: JsonSubscriber,
-    ) -> Self {
-        Self {
-            p2p_handler,
-            validator,
-            miner,
-            txs_batch_size,
-            subscribers,
-            rpc_connections: Mutex::new(HashSet::new()),
-            rpc_client,
-            dnet_sub,
-        }
-    }
+    net: SettingsOpt,
 }
 
 async_daemonize!(realmain);
@@ -279,165 +190,83 @@ async fn realmain(args: Args, ex: Arc<smol::Executor<'static>>) -> Result<()> {
         verify_fees: !blockchain_config.skip_fees,
     };
 
-    // Initialize validator
-    let validator = Validator::new(&sled_db, config).await?;
-
-    // Here we initialize various subscribers that can export live blockchain/consensus data.
-    let mut subscribers = HashMap::new();
-    subscribers.insert("blocks", JsonSubscriber::new("blockchain.subscribe_blocks"));
-    subscribers.insert("txs", JsonSubscriber::new("blockchain.subscribe_txs"));
-    subscribers.insert("proposals", JsonSubscriber::new("blockchain.subscribe_proposals"));
-
-    // Initialize P2P network
-    let p2p_handler = DarkfidP2pHandler::init(&blockchain_config.net.into(), &ex).await?;
-
-    // Initialize JSON-RPC client to perform requests to minerd
-    let rpc_client = if blockchain_config.miner {
-        let Ok(rpc_client) =
-            MinerRpcCLient::new(blockchain_config.minerd_endpoint, ex.clone()).await
-        else {
-            error!(target: "darkfid", "Failed to initialize miner daemon rpc client, check if minerd is running");
-            return Err(Error::RpcClientStopped)
-        };
-        Some(Mutex::new(rpc_client))
-    } else {
-        None
-    };
-
-    // Grab blockchain network configured transactions batch size for garbage collection
-    let txs_batch_size = match blockchain_config.txs_batch_size {
-        Some(b) => {
-            if b > 0 {
-                b
-            } else {
-                50
-            }
-        }
-        None => 50,
-    };
-
-    info!(target: "darkfid", "Starting dnet subs task");
-    let dnet_sub = JsonSubscriber::new("dnet.subscribe_events");
-    let dnet_sub_ = dnet_sub.clone();
-    let p2p_ = p2p_handler.p2p.clone();
-    let dnet_task = StoppableTask::new();
-    dnet_task.clone().start(
-        async move {
-            let dnet_sub = p2p_.dnet_subscribe().await;
-            loop {
-                let event = dnet_sub.receive().await;
-                debug!(target: "darkfid", "Got dnet event: {:?}", event);
-                dnet_sub_.notify(vec![event.into()].into()).await;
-            }
-        },
-        |res| async {
-            match res {
-                Ok(()) | Err(Error::DetachedTaskStopped) => { /* Do nothing */ }
-                Err(e) => error!(target: "darkfid", "Failed starting dnet subs task: {}", e),
-            }
-        },
-        Error::DetachedTaskStopped,
-        ex.clone(),
-    );
-
-    // Initialize node
-    let darkfid = Darkfid::new(
-        p2p_handler.clone(),
-        validator.clone(),
-        blockchain_config.miner,
-        txs_batch_size,
-        subscribers.clone(),
-        rpc_client,
-        dnet_sub,
+    // Generate the daemon
+    let daemon = Darkfid::init(
+        &sled_db,
+        &config,
+        &blockchain_config.net.into(),
+        &blockchain_config.minerd_endpoint,
+        &blockchain_config.txs_batch_size,
+        &ex,
     )
-    .await;
-    let darkfid = Arc::new(darkfid);
-    info!(target: "darkfid", "Node initialized successfully!");
-
-    // Pinging minerd daemon to verify it listens
-    if blockchain_config.miner {
-        if let Err(e) = darkfid.ping_miner_daemon().await {
-            error!(target: "darkfid", "Failed to ping miner daemon: {}", e);
-            return Err(Error::RpcClientStopped)
-        }
-    }
-
-    // JSON-RPC server
-    info!(target: "darkfid", "Starting JSON-RPC server");
-    // Here we create a task variable so we can manually close the
-    // task later. P2P tasks don't need this since it has its own
-    // stop() function to shut down, also terminating the task we
-    // created for it.
-    let rpc_task = StoppableTask::new();
-    let darkfid_ = darkfid.clone();
-    rpc_task.clone().start(
-        listen_and_serve(blockchain_config.rpc_listen, darkfid.clone(), None, ex.clone()),
-        |res| async move {
-            match res {
-                Ok(()) | Err(Error::RpcServerStopped) => darkfid_.stop_connections().await,
-                Err(e) => error!(target: "darkfid", "Failed starting sync JSON-RPC server: {}", e),
-            }
-        },
-        Error::RpcServerStopped,
-        ex.clone(),
-    );
-
-    info!(target: "darkfid", "Starting P2P network");
-    p2p_handler.clone().start(&ex, &validator, &subscribers).await?;
-
-    // Consensus protocol
-    info!(target: "darkfid", "Starting consensus protocol task");
-    let consensus_task = StoppableTask::new();
-    consensus_task.clone().start(
-        consensus_init_task(
-            darkfid.clone(),
-            ConsensusInitTaskConfig {
-                skip_sync: blockchain_config.skip_sync,
-                checkpoint_height: blockchain_config.checkpoint_height,
-                checkpoint: blockchain_config.checkpoint,
-                miner: blockchain_config.miner,
-                recipient: blockchain_config.recipient,
-                spend_hook: blockchain_config.spend_hook,
-                user_data: blockchain_config.user_data,
-                bootstrap,
-            },
-            ex.clone(),
-        ),
-        |res| async move {
-            match res {
-                Ok(()) | Err(Error::ConsensusTaskStopped) | Err(Error::MinerTaskStopped) => { /* Do nothing */ }
-                Err(e) => error!(target: "darkfid", "Failed starting consensus initialization task: {}", e),
-            }
-        },
-        Error::ConsensusTaskStopped,
-        ex.clone(),
-    );
+    .await?;
+
+    // Start the daemon
+    let config = ConsensusInitTaskConfig {
+        skip_sync: blockchain_config.skip_sync,
+        checkpoint_height: blockchain_config.checkpoint_height,
+        checkpoint: blockchain_config.checkpoint,
+        miner: blockchain_config.minerd_endpoint.is_some(),
+        recipient: blockchain_config.recipient,
+        spend_hook: blockchain_config.spend_hook,
+        user_data: blockchain_config.user_data,
+        bootstrap,
+    };
+    daemon.start(&ex, &blockchain_config.rpc_listen, &config).await?;
 
     // Signal handling for graceful termination.
     let (signals_handler, signals_task) = SignalHandler::new(ex)?;
     signals_handler.wait_termination(signals_task).await?;
     info!(target: "darkfid", "Caught termination signal, cleaning up and exiting...");
 
-    info!(target: "darkfid", "Stopping dnet subs task...");
-    dnet_task.stop().await;
+    daemon.stop().await?;
 
-    info!(target: "darkfid", "Stopping JSON-RPC server...");
-    rpc_task.stop().await;
+    info!(target: "darkfid", "Shut down successfully");
 
-    info!(target: "darkfid", "Stopping P2P network protocols handler...");
-    p2p_handler.stop().await;
-
-    info!(target: "darkfid", "Stopping consensus task...");
-    consensus_task.stop().await;
+    Ok(())
+}
 
-    info!(target: "darkfid", "Flushing sled database...");
-    let flushed_bytes = sled_db.flush_async().await?;
-    info!(target: "darkfid", "Flushed {} bytes", flushed_bytes);
+/// Auxiliary function to parse darkfid configuration file and extract requested
+/// blockchain network config.
+pub async fn parse_blockchain_config(
+    config: Option<String>,
+    network: &str,
+) -> Result<BlockchainNetwork> {
+    // Grab config path
+    let config_path = get_config_path(config, CONFIG_FILE)?;
+    debug!(target: "darkfid", "Parsing configuration file: {:?}", config_path);
+
+    // Parse TOML file contents
+    let contents = read_to_string(&config_path).await?;
+    let contents: toml::Value = match toml::from_str(&contents) {
+        Ok(v) => v,
+        Err(e) => {
+            error!(target: "darkfid", "Failed parsing TOML config: {}", e);
+            return Err(Error::ParseFailed("Failed parsing TOML config"))
+        }
+    };
 
-    if let Some(ref rpc_client) = darkfid.rpc_client {
-        info!(target: "darkfid", "Stopping JSON-RPC client...");
-        rpc_client.lock().await.client.stop().await;
+    // Grab requested network config
+    let Some(table) = contents.as_table() else { return Err(Error::ParseFailed("TOML not a map")) };
+    let Some(network_configs) = table.get("network_config") else {
+        return Err(Error::ParseFailed("TOML does not contain network configurations"))
     };
+    let Some(network_configs) = network_configs.as_table() else {
+        return Err(Error::ParseFailed("`network_config` not a map"))
+    };
+    let Some(network_config) = network_configs.get(network) else {
+        return Err(Error::ParseFailed("TOML does not contain requested network configuration"))
+    };
+    let network_config = toml::to_string(&network_config).unwrap();
+    let network_config =
+        match BlockchainNetwork::from_iter_with_toml::<Vec<String>>(&network_config, vec![]) {
+            Ok(v) => v,
+            Err(e) => {
+                error!(target: "darkfid", "Failed parsing requested network configuration: {}", e);
+                return Err(Error::ParseFailed("Failed parsing requested network configuration"))
+            }
+        };
+    debug!(target: "darkfid", "Parsed network configuration: {:?}", network_config);
 
-    Ok(())
+    Ok(network_config)
 }

+ 5 - 5
bin/darkfid/src/rpc.rs

@@ -38,12 +38,12 @@ use darkfi::{
 
 use crate::{
     error::{server_error, RpcError},
-    Darkfid,
+    DarkfiNode,
 };
 
 #[async_trait]
 #[rustfmt::skip]
-impl RequestHandler for Darkfid {
+impl RequestHandler for DarkfiNode {
     async fn handle_request(&self, req: JsonRequest) -> JsonResult {
         debug!(target: "darkfid::rpc", "--> {}", req.stringify().unwrap());
 
@@ -94,7 +94,7 @@ impl RequestHandler for Darkfid {
     }
 }
 
-impl Darkfid {
+impl DarkfiNode {
     // RPCAPI:
     // Returns current system clock as `u64` (String) timestamp.
     //
@@ -142,7 +142,7 @@ impl Darkfid {
             return JsonError::new(ErrorCode::InvalidParams, None, id).into()
         }
 
-        self.dnet_sub.clone().into()
+        self.subscribers.get("dnet").unwrap().clone().into()
     }
 
     // RPCAPI:
@@ -222,7 +222,7 @@ impl Darkfid {
     }
 }
 
-impl HandlerP2p for Darkfid {
+impl HandlerP2p for DarkfiNode {
     fn p2p(&self) -> P2pPtr {
         self.p2p_handler.p2p.clone()
     }

+ 2 - 2
bin/darkfid/src/rpc_blockchain.rs

@@ -32,9 +32,9 @@ use darkfi::{
     util::encoding::base64,
 };
 
-use crate::{server_error, Darkfid, RpcError};
+use crate::{server_error, DarkfiNode, RpcError};
 
-impl Darkfid {
+impl DarkfiNode {
     // RPCAPI:
     // Queries the blockchain database for a block in the given height.
     // Returns a readable block upon success.

+ 4 - 4
bin/darkfid/src/rpc_tx.rs

@@ -29,10 +29,10 @@ use darkfi::{
     util::encoding::base64,
 };
 
-use super::Darkfid;
+use super::DarkfiNode;
 use crate::{server_error, RpcError};
 
-impl Darkfid {
+impl DarkfiNode {
     // RPCAPI:
     // Simulate a network state transition with the given transaction.
     // Returns `true` if the transaction is valid, otherwise, a corresponding
@@ -122,13 +122,13 @@ impl Darkfid {
         // Block production participants can directly perform
         // the state transition check and append to their
         // pending transactions store.
-        let error_message = if self.miner {
+        let error_message = if self.rpc_client.is_some() {
             "Failed to append transaction to mempool"
         } else {
             "Failed to validate state transition"
         };
         // We'll perform the state transition check here.
-        if let Err(e) = self.validator.append_tx(&tx, self.miner).await {
+        if let Err(e) = self.validator.append_tx(&tx, self.rpc_client.is_some()).await {
             error!(target: "darkfid::rpc::tx_broadcast", "{}: {}", error_message, e);
             return server_error(RpcError::TxSimulationFail, id, None)
         };

+ 14 - 19
bin/darkfid/src/task/consensus.rs

@@ -16,12 +16,12 @@
  * along with this program.  If not, see <https://www.gnu.org/licenses/>.
  */
 
-use std::{str::FromStr, sync::Arc};
+use std::str::FromStr;
 
 use darkfi::{
     blockchain::HeaderHash,
     rpc::{jsonrpc::JsonNotification, util::JsonValue},
-    system::{sleep, StoppableTask, Subscription},
+    system::{sleep, ExecutorPtr, StoppableTask, Subscription},
     util::{encoding::base64, time::Timestamp},
     Error, Result,
 };
@@ -34,10 +34,11 @@ use log::{error, info};
 
 use crate::{
     task::{garbage_collect_task, miner::MinerRewardsRecipientConfig, miner_task, sync_task},
-    Darkfid,
+    DarkfiNodePtr,
 };
 
 /// Auxiliary structure representing node consensus init task configuration
+#[derive(Clone)]
 pub struct ConsensusInitTaskConfig {
     pub skip_sync: bool,
     pub checkpoint_height: Option<u32>,
@@ -51,9 +52,9 @@ pub struct ConsensusInitTaskConfig {
 
 /// Sync the node consensus state and start the corresponding task, based on node type.
 pub async fn consensus_init_task(
-    node: Arc<Darkfid>,
+    node: DarkfiNodePtr,
     config: ConsensusInitTaskConfig,
-    ex: Arc<smol::Executor<'static>>,
+    ex: ExecutorPtr,
 ) -> Result<()> {
     // Check if network is configured to start in the future.
     // NOTE: Always configure the network to start in the future when bootstrapping
@@ -100,15 +101,15 @@ pub async fn consensus_init_task(
             Err(_) => return Err(Error::InvalidAddress),
         };
 
-        let spend_hook = match config.spend_hook {
-            Some(s) => match FuncId::from_str(&s) {
+        let spend_hook = match &config.spend_hook {
+            Some(s) => match FuncId::from_str(s) {
                 Ok(s) => Some(s),
                 Err(_) => return Err(Error::ParseFailed("Invalid spend hook")),
             },
             None => None,
         };
 
-        let user_data = match config.user_data {
+        let user_data = match &config.user_data {
             Some(u) => {
                 let bytes: [u8; 32] = match bs58::decode(&u).into_vec()?.try_into() {
                     Ok(b) => b,
@@ -131,15 +132,9 @@ pub async fn consensus_init_task(
     // Gracefully handle network disconnections
     loop {
         let result = if config.miner {
-            miner_task(
-                node.clone(),
-                recipient_config.as_ref().unwrap(),
-                config.skip_sync,
-                ex.clone(),
-            )
-            .await
+            miner_task(&node, recipient_config.as_ref().unwrap(), config.skip_sync, &ex).await
         } else {
-            replicator_task(node.clone(), ex.clone()).await
+            replicator_task(&node, &ex).await
         };
 
         match result {
@@ -160,7 +155,7 @@ pub async fn consensus_init_task(
 }
 
 /// Async task to start the consensus task, while monitoring for a network disconnections.
-async fn replicator_task(node: Arc<Darkfid>, ex: Arc<smol::Executor<'static>>) -> Result<()> {
+async fn replicator_task(node: &DarkfiNodePtr, ex: &ExecutorPtr) -> Result<()> {
     // Grab proposals subscriber and subscribe to it
     let proposals_sub = node.subscribers.get("proposals").unwrap();
     let prop_subscription = proposals_sub.publisher.clone().subscribe().await;
@@ -188,9 +183,9 @@ async fn monitor_network(subscription: &Subscription<Error>) -> Result<()> {
 
 /// Async task used for listening for new blocks and perform consensus.
 async fn consensus_task(
-    node: Arc<Darkfid>,
+    node: &DarkfiNodePtr,
     subscription: &Subscription<JsonNotification>,
-    ex: Arc<smol::Executor<'static>>,
+    ex: &ExecutorPtr,
 ) -> Result<()> {
     info!(target: "darkfid::task::consensus_task", "Starting consensus task...");
 

+ 2 - 4
bin/darkfid/src/task/garbage_collect.rs

@@ -16,16 +16,14 @@
  * along with this program.  If not, see <https://www.gnu.org/licenses/>.
  */
 
-use std::sync::Arc;
-
 use darkfi::{error::TxVerifyFailed, validator::verification::verify_transactions, Error, Result};
 use darkfi_sdk::crypto::MerkleTree;
 use log::{debug, error, info};
 
-use crate::Darkfid;
+use crate::DarkfiNodePtr;
 
 /// Async task used for purging erroneous pending transactions from the nodes mempool.
-pub async fn garbage_collect_task(node: Arc<Darkfid>) -> Result<()> {
+pub async fn garbage_collect_task(node: DarkfiNodePtr) -> Result<()> {
     info!(target: "darkfid::task::garbage_collect_task", "Starting garbage collection task...");
 
     // Grab all current unproposed transactions.  We verify them in batches,

+ 10 - 11
bin/darkfid/src/task/miner.rs

@@ -16,12 +16,10 @@
  * along with this program.  If not, see <https://www.gnu.org/licenses/>.
  */
 
-use std::sync::Arc;
-
 use darkfi::{
     blockchain::{BlockInfo, Header},
     rpc::{jsonrpc::JsonNotification, util::JsonValue},
-    system::{StoppableTask, Subscription},
+    system::{ExecutorPtr, StoppableTask, Subscription},
     tx::{ContractCallLeaf, Transaction, TransactionBuilder},
     util::{encoding::base64, time::Timestamp},
     validator::{
@@ -46,7 +44,7 @@ use num_bigint::BigUint;
 use rand::rngs::OsRng;
 use smol::channel::{Receiver, Sender};
 
-use crate::{proto::ProposalMessage, task::garbage_collect_task, Darkfid};
+use crate::{proto::ProposalMessage, task::garbage_collect_task, DarkfiNodePtr};
 
 /// Auxiliary structure representing node miner rewards recipient configuration
 pub struct MinerRewardsRecipientConfig {
@@ -56,6 +54,7 @@ pub struct MinerRewardsRecipientConfig {
 }
 
 /// Async task used for participating in the PoW block production.
+///
 /// Miner initializes their setup and waits for next finalization,
 /// by listenning for new proposals from the network, for optimal
 /// conditions. After finalization occurs, they start the actual
@@ -66,10 +65,10 @@ pub struct MinerRewardsRecipientConfig {
 /// mining. These two tasks run in parallel, and after one of them
 /// finishes, node triggers finallization check.
 pub async fn miner_task(
-    node: Arc<Darkfid>,
+    node: &DarkfiNodePtr,
     recipient_config: &MinerRewardsRecipientConfig,
     skip_sync: bool,
-    ex: Arc<smol::Executor<'static>>,
+    ex: &ExecutorPtr,
 ) -> Result<()> {
     // Initialize miner configuration
     info!(target: "darkfid::task::miner_task", "Starting miner task...");
@@ -163,9 +162,9 @@ pub async fn miner_task(
 
         // Start listenning for network proposals and mining next block for best fork.
         match smol::future::or(
-            listen_to_network(&node, &extended_fork, &subscription, &sender),
+            listen_to_network(node, &extended_fork, &subscription, &sender),
             mine(
-                &node,
+                node,
                 &extended_fork,
                 &mut secret,
                 recipient_config,
@@ -234,7 +233,7 @@ pub async fn miner_task(
 
 /// Async task to listen for incoming proposals and check if the best fork has changed.
 async fn listen_to_network(
-    node: &Darkfid,
+    node: &DarkfiNodePtr,
     extended_fork: &Fork,
     subscription: &Subscription<JsonNotification>,
     sender: &Sender<()>,
@@ -273,7 +272,7 @@ async fn listen_to_network(
 /// while listening for a stop signal.
 #[allow(clippy::too_many_arguments)]
 async fn mine(
-    node: &Darkfid,
+    node: &DarkfiNodePtr,
     extended_fork: &Fork,
     secret: &mut SecretKey,
     recipient_config: &MinerRewardsRecipientConfig,
@@ -304,7 +303,7 @@ pub async fn wait_stop_signal(stop_signal: &Receiver<()>) -> Result<()> {
 
 /// Async task to generate and mine provided fork index next block.
 async fn mine_next_block(
-    node: &Darkfid,
+    node: &DarkfiNodePtr,
     extended_fork: &Fork,
     secret: &mut SecretKey,
     recipient_config: &MinerRewardsRecipientConfig,

+ 7 - 7
bin/darkfid/src/task/sync.rs

@@ -32,14 +32,14 @@ use crate::{
         ForkSyncRequest, ForkSyncResponse, HeaderSyncRequest, HeaderSyncResponse, SyncRequest,
         SyncResponse, TipRequest, TipResponse, BATCH,
     },
-    Darkfid,
+    DarkfiNodePtr,
 };
 
 // TODO: Parallelize independent requests.
 //       We can also make them be like torrents, where we retrieve chunks not in order.
 /// async task used for block syncing.
 /// A checkpoint can be provided to ensure node syncs the correct sequence.
-pub async fn sync_task(node: &Darkfid, checkpoint: Option<(u32, HeaderHash)>) -> Result<()> {
+pub async fn sync_task(node: &DarkfiNodePtr, checkpoint: Option<(u32, HeaderHash)>) -> Result<()> {
     info!(target: "darkfid::task::sync_task", "Starting blockchain sync...");
 
     // Grab blocks subscriber
@@ -133,7 +133,7 @@ pub async fn sync_task(node: &Darkfid, checkpoint: Option<(u32, HeaderHash)>) ->
 /// Auxiliary function to block until node is connected to at least one synced peer,
 /// and retrieve the synced peers tips.
 async fn synced_peers(
-    node: &Darkfid,
+    node: &DarkfiNodePtr,
     last_tip: &HeaderHash,
     checkpoint: Option<(u32, HeaderHash)>,
 ) -> HashMap<(u32, [u8; 32]), Vec<ChannelPtr>> {
@@ -223,7 +223,7 @@ async fn synced_peers(
 
 /// Auxiliary function to ask all peers for their current tip and find the most common one.
 async fn most_common_tip(
-    node: &Darkfid,
+    node: &DarkfiNodePtr,
     last_tip: &HeaderHash,
     checkpoint: Option<(u32, HeaderHash)>,
 ) -> (u32, Vec<ChannelPtr>) {
@@ -253,7 +253,7 @@ async fn most_common_tip(
 
 /// Auxiliary function to retrieve headers backwards until our last known one and verify them.
 async fn retrieve_headers(
-    node: &Darkfid,
+    node: &DarkfiNodePtr,
     peers: &[ChannelPtr],
     last_known: u32,
     tip_height: u32,
@@ -371,7 +371,7 @@ async fn retrieve_headers(
 
 /// Auxiliary function to retrieve blocks of provided headers and apply them to canonical.
 async fn retrieve_blocks(
-    node: &Darkfid,
+    node: &DarkfiNodePtr,
     peers: &[ChannelPtr],
     last_known: (u32, HeaderHash),
     block_sub: &JsonSubscriber,
@@ -484,7 +484,7 @@ async fn retrieve_blocks(
 }
 
 /// Auxiliary function to retrieve best fork state from a random peer.
-async fn sync_best_fork(node: &Darkfid, peers: &[ChannelPtr], last_tip: &HeaderHash) {
+async fn sync_best_fork(node: &DarkfiNodePtr, peers: &[ChannelPtr], last_tip: &HeaderHash) {
     info!(target: "darkfid::task::sync::sync_best_fork", "Syncing fork states from peers...");
     // Getting a random peer to ask for blocks
     let peer = &peers.choose(&mut OsRng).unwrap();

+ 12 - 22
bin/darkfid/src/tests/harness.rs

@@ -44,7 +44,7 @@ use url::Url;
 use crate::{
     proto::{DarkfidP2pHandler, ProposalMessage},
     task::sync::sync_task,
-    Darkfid,
+    DarkfiNode, DarkfiNodePtr,
 };
 
 pub struct HarnessConfig {
@@ -59,8 +59,8 @@ pub struct Harness {
     pub config: HarnessConfig,
     pub vks: Vec<(Vec<u8>, String, Vec<u8>)>,
     pub validator_config: ValidatorConfig,
-    pub alice: Darkfid,
-    pub bob: Darkfid,
+    pub alice: DarkfiNodePtr,
+    pub bob: DarkfiNodePtr,
 }
 
 impl Harness {
@@ -97,13 +97,13 @@ impl Harness {
         // Alice
         let alice_url = Url::parse(&config.alice_url)?;
         settings.inbound_addrs = vec![alice_url.clone()];
-        let alice = generate_node(&vks, &validator_config, &settings, ex, true, true, None).await?;
+        let alice = generate_node(&vks, &validator_config, &settings, ex, true, None).await?;
 
         // Bob
         let bob_url = Url::parse(&config.bob_url)?;
         settings.inbound_addrs = vec![bob_url];
         settings.peers = vec![alice_url];
-        let bob = generate_node(&vks, &validator_config, &settings, ex, true, false, None).await?;
+        let bob = generate_node(&vks, &validator_config, &settings, ex, false, None).await?;
 
         Ok(Self { config, vks, validator_config, alice, bob })
     }
@@ -223,40 +223,30 @@ impl Harness {
     }
 }
 
-// Note: This function should mirror darkfid::main
+// Note: This function should mirror `darkfid::Darkfid::init`
 pub async fn generate_node(
     vks: &Vec<(Vec<u8>, String, Vec<u8>)>,
     config: &ValidatorConfig,
     settings: &Settings,
     ex: &Arc<smol::Executor<'static>>,
-    miner: bool,
     skip_sync: bool,
     checkpoint: Option<(u32, HeaderHash)>,
-) -> Result<Darkfid> {
+) -> Result<DarkfiNodePtr> {
     let sled_db = sled::Config::new().temporary(true).open()?;
     vks::inject(&sled_db, vks)?;
 
-    let validator = Validator::new(&sled_db, config.clone()).await?;
+    let validator = Validator::new(&sled_db, config).await?;
 
     let mut subscribers = HashMap::new();
     subscribers.insert("blocks", JsonSubscriber::new("blockchain.subscribe_blocks"));
     subscribers.insert("txs", JsonSubscriber::new("blockchain.subscribe_txs"));
     subscribers.insert("proposals", JsonSubscriber::new("blockchain.subscribe_proposals"));
-
-    // We initialize a dnet subscriber but do not activate it.
-    let dnet_sub = JsonSubscriber::new("dnet.subscribe_events");
+    subscribers.insert("dnet", JsonSubscriber::new("dnet.subscribe_events"));
 
     let p2p_handler = DarkfidP2pHandler::init(settings, ex).await?;
-    let node = Darkfid::new(
-        p2p_handler.clone(),
-        validator.clone(),
-        miner,
-        50,
-        subscribers.clone(),
-        None,
-        dnet_sub,
-    )
-    .await;
+    let node =
+        DarkfiNode::new(p2p_handler.clone(), validator.clone(), 50, subscribers.clone(), None)
+            .await;
 
     p2p_handler.clone().start(ex, &validator, &subscribers).await?;
 

+ 91 - 1
bin/darkfid/src/tests/mod.rs

@@ -98,7 +98,6 @@ async fn sync_blocks_real(ex: Arc<Executor<'static>>) -> Result<()> {
         &settings,
         &ex,
         false,
-        false,
         Some((block2.header.height, block2.hash())),
     )
     .await?;
@@ -200,3 +199,94 @@ fn sync_blocks() -> Result<()> {
 
     Ok(())
 }
+
+#[test]
+/// Test the programmatic control of `Darkfid`.
+///
+/// First we initialize a daemon, start it and then perform
+/// couple of restarts to verify everything works as expected.
+fn darkfid_programmatic_control() -> Result<()> {
+    // Initialize logger
+    let mut cfg = simplelog::ConfigBuilder::new();
+
+    // We check this error so we can execute same file tests in parallel,
+    // otherwise second one fails to init logger here.
+    if simplelog::TermLogger::init(
+        simplelog::LevelFilter::Info,
+        //simplelog::LevelFilter::Debug,
+        //simplelog::LevelFilter::Trace,
+        cfg.build(),
+        simplelog::TerminalMode::Mixed,
+        simplelog::ColorChoice::Auto,
+    )
+    .is_err()
+    {
+        log::debug!(target: "darkfid_programmatic_control", "Logger initialized");
+    }
+
+    // Daemon configuration
+    let mut genesis_block = darkfi::blockchain::BlockInfo::default();
+    let producer_tx = genesis_block.txs.pop().unwrap();
+    genesis_block.append_txs(vec![producer_tx]);
+    let bootstrap = genesis_block.header.timestamp.inner();
+    let config = darkfi::validator::ValidatorConfig {
+        finalization_threshold: 1,
+        pow_target: 20,
+        pow_fixed_difficulty: Some(BigUint::one()),
+        genesis_block,
+        verify_fees: false,
+    };
+    let consensus_config = crate::ConsensusInitTaskConfig {
+        skip_sync: true,
+        checkpoint_height: None,
+        checkpoint: None,
+        miner: false,
+        recipient: None,
+        spend_hook: None,
+        user_data: None,
+        bootstrap,
+    };
+    let sled_db = sled_overlay::sled::Config::new().temporary(true).open()?;
+    let (_, vks) = darkfi_contract_test_harness::vks::get_cached_pks_and_vks()?;
+    darkfi_contract_test_harness::vks::inject(&sled_db, &vks)?;
+    let rpc_listen = Url::parse("tcp://127.0.0.1:8240")?;
+
+    // Create an executor and communication signals
+    let ex = Arc::new(smol::Executor::new());
+    let (signal, shutdown) = smol::channel::unbounded::<()>();
+
+    easy_parallel::Parallel::new().each(0..1, |_| smol::block_on(ex.run(shutdown.recv()))).finish(
+        || {
+            smol::block_on(async {
+                // Initialize a daemon
+                let daemon = crate::Darkfid::init(
+                    &sled_db,
+                    &config,
+                    &darkfi::net::Settings::default(),
+                    &None,
+                    &None,
+                    &ex,
+                )
+                .await
+                .unwrap();
+
+                // Start it
+                daemon.start(&ex, &rpc_listen, &consensus_config).await.unwrap();
+
+                // Stop it
+                daemon.stop().await.unwrap();
+
+                // Start it again
+                daemon.start(&ex, &rpc_listen, &consensus_config).await.unwrap();
+
+                // Stop it
+                daemon.stop().await.unwrap();
+
+                // Shutdown entirely
+                drop(signal);
+            })
+        },
+    );
+
+    Ok(())
+}

+ 1 - 2
bin/darkfid/src/tests/sync_forks.rs

@@ -69,8 +69,7 @@ async fn sync_forks_real(ex: Arc<Executor<'static>>) -> Result<()> {
     settings.inbound_addrs = vec![charlie_url];
     let bob_url = th.bob.p2p_handler.p2p.settings().read().await.inbound_addrs[0].clone();
     settings.peers = vec![bob_url];
-    let charlie =
-        generate_node(&th.vks, &th.validator_config, &settings, &ex, false, false, None).await?;
+    let charlie = generate_node(&th.vks, &th.validator_config, &settings, &ex, false, None).await?;
 
     // Verify node synced the best fork
     let forks = th.alice.validator.consensus.forks.read().await;

+ 0 - 70
bin/darkfid/src/utils.rs

@@ -1,70 +0,0 @@
-/* This file is part of DarkFi (https://dark.fi)
- *
- * Copyright (C) 2020-2024 Dyne.org foundation
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program.  If not, see <https://www.gnu.org/licenses/>.
- */
-
-use log::{debug, error};
-use smol::fs::read_to_string;
-use structopt_toml::StructOptToml;
-
-use darkfi::{util::path::get_config_path, Error, Result};
-
-use crate::{BlockchainNetwork, CONFIG_FILE};
-
-/// Auxiliary function to parse darkfid configuration file and extract requested
-/// blockchain network config.
-pub async fn parse_blockchain_config(
-    config: Option<String>,
-    network: &str,
-) -> Result<BlockchainNetwork> {
-    // Grab config path
-    let config_path = get_config_path(config, CONFIG_FILE)?;
-    debug!(target: "darkfid", "Parsing configuration file: {:?}", config_path);
-
-    // Parse TOML file contents
-    let contents = read_to_string(&config_path).await?;
-    let contents: toml::Value = match toml::from_str(&contents) {
-        Ok(v) => v,
-        Err(e) => {
-            error!(target: "darkfid", "Failed parsing TOML config: {}", e);
-            return Err(Error::ParseFailed("Failed parsing TOML config"))
-        }
-    };
-
-    // Grab requested network config
-    let Some(table) = contents.as_table() else { return Err(Error::ParseFailed("TOML not a map")) };
-    let Some(network_configs) = table.get("network_config") else {
-        return Err(Error::ParseFailed("TOML does not contain network configurations"))
-    };
-    let Some(network_configs) = network_configs.as_table() else {
-        return Err(Error::ParseFailed("`network_config` not a map"))
-    };
-    let Some(network_config) = network_configs.get(network) else {
-        return Err(Error::ParseFailed("TOML does not contain requested network configuration"))
-    };
-    let network_config = toml::to_string(&network_config).unwrap();
-    let network_config =
-        match BlockchainNetwork::from_iter_with_toml::<Vec<String>>(&network_config, vec![]) {
-            Ok(v) => v,
-            Err(e) => {
-                error!(target: "darkfid", "Failed parsing requested network configuration: {}", e);
-                return Err(Error::ParseFailed("Failed parsing requested network configuration"))
-            }
-        };
-    debug!(target: "darkfid", "Parsed network configuration: {:?}", network_config);
-
-    Ok(network_config)
-}

+ 8 - 5
bin/minerd/src/lib.rs

@@ -102,7 +102,7 @@ impl Minerd {
             |res| async move {
                 match res {
                     Ok(()) | Err(Error::RpcServerStopped) => node_.stop_connections().await,
-                    Err(e) => error!(target: "minerd::Minerd::start", "Failed stopping JSON-RPC server: {}", e),
+                    Err(e) => error!(target: "minerd::Minerd::start", "Failed starting JSON-RPC server: {}", e),
                 }
             },
             Error::RpcServerStopped,
@@ -189,10 +189,13 @@ fn minerd_programmatic_control() -> Result<()> {
                 daemon.start(&ex, &rpc_listen);
 
                 // Generate a JSON-RPC client to send mining jobs
-                let rpc_client =
-                    darkfi::rpc::client::RpcClient::new(rpc_listen.clone(), ex.clone())
-                        .await
-                        .unwrap();
+                let mut rpc_client =
+                    darkfi::rpc::client::RpcClient::new(rpc_listen.clone(), ex.clone()).await;
+                while rpc_client.is_err() {
+                    rpc_client =
+                        darkfi::rpc::client::RpcClient::new(rpc_listen.clone(), ex.clone()).await;
+                }
+                let rpc_client = rpc_client.unwrap();
 
                 // Send a mining job but stop the daemon after it starts mining
                 smol::future::or(

+ 0 - 3
contrib/localnet/darkfid-five-nodes/darkfid1.toml

@@ -26,9 +26,6 @@ minerd_endpoint = "tcp://127.0.0.1:48567"
 # PoW block production target, in seconds
 pow_target = 60
 
-# Participate in block production
-miner = true
-
 # Wallet address to receive mining rewards.
 # This is a dummy one so the miner can start,
 # replace with your own one.

+ 0 - 3
contrib/localnet/darkfid-five-nodes/darkfid2.toml

@@ -26,9 +26,6 @@ minerd_endpoint = "tcp://127.0.0.1:48667"
 # PoW block production target, in seconds
 pow_target = 60
 
-# Participate in block production
-miner = true
-
 # Wallet address to receive mining rewards.
 # This is a dummy one so the miner can start,
 # replace with your own one.

+ 0 - 3
contrib/localnet/darkfid-five-nodes/darkfid3.toml

@@ -26,9 +26,6 @@ minerd_endpoint = "tcp://127.0.0.1:48767"
 # PoW block production target, in seconds
 pow_target = 60
 
-# Participate in block production
-miner = true
-
 # Wallet address to receive mining rewards.
 # This is a dummy one so the miner can start,
 # replace with your own one.

+ 0 - 3
contrib/localnet/darkfid-five-nodes/darkfid4.toml

@@ -26,9 +26,6 @@ minerd_endpoint = "tcp://127.0.0.1:48867"
 # PoW block production target, in seconds
 pow_target = 60
 
-# Participate in block production
-miner = true
-
 # Wallet address to receive mining rewards.
 # This is a dummy one so the miner can start,
 # replace with your own one.

+ 0 - 3
contrib/localnet/darkfid-single-node/darkfid.toml

@@ -29,9 +29,6 @@ pow_target = 10
 # Optional fixed PoW difficulty, used for testing
 pow_fixed_difficulty = 1
 
-# Participate in block production
-miner = true
-
 # Wallet address to receive mining rewards.
 # This is a dummy one so the miner can start,
 # replace with your own one.

+ 0 - 3
contrib/localnet/darkfid-small/darkfid0.toml

@@ -26,9 +26,6 @@ minerd_endpoint = "tcp://127.0.0.1:48467"
 # PoW block production target, in seconds
 pow_target = 20
 
-# Participate in block production
-miner = true
-
 # Wallet address to receive mining rewards.
 # This is a dummy one so the miner can start,
 # replace with your own one.

+ 0 - 3
contrib/localnet/darkfid-small/darkfid1.toml

@@ -34,9 +34,6 @@ minerd_endpoint = "tcp://127.0.0.1:48567"
 # PoW block production target, in seconds
 pow_target = 20
 
-# Participate in block production
-miner = true
-
 # Wallet address to receive mining rewards.
 # This is a dummy one so the miner can start,
 # replace with your own one.

+ 0 - 3
contrib/localnet/darkfid-small/darkfid2.toml

@@ -26,9 +26,6 @@ minerd_endpoint = "tcp://127.0.0.1:28467"
 # PoW block production target, in seconds
 pow_target = 20
 
-# Participate in block production
-miner = false
-
 # Skip syncing process and start node right away
 skip_sync = false
 

+ 1 - 1
src/contract/test-harness/src/lib.rs

@@ -170,7 +170,7 @@ impl Wallet {
             genesis_block,
             verify_fees,
         };
-        let validator = Validator::new(&sled_db, validator_config).await?;
+        let validator = Validator::new(&sled_db, &validator_config).await?;
 
         // The Merkle tree for the `Money` contract is initialized with a "null"
         // leaf at position 0.

+ 2 - 2
src/validator/mod.rs

@@ -88,7 +88,7 @@ pub struct Validator {
 }
 
 impl Validator {
-    pub async fn new(db: &sled::Db, config: ValidatorConfig) -> Result<ValidatorPtr> {
+    pub async fn new(db: &sled::Db, config: &ValidatorConfig) -> Result<ValidatorPtr> {
         info!(target: "validator::new", "Initializing Validator");
 
         info!(target: "validator::new", "Initializing Blockchain");
@@ -114,7 +114,7 @@ impl Validator {
             blockchain.clone(),
             config.finalization_threshold,
             config.pow_target,
-            config.pow_fixed_difficulty,
+            config.pow_fixed_difficulty.clone(),
         )?;
 
         // Create the actual state