Przeglądaj źródła

[WIP] darkfid: introduced a registry to handle all miners related stuff

skoupidi 7 miesięcy temu
rodzic
commit
760d230d61

+ 32 - 147
bin/darkfid/src/lib.rs

@@ -25,7 +25,6 @@ use smol::lock::Mutex;
 use tracing::{debug, error, info};
 
 use darkfi::{
-    blockchain::BlockInfo,
     net::settings::Settings,
     rpc::{
         jsonrpc::JsonSubscriber,
@@ -34,15 +33,9 @@ use darkfi::{
     },
     system::{ExecutorPtr, StoppableTask, StoppableTaskPtr},
     validator::{Validator, ValidatorConfig, ValidatorPtr},
-    zk::{empty_witnesses, ProvingKey, ZkCircuit},
-    zkas::ZkBinary,
     Error, Result,
 };
-use darkfi_money_contract::MONEY_CONTRACT_ZKAS_MINT_NS_V1;
-use darkfi_sdk::crypto::{
-    keypair::{Network, SecretKey},
-    MONEY_CONTRACT_ID,
-};
+use darkfi_sdk::crypto::keypair::Network;
 
 #[cfg(test)]
 mod tests;
@@ -55,9 +48,8 @@ mod rpc;
 use rpc::{DefaultRpcHandler, MmRpcHandler, StratumRpcHandler};
 mod rpc_blockchain;
 mod rpc_miner;
-mod rpc_tx;
-use rpc_miner::BlockTemplate;
 mod rpc_stratum;
+mod rpc_tx;
 mod rpc_xmr;
 
 /// Validator async tasks
@@ -68,109 +60,55 @@ use task::{consensus::ConsensusInitTaskConfig, consensus_init_task};
 mod proto;
 use proto::{DarkfidP2pHandler, DarkfidP2pHandlerPtr};
 
+/// Miners registry
+mod registry;
+use registry::{
+    model::{BlockTemplate, MiningJobs},
+    DarkfiMinersRegistry, DarkfiMinersRegistryPtr,
+};
+
 /// Atomic pointer to the DarkFi node
 pub type DarkfiNodePtr = Arc<DarkfiNode>;
 
-/// Storage for active mining jobs. These are stored per connection ID.
-/// A new map will be made for each stratum login.
-#[derive(Debug, Default)]
-pub struct MiningJobs(HashMap<[u8; 32], BlockTemplate>);
-
-impl MiningJobs {
-    pub fn insert(&mut self, job_id: [u8; 32], blocktemplate: BlockTemplate) {
-        self.0.insert(job_id, blocktemplate);
-    }
-
-    pub fn get(&self, job_id: &[u8; 32]) -> Option<&BlockTemplate> {
-        self.0.get(job_id)
-    }
-
-    pub fn get_mut(&mut self, job_id: &[u8; 32]) -> Option<&mut BlockTemplate> {
-        self.0.get_mut(job_id)
-    }
-}
-
 /// Structure representing a DarkFi node
 pub struct DarkfiNode {
     /// Blockchain network
     network: Network,
-    /// P2P network protocols handler.
-    p2p_handler: DarkfidP2pHandlerPtr,
     /// Validator(node) pointer
     validator: ValidatorPtr,
+    /// P2P network protocols handler
+    p2p_handler: DarkfidP2pHandlerPtr,
+    /// Node miners registry pointer
+    registry: DarkfiMinersRegistryPtr,
     /// 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>,
-    /// Native mining block templates
-    blocktemplates: Mutex<HashMap<Vec<u8>, BlockTemplate>>,
-    /// Active mining jobs per connection ID
-    mining_jobs: Mutex<HashMap<[u8; 32], MiningJobs>>,
-    /// Merge mining block templates
-    mm_blocktemplates: Mutex<HashMap<Vec<u8>, (BlockInfo, f64, SecretKey)>>,
     /// Main JSON-RPC connection tracker
     rpc_connections: Mutex<HashSet<StoppableTaskPtr>>,
-    /// Stratum JSON-RPC connection tracker
-    stratum_rpc_connections: Mutex<HashSet<StoppableTaskPtr>>,
-    /// HTTP JSON-RPC connection tracker
-    mm_rpc_connections: Mutex<HashSet<StoppableTaskPtr>>,
-    /// PowRewardV1 ZK data
-    powrewardv1_zk: PowRewardV1Zk,
 }
 
 impl DarkfiNode {
     pub async fn new(
         network: Network,
-        p2p_handler: DarkfidP2pHandlerPtr,
         validator: ValidatorPtr,
+        p2p_handler: DarkfidP2pHandlerPtr,
+        registry: DarkfiMinersRegistryPtr,
         txs_batch_size: usize,
         subscribers: HashMap<&'static str, JsonSubscriber>,
     ) -> Result<DarkfiNodePtr> {
-        let powrewardv1_zk = PowRewardV1Zk::new(validator.clone())?;
-
         Ok(Arc::new(Self {
             network,
-            p2p_handler,
             validator,
+            p2p_handler,
+            registry,
             txs_batch_size,
             subscribers,
-            mining_jobs: Mutex::new(HashMap::new()),
-            blocktemplates: Mutex::new(HashMap::new()),
-            mm_blocktemplates: Mutex::new(HashMap::new()),
             rpc_connections: Mutex::new(HashSet::new()),
-            stratum_rpc_connections: Mutex::new(HashSet::new()),
-            mm_rpc_connections: Mutex::new(HashSet::new()),
-            powrewardv1_zk,
         }))
     }
 }
 
-/// ZK data used to generate the "coinbase" transaction in a block
-pub(crate) struct PowRewardV1Zk {
-    pub zkbin: ZkBinary,
-    pub provingkey: ProvingKey,
-}
-
-impl PowRewardV1Zk {
-    pub fn new(validator: ValidatorPtr) -> Result<Self> {
-        info!(
-            target: "darkfid::PowRewardV1Zk::new",
-            "Generating PowRewardV1 ZkCircuit and ProvingKey...",
-        );
-
-        let (zkbin, _) = validator.blockchain.contracts.get_zkas(
-            &validator.blockchain.sled_db,
-            &MONEY_CONTRACT_ID,
-            MONEY_CONTRACT_ZKAS_MINT_NS_V1,
-        )?;
-
-        let circuit = ZkCircuit::new(empty_witnesses(&zkbin)?, &zkbin);
-        let provingkey = ProvingKey::build(zkbin.k, &circuit);
-
-        Ok(Self { zkbin, provingkey })
-    }
-}
-
 /// Atomic pointer to the DarkFi daemon
 pub type DarkfidPtr = Arc<Darkfid>;
 
@@ -182,10 +120,6 @@ pub struct Darkfid {
     dnet_task: StoppableTaskPtr,
     /// Main JSON-RPC background task
     rpc_task: StoppableTaskPtr,
-    /// Stratum JSON-RPC background task
-    stratum_rpc_task: StoppableTaskPtr,
-    /// HTTP JSON-RPC background task
-    mm_rpc_task: StoppableTaskPtr,
     /// Consensus protocol background task
     consensus_task: StoppableTaskPtr,
 }
@@ -210,6 +144,9 @@ impl Darkfid {
         // Initialize P2P network
         let p2p_handler = DarkfidP2pHandler::init(net_settings, ex).await?;
 
+        // Initialize the miners registry
+        let registry = DarkfiMinersRegistry::init(&validator)?;
+
         // Grab blockchain network configured transactions batch size for garbage collection
         let txs_batch_size = match txs_batch_size {
             Some(b) => {
@@ -231,25 +168,17 @@ impl Darkfid {
 
         // Initialize node
         let node =
-            DarkfiNode::new(network, p2p_handler, validator, txs_batch_size, subscribers).await?;
+            DarkfiNode::new(network, validator, p2p_handler, registry, txs_batch_size, subscribers)
+                .await?;
 
         // Generate the background tasks
         let dnet_task = StoppableTask::new();
         let rpc_task = StoppableTask::new();
-        let stratum_rpc_task = StoppableTask::new();
-        let mm_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,
-            stratum_rpc_task,
-            mm_rpc_task,
-            consensus_task,
-        }))
+        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
@@ -258,7 +187,7 @@ impl Darkfid {
         &self,
         executor: &ExecutorPtr,
         rpc_settings: &RpcSettings,
-        stratum_rpc_settings: &RpcSettings,
+        stratum_rpc_settings: &Option<RpcSettings>,
         mm_rpc_settings: &Option<RpcSettings>,
         config: &ConsensusInitTaskConfig,
     ) -> Result<()> {
@@ -302,53 +231,13 @@ impl Darkfid {
             executor.clone(),
         );
 
-        // Start the stratum server JSON-RPC task
-        info!(target: "darkfid::Darkfid::start", "Starting Stratum JSON-RPC server");
-        let node_ = self.node.clone();
-        self.stratum_rpc_task.clone().start(
-            listen_and_serve::<StratumRpcHandler>(stratum_rpc_settings.clone(), self.node.clone(), None, executor.clone()),
-            |res| async move {
-                match res {
-                    Ok(()) | Err(Error::RpcServerStopped) => <DarkfiNode as RequestHandler<StratumRpcHandler>>::stop_connections(&node_).await,
-                    Err(e) => error!(target: "darkfid::Darkfid::start", "Failed starting Stratum JSON-RPC server: {e}"),
-                }
-            },
-            Error::RpcServerStopped,
-            executor.clone(),
-        );
-
-        // Start the merge mining JSON-RPC task
-        if let Some(mm_rpc) = mm_rpc_settings {
-            info!(target: "darkfid::Darkfid::start", "Starting merge mining JSON-RPC server");
-            let node_ = self.node.clone();
-            self.mm_rpc_task.clone().start(
-                listen_and_serve::<MmRpcHandler>(mm_rpc.clone(), self.node.clone(), None, executor.clone()),
-                |res| async move {
-                    match res {
-                        Ok(()) | Err(Error::RpcServerStopped) => <DarkfiNode as RequestHandler<MmRpcHandler>>::stop_connections(&node_).await,
-                        Err(e) => error!(target: "darkfid::Darkfid::start", "Failed starting merge mining JSON-RPC server: {e}"),
-                    }
-                },
-                Error::RpcServerStopped,
-                executor.clone(),
-            );
-        } else {
-            // Create a dummy task
-            self.mm_rpc_task.clone().start(
-                async { Ok(()) },
-                |_| async { /* Do nothing */ },
-                Error::RpcServerStopped,
-                executor.clone(),
-            );
-        }
+        // Start the miners registry
+        info!(target: "darkfid::Darkfid::start", "Starting miners registry");
+        self.node.registry.start(executor, &self.node, stratum_rpc_settings, mm_rpc_settings)?;
 
         // 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?;
+        self.node.p2p_handler.start(executor, &self.node.validator, &self.node.subscribers).await?;
 
         // Start the consensus protocol
         info!(target: "darkfid::Darkfid::start", "Starting consensus protocol task");
@@ -380,17 +269,13 @@ impl Darkfid {
         info!(target: "darkfid::Darkfid::stop", "Stopping dnet subs task...");
         self.dnet_task.stop().await;
 
-        // Stop the JSON-RPC task
+        // Stop the main JSON-RPC task
         info!(target: "darkfid::Darkfid::stop", "Stopping main JSON-RPC server...");
         self.rpc_task.stop().await;
 
-        // Stop the Stratum JSON-RPC task
-        info!(target: "darkfid::Darkfid::stop", "Stopping Stratum JSON-RPC server...");
-        self.stratum_rpc_task.stop().await;
-
-        // Stop the merge mining JSON-RPC task
-        info!(target: "darkfid::Darkfid::stop", "Stopping merge mining JSON-RPC server...");
-        self.mm_rpc_task.stop().await;
+        // Stop the miners registry
+        info!(target: "darkfid::Darkfid::stop", "Stopping miners registry...");
+        self.node.registry.stop().await;
 
         // Stop the P2P network
         info!(target: "darkfid::Darkfid::stop", "Stopping P2P network protocols handler...");

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

@@ -135,9 +135,9 @@ pub struct BlockchainNetwork {
     /// Main server JSON-RPC settings
     rpc: RpcSettingsOpt,
 
-    #[structopt(flatten)]
-    /// Stratum server JSON-RPC settings
-    stratum_rpc: RpcSettingsOpt,
+    #[structopt(skip)]
+    /// Stratum server JSON-RPC settings (optional)
+    stratum_rpc: Option<RpcSettingsOpt>,
 
     #[structopt(skip)]
     /// Merge mining server JSON-RPC settings (optional)
@@ -248,7 +248,7 @@ async fn realmain(args: Args, ex: Arc<smol::Executor<'static>>) -> Result<()> {
         .start(
             &ex,
             &blockchain_config.rpc.into(),
-            &blockchain_config.stratum_rpc.into(),
+            &blockchain_config.stratum_rpc.map(|stratum_rpc_opts| stratum_rpc_opts.into()),
             &blockchain_config.mm_rpc.map(|mm_rpc_opts| mm_rpc_opts.into()),
             &config,
         )

+ 190 - 0
bin/darkfid/src/registry/mod.rs

@@ -0,0 +1,190 @@
+/* This file is part of DarkFi (https://dark.fi)
+ *
+ * Copyright (C) 2020-2025 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 smol::lock::Mutex;
+use tracing::{error, info};
+
+use darkfi::{
+    rpc::{
+        server::{listen_and_serve, RequestHandler},
+        settings::RpcSettings,
+    },
+    system::{ExecutorPtr, StoppableTask, StoppableTaskPtr},
+    validator::ValidatorPtr,
+    Error, Result,
+};
+
+use crate::{DarkfiNode, DarkfiNodePtr, MmRpcHandler, StratumRpcHandler};
+
+/// Block related structures
+pub mod model;
+use model::{BlockTemplate, MiningJobs, MmBlockTemplate, PowRewardV1Zk};
+
+/// Atomic pointer to the DarkFi node miners registry.
+pub type DarkfiMinersRegistryPtr = Arc<DarkfiMinersRegistry>;
+
+/// DarkFi node miners registry.
+pub struct DarkfiMinersRegistry {
+    /// PowRewardV1 ZK data
+    pub powrewardv1_zk: PowRewardV1Zk,
+    /// Native mining block templates
+    pub blocktemplates: Mutex<HashMap<Vec<u8>, BlockTemplate>>,
+    /// Active native mining jobs per connection ID
+    pub mining_jobs: Mutex<HashMap<[u8; 32], MiningJobs>>,
+    /// Merge mining block templates
+    pub mm_blocktemplates: Mutex<HashMap<Vec<u8>, MmBlockTemplate>>,
+    /// Stratum JSON-RPC background task
+    stratum_rpc_task: StoppableTaskPtr,
+    /// Stratum JSON-RPC connection tracker
+    pub stratum_rpc_connections: Mutex<HashSet<StoppableTaskPtr>>,
+    /// HTTP JSON-RPC background task
+    mm_rpc_task: StoppableTaskPtr,
+    /// HTTP JSON-RPC connection tracker
+    pub mm_rpc_connections: Mutex<HashSet<StoppableTaskPtr>>,
+}
+
+impl DarkfiMinersRegistry {
+    /// Initialize a DarkFi node miners registry.
+    pub fn init(validator: &ValidatorPtr) -> Result<DarkfiMinersRegistryPtr> {
+        info!(
+            target: "darkfid::registry::mod::DarkfiMinersRegistry::init",
+            "Initializing a new DarkFi node miners registry..."
+        );
+
+        // Generate the PowRewardV1 ZK data
+        let powrewardv1_zk = PowRewardV1Zk::new(validator)?;
+
+        // Generate the stratum JSON-RPC background task and its
+        // connections tracker.
+        let stratum_rpc_task = StoppableTask::new();
+        let stratum_rpc_connections = Mutex::new(HashSet::new());
+
+        // Generate the HTTP JSON-RPC background task and its
+        // connections tracker.
+        let mm_rpc_task = StoppableTask::new();
+        let mm_rpc_connections = Mutex::new(HashSet::new());
+
+        info!(
+            target: "darkfid::registry::mod::DarkfiMinersRegistry::init",
+            "DarkFi node miners registry generated successfully!"
+        );
+
+        Ok(Arc::new(Self {
+            powrewardv1_zk,
+            blocktemplates: Mutex::new(HashMap::new()),
+            mining_jobs: Mutex::new(HashMap::new()),
+            mm_blocktemplates: Mutex::new(HashMap::new()),
+            stratum_rpc_task,
+            stratum_rpc_connections,
+            mm_rpc_task,
+            mm_rpc_connections,
+        }))
+    }
+
+    /// Start the DarkFi node miners registry for provided DarkFi node
+    /// instance.
+    pub fn start(
+        &self,
+        executor: &ExecutorPtr,
+        node: &DarkfiNodePtr,
+        stratum_rpc_settings: &Option<RpcSettings>,
+        mm_rpc_settings: &Option<RpcSettings>,
+    ) -> Result<()> {
+        info!(
+            target: "darkfid::registry::mod::DarkfiMinersRegistry::start",
+            "Starting the DarkFi node miners registry..."
+        );
+
+        // Start the stratum server JSON-RPC task
+        if let Some(stratum_rpc) = stratum_rpc_settings {
+            info!(target: "darkfid::registry::mod::DarkfiMinersRegistry::start", "Starting Stratum JSON-RPC server");
+            let node_ = node.clone();
+            self.stratum_rpc_task.clone().start(
+                listen_and_serve::<StratumRpcHandler>(stratum_rpc.clone(), node.clone(), None, executor.clone()),
+                |res| async move {
+                    match res {
+                        Ok(()) | Err(Error::RpcServerStopped) => <DarkfiNode as RequestHandler<StratumRpcHandler>>::stop_connections(&node_).await,
+                        Err(e) => error!(target: "darkfid::registry::mod::DarkfiMinersRegistry::start", "Failed starting Stratum JSON-RPC server: {e}"),
+                    }
+                },
+                Error::RpcServerStopped,
+                executor.clone(),
+            );
+        } else {
+            // Create a dummy task
+            self.stratum_rpc_task.clone().start(
+                async { Ok(()) },
+                |_| async { /* Do nothing */ },
+                Error::RpcServerStopped,
+                executor.clone(),
+            );
+        }
+
+        // Start the merge mining JSON-RPC task
+        if let Some(mm_rpc) = mm_rpc_settings {
+            info!(target: "darkfid::registry::mod::DarkfiMinersRegistry::start", "Starting merge mining JSON-RPC server");
+            let node_ = node.clone();
+            self.mm_rpc_task.clone().start(
+                listen_and_serve::<MmRpcHandler>(mm_rpc.clone(), node.clone(), None, executor.clone()),
+                |res| async move {
+                    match res {
+                        Ok(()) | Err(Error::RpcServerStopped) => <DarkfiNode as RequestHandler<MmRpcHandler>>::stop_connections(&node_).await,
+                        Err(e) => error!(target: "darkfid::registry::mod::DarkfiMinersRegistry::start", "Failed starting merge mining JSON-RPC server: {e}"),
+                    }
+                },
+                Error::RpcServerStopped,
+                executor.clone(),
+            );
+        } else {
+            // Create a dummy task
+            self.mm_rpc_task.clone().start(
+                async { Ok(()) },
+                |_| async { /* Do nothing */ },
+                Error::RpcServerStopped,
+                executor.clone(),
+            );
+        }
+
+        info!(
+            target: "darkfid::registry::mod::DarkfiMinersRegistry::start",
+            "DarkFi node miners registry started successfully!"
+        );
+
+        Ok(())
+    }
+
+    /// Stop the DarkFi node miners registry.
+    pub async fn stop(&self) {
+        info!(target: "darkfid::registry::mod::DarkfiMinersRegistry::stop", "Terminating DarkFi node miners registry...");
+
+        // Stop the Stratum JSON-RPC task
+        info!(target: "darkfid::registry::mod::DarkfiMinersRegistry::stop", "Stopping Stratum JSON-RPC server...");
+        self.stratum_rpc_task.stop().await;
+
+        // Stop the merge mining JSON-RPC task
+        info!(target: "darkfid::registry::mod::DarkfiMinersRegistry::stop", "Stopping merge mining JSON-RPC server...");
+        self.mm_rpc_task.stop().await;
+
+        info!(target: "darkfid::registry::mod::DarkfiMinersRegistry::stop", "DarkFi node miners registry terminated successfully!");
+    }
+}

+ 239 - 0
bin/darkfid/src/registry/model.rs

@@ -0,0 +1,239 @@
+/* This file is part of DarkFi (https://dark.fi)
+ *
+ * Copyright (C) 2020-2025 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;
+
+use num_bigint::BigUint;
+use rand::rngs::OsRng;
+use tracing::info;
+
+use darkfi::{
+    blockchain::{BlockInfo, Header},
+    tx::{ContractCallLeaf, Transaction, TransactionBuilder},
+    util::time::Timestamp,
+    validator::{consensus::Fork, verification::apply_producer_transaction, ValidatorPtr},
+    zk::{empty_witnesses, ProvingKey, ZkCircuit},
+    zkas::ZkBinary,
+    Error, Result,
+};
+use darkfi_money_contract::{
+    client::pow_reward_v1::PoWRewardCallBuilder, MoneyFunction, MONEY_CONTRACT_ZKAS_MINT_NS_V1,
+};
+use darkfi_sdk::{
+    crypto::{
+        keypair::{Address, Keypair, SecretKey},
+        FuncId, MerkleTree, MONEY_CONTRACT_ID,
+    },
+    pasta::pallas,
+    ContractCall,
+};
+use darkfi_serial::Encodable;
+
+/// Auxiliary structure representing node miner rewards recipient configuration.
+pub struct MinerRewardsRecipientConfig {
+    /// Wallet mining address to receive mining rewards
+    pub recipient: Address,
+    /// Optional contract spend hook to use in the mining reward
+    pub spend_hook: Option<FuncId>,
+    /// Optional contract user data to use in the mining reward.
+    /// This is not arbitrary data.
+    pub user_data: Option<pallas::Base>,
+}
+
+/// Auxiliary structure representing a block template for native
+/// mining.
+#[derive(Debug, Clone)]
+pub struct BlockTemplate {
+    /// Block that is being mined
+    pub block: BlockInfo,
+    /// RandomX init key
+    pub randomx_key: [u8; 32],
+    /// Block mining target
+    pub target: BigUint,
+    /// Ephemeral signing secret for this blocktemplate
+    pub secret: SecretKey,
+}
+
+/// Auxiliary structure representing a block template for merge mining.
+#[derive(Debug, Clone)]
+pub struct MmBlockTemplate {
+    /// Block that is being mined
+    pub block: BlockInfo,
+    /// Block difficulty
+    pub difficulty: f64,
+    /// Ephemeral signing secret for this blocktemplate
+    pub secret: SecretKey,
+}
+
+/// Storage for active mining jobs. These are stored per connection ID.
+/// A new map will be made for each stratum login.
+#[derive(Debug, Default)]
+pub struct MiningJobs(HashMap<[u8; 32], BlockTemplate>);
+
+impl MiningJobs {
+    pub fn insert(&mut self, job_id: [u8; 32], blocktemplate: BlockTemplate) {
+        self.0.insert(job_id, blocktemplate);
+    }
+
+    pub fn get(&self, job_id: &[u8; 32]) -> Option<&BlockTemplate> {
+        self.0.get(job_id)
+    }
+
+    pub fn get_mut(&mut self, job_id: &[u8; 32]) -> Option<&mut BlockTemplate> {
+        self.0.get_mut(job_id)
+    }
+}
+
+/// ZK data used to generate the "coinbase" transaction in a block
+pub struct PowRewardV1Zk {
+    pub zkbin: ZkBinary,
+    pub provingkey: ProvingKey,
+}
+
+impl PowRewardV1Zk {
+    pub fn new(validator: &ValidatorPtr) -> Result<Self> {
+        info!(
+            target: "darkfid::registry::model::PowRewardV1Zk::new",
+            "Generating PowRewardV1 ZkCircuit and ProvingKey...",
+        );
+
+        let (zkbin, _) = validator.blockchain.contracts.get_zkas(
+            &validator.blockchain.sled_db,
+            &MONEY_CONTRACT_ID,
+            MONEY_CONTRACT_ZKAS_MINT_NS_V1,
+        )?;
+
+        let circuit = ZkCircuit::new(empty_witnesses(&zkbin)?, &zkbin);
+        let provingkey = ProvingKey::build(zkbin.k, &circuit);
+
+        Ok(Self { zkbin, provingkey })
+    }
+}
+
+/// Auxiliary function to generate next block in an atomic manner.
+pub async fn generate_next_block(
+    extended_fork: &mut Fork,
+    recipient_config: &MinerRewardsRecipientConfig,
+    zkbin: &ZkBinary,
+    pk: &ProvingKey,
+    block_target: u32,
+    verify_fees: bool,
+) -> Result<(BigUint, BlockInfo, SecretKey)> {
+    // Grab forks' last block proposal(previous)
+    let last_proposal = extended_fork.last_proposal()?;
+
+    // Grab forks' next block height
+    let next_block_height = last_proposal.block.header.height + 1;
+
+    // Grab forks' unproposed transactions
+    let (mut txs, _, fees, overlay) = extended_fork
+        .unproposed_txs(&extended_fork.blockchain, next_block_height, block_target, verify_fees)
+        .await?;
+
+    // Create an ephemeral block signing keypair. Its secret key will
+    // be stored in the PowReward transaction's encrypted note for
+    // later retrieval. It is encrypted towards the recipient's public
+    // key.
+    let block_signing_keypair = Keypair::random(&mut OsRng);
+
+    // Generate reward transaction
+    let tx = generate_transaction(
+        next_block_height,
+        fees,
+        &block_signing_keypair,
+        recipient_config,
+        zkbin,
+        pk,
+    )?;
+
+    // Apply producer transaction in the overlay
+    let _ = apply_producer_transaction(
+        &overlay,
+        next_block_height,
+        block_target,
+        &tx,
+        &mut MerkleTree::new(1),
+    )
+    .await?;
+    txs.push(tx);
+
+    // Grab the updated contracts states root
+    let diff = overlay.lock().unwrap().overlay.lock().unwrap().diff(&extended_fork.diffs)?;
+    overlay
+        .lock()
+        .unwrap()
+        .contracts
+        .update_state_monotree(&diff, &mut extended_fork.state_monotree)?;
+    let Some(state_root) = extended_fork.state_monotree.get_headroot()? else {
+        return Err(Error::ContractsStatesRootNotFoundError);
+    };
+
+    // Drop new trees opened by the unproposed transactions overlay
+    overlay.lock().unwrap().overlay.lock().unwrap().purge_new_trees()?;
+
+    // Generate the new header
+    let mut header =
+        Header::new(last_proposal.hash, next_block_height, Timestamp::current_time(), 0);
+    header.state_root = state_root;
+
+    // Generate the block
+    let mut next_block = BlockInfo::new_empty(header);
+
+    // Add transactions to the block
+    next_block.append_txs(txs);
+
+    // Grab the next mine target
+    let target = extended_fork.module.next_mine_target()?;
+
+    Ok((target, next_block, block_signing_keypair.secret))
+}
+
+/// Auxiliary function to generate a Money::PoWReward transaction.
+fn generate_transaction(
+    block_height: u32,
+    fees: u64,
+    block_signing_keypair: &Keypair,
+    recipient_config: &MinerRewardsRecipientConfig,
+    zkbin: &ZkBinary,
+    pk: &ProvingKey,
+) -> Result<Transaction> {
+    // Build the transaction debris
+    let debris = PoWRewardCallBuilder {
+        signature_keypair: *block_signing_keypair,
+        block_height,
+        fees,
+        recipient: Some(*recipient_config.recipient.public_key()),
+        spend_hook: recipient_config.spend_hook,
+        user_data: recipient_config.user_data,
+        mint_zkbin: zkbin.clone(),
+        mint_pk: pk.clone(),
+    }
+    .build()?;
+
+    // Generate and sign the actual transaction
+    let mut data = vec![MoneyFunction::PoWRewardV1 as u8];
+    debris.params.encode(&mut data)?;
+    let call = ContractCall { contract_id: *MONEY_CONTRACT_ID, data };
+    let mut tx_builder =
+        TransactionBuilder::new(ContractCallLeaf { call, proofs: debris.proofs }, vec![])?;
+    let mut tx = tx_builder.build()?;
+    let sigs = tx.create_sigs(&[block_signing_keypair.secret])?;
+    tx.signatures = vec![sigs];
+
+    Ok(tx)
+}

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

@@ -122,7 +122,7 @@ impl RequestHandler<StratumRpcHandler> for DarkfiNode {
 	}
 
     async fn connections_mut(&self) -> MutexGuard<'life0, HashSet<StoppableTaskPtr>> {
-        self.stratum_rpc_connections.lock().await
+        self.registry.stratum_rpc_connections.lock().await
     }
 }
 
@@ -148,7 +148,7 @@ impl RequestHandler<MmRpcHandler> for DarkfiNode {
     }
 
     async fn connections_mut(&self) -> MutexGuard<'life0, HashSet<StoppableTaskPtr>> {
-        self.mm_rpc_connections.lock().await
+        self.registry.mm_rpc_connections.lock().await
     }
 }
 

+ 1 - 170
bin/darkfid/src/rpc_miner.rs

@@ -16,63 +16,7 @@
  * along with this program.  If not, see <https://www.gnu.org/licenses/>.
  */
 
-use std::{collections::HashMap, str::FromStr};
-
-use darkfi::{
-    blockchain::{BlockInfo, Header, HeaderHash},
-    rpc::jsonrpc::{ErrorCode, ErrorCode::InvalidParams, JsonError, JsonResponse, JsonResult},
-    tx::{ContractCallLeaf, Transaction, TransactionBuilder},
-    util::{encoding::base64, time::Timestamp},
-    validator::{
-        consensus::{Fork, Proposal},
-        pow::{RANDOMX_KEY_CHANGE_DELAY, RANDOMX_KEY_CHANGING_HEIGHT},
-        verification::apply_producer_transaction,
-    },
-    zk::ProvingKey,
-    zkas::ZkBinary,
-    Error, Result,
-};
-use darkfi_money_contract::{client::pow_reward_v1::PoWRewardCallBuilder, MoneyFunction};
-use darkfi_sdk::{
-    crypto::{
-        keypair::{Address, Keypair, SecretKey},
-        pasta_prelude::PrimeField,
-        FuncId, MerkleTree, MONEY_CONTRACT_ID,
-    },
-    pasta::pallas,
-    ContractCall,
-};
-use darkfi_serial::{serialize_async, Encodable};
-use num_bigint::BigUint;
-use rand::rngs::OsRng;
-use tinyjson::JsonValue;
-use tracing::{error, info};
-
-use crate::{proto::ProposalMessage, server_error, DarkfiNode, RpcError};
-
-/// Auxiliary structure representing node miner rewards recipient configuration.
-pub struct MinerRewardsRecipientConfig {
-    /// Wallet mining address to receive mining rewards
-    pub recipient: Address,
-    /// Optional contract spend hook to use in the mining reward
-    pub spend_hook: Option<FuncId>,
-    /// Optional contract user data to use in the mining reward.
-    /// This is not arbitrary data.
-    pub user_data: Option<pallas::Base>,
-}
-
-/// Auxiliary structure representing a block template for native mining.
-#[derive(Debug, Clone)]
-pub struct BlockTemplate {
-    /// Block that is being mined
-    pub block: BlockInfo,
-    /// RandomX init key
-    pub randomx_key: [u8; 32],
-    /// Block mining target
-    pub target: BigUint,
-    /// Ephemeral signing secret for this blocktemplate
-    pub secret: SecretKey,
-}
+use crate::DarkfiNode;
 
 impl DarkfiNode {
     /*
@@ -488,116 +432,3 @@ impl DarkfiNode {
     }
     */
 }
-
-/// Auxiliary function to generate next block in an atomic manner.
-pub async fn generate_next_block(
-    extended_fork: &mut Fork,
-    recipient_config: &MinerRewardsRecipientConfig,
-    zkbin: &ZkBinary,
-    pk: &ProvingKey,
-    block_target: u32,
-    verify_fees: bool,
-) -> Result<(BigUint, BlockInfo, SecretKey)> {
-    // Grab forks' last block proposal(previous)
-    let last_proposal = extended_fork.last_proposal()?;
-
-    // Grab forks' next block height
-    let next_block_height = last_proposal.block.header.height + 1;
-
-    // Grab forks' unproposed transactions
-    let (mut txs, _, fees, overlay) = extended_fork
-        .unproposed_txs(&extended_fork.blockchain, next_block_height, block_target, verify_fees)
-        .await?;
-
-    // Create an ephemeral block signing keypair. Its secret key will
-    // be stored in the PowReward transaction's encrypted note for
-    // later retrieval. It is encrypted towards the recipient's public
-    // key.
-    let block_signing_keypair = Keypair::random(&mut OsRng);
-
-    // Generate reward transaction
-    let tx = generate_transaction(
-        next_block_height,
-        fees,
-        &block_signing_keypair,
-        recipient_config,
-        zkbin,
-        pk,
-    )?;
-
-    // Apply producer transaction in the overlay
-    let _ = apply_producer_transaction(
-        &overlay,
-        next_block_height,
-        block_target,
-        &tx,
-        &mut MerkleTree::new(1),
-    )
-    .await?;
-    txs.push(tx);
-
-    // Grab the updated contracts states root
-    let diff = overlay.lock().unwrap().overlay.lock().unwrap().diff(&extended_fork.diffs)?;
-    overlay
-        .lock()
-        .unwrap()
-        .contracts
-        .update_state_monotree(&diff, &mut extended_fork.state_monotree)?;
-    let Some(state_root) = extended_fork.state_monotree.get_headroot()? else {
-        return Err(Error::ContractsStatesRootNotFoundError);
-    };
-
-    // Drop new trees opened by the unproposed transactions overlay
-    overlay.lock().unwrap().overlay.lock().unwrap().purge_new_trees()?;
-
-    // Generate the new header
-    let mut header =
-        Header::new(last_proposal.hash, next_block_height, Timestamp::current_time(), 0);
-    header.state_root = state_root;
-
-    // Generate the block
-    let mut next_block = BlockInfo::new_empty(header);
-
-    // Add transactions to the block
-    next_block.append_txs(txs);
-
-    // Grab the next mine target
-    let target = extended_fork.module.next_mine_target()?;
-
-    Ok((target, next_block, block_signing_keypair.secret))
-}
-
-/// Auxiliary function to generate a Money::PoWReward transaction.
-fn generate_transaction(
-    block_height: u32,
-    fees: u64,
-    block_signing_keypair: &Keypair,
-    recipient_config: &MinerRewardsRecipientConfig,
-    zkbin: &ZkBinary,
-    pk: &ProvingKey,
-) -> Result<Transaction> {
-    // Build the transaction debris
-    let debris = PoWRewardCallBuilder {
-        signature_keypair: *block_signing_keypair,
-        block_height,
-        fees,
-        recipient: Some(*recipient_config.recipient.public_key()),
-        spend_hook: recipient_config.spend_hook,
-        user_data: recipient_config.user_data,
-        mint_zkbin: zkbin.clone(),
-        mint_pk: pk.clone(),
-    }
-    .build()?;
-
-    // Generate and sign the actual transaction
-    let mut data = vec![MoneyFunction::PoWRewardV1 as u8];
-    debris.params.encode(&mut data)?;
-    let call = ContractCall { contract_id: *MONEY_CONTRACT_ID, data };
-    let mut tx_builder =
-        TransactionBuilder::new(ContractCallLeaf { call, proofs: debris.proofs }, vec![])?;
-    let mut tx = tx_builder.build()?;
-    let sigs = tx.create_sigs(&[block_signing_keypair.secret])?;
-    tx.signatures = vec![sigs];
-
-    Ok(tx)
-}

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

@@ -30,7 +30,7 @@ use tracing::{error, info};
 
 use crate::{
     proto::ProposalMessage,
-    rpc_miner::{generate_next_block, MinerRewardsRecipientConfig},
+    registry::model::{generate_next_block, MinerRewardsRecipientConfig},
     BlockTemplate, DarkfiNode, MiningJobs,
 };
 
@@ -132,7 +132,7 @@ impl DarkfiNode {
         // JSONRPC notifications to this connection when a new job is available.
 
         // We'll clear any existing jobs for this login.
-        let mut mining_jobs = self.mining_jobs.lock().await;
+        let mut mining_jobs = self.registry.mining_jobs.lock().await;
         mining_jobs.insert(conn_id, MiningJobs::default());
 
         // Find applicable chain fork
@@ -165,8 +165,8 @@ impl DarkfiNode {
         let (target, block, secret) = match generate_next_block(
             &mut extended_fork,
             &recipient_config,
-            &self.powrewardv1_zk.zkbin,
-            &self.powrewardv1_zk.provingkey,
+            &self.registry.powrewardv1_zk.zkbin,
+            &self.registry.powrewardv1_zk.provingkey,
             target,
             self.validator.verify_fees,
         )
@@ -285,7 +285,7 @@ impl DarkfiNode {
         let job_id: [u8; 32] = job_id.try_into().unwrap();
 
         // We should be aware of this conn_id and job_id.
-        let mut mining_jobs = self.mining_jobs.lock().await;
+        let mut mining_jobs = self.registry.mining_jobs.lock().await;
         let Some(jobs) = mining_jobs.get_mut(&conn_id) else {
             return JsonError::new(InvalidParams, None, id).into()
         };

+ 18 - 16
bin/darkfid/src/rpc_xmr.rs

@@ -42,7 +42,7 @@ use tracing::{error, info};
 
 use crate::{
     proto::ProposalMessage,
-    rpc_miner::{generate_next_block, MinerRewardsRecipientConfig},
+    registry::model::{generate_next_block, MinerRewardsRecipientConfig, MmBlockTemplate},
     server_error, DarkfiNode, RpcError,
 };
 
@@ -208,7 +208,7 @@ impl DarkfiNode {
         // We'll also obtain a lock here to avoid getting polled
         // multiple times and potentially missing a job. The lock is
         // released when this function exits.
-        let mut mm_blocktemplates = self.mm_blocktemplates.lock().await;
+        let mut mm_blocktemplates = self.registry.mm_blocktemplates.lock().await;
         let mut extended_fork = match self.validator.best_current_fork().await {
             Ok(f) => f,
             Err(e) => {
@@ -219,7 +219,7 @@ impl DarkfiNode {
                 return JsonError::new(ErrorCode::InternalError, None, id).into()
             }
         };
-        if let Some((block, difficulty, _)) = mm_blocktemplates.get(&address_bytes) {
+        if let Some(blocktemplate) = mm_blocktemplates.get(&address_bytes) {
             let last_proposal = match extended_fork.last_proposal() {
                 Ok(p) => p,
                 Err(e) => {
@@ -230,13 +230,13 @@ impl DarkfiNode {
                     return JsonError::new(ErrorCode::InternalError, None, id).into()
                 }
             };
-            if last_proposal.hash == block.header.previous {
-                let blockhash = block.header.template_hash();
+            if last_proposal.hash == blocktemplate.block.header.previous {
+                let blockhash = blocktemplate.block.header.template_hash();
                 return if blockhash != aux_hash {
                     JsonResponse::new(
                         JsonValue::from(HashMap::from([
                             ("aux_blob".to_string(), JsonValue::from(hex::encode(address_bytes))),
-                            ("aux_diff".to_string(), JsonValue::from(*difficulty)),
+                            ("aux_diff".to_string(), JsonValue::from(blocktemplate.difficulty)),
                             ("aux_hash".to_string(), JsonValue::from(blockhash.as_string())),
                         ])),
                         id,
@@ -279,11 +279,11 @@ impl DarkfiNode {
             }
         };
 
-        let (_, blocktemplate, block_signing_secret) = match generate_next_block(
+        let (_, block, block_signing_secret) = match generate_next_block(
             &mut extended_fork,
             &recipient_config,
-            &self.powrewardv1_zk.zkbin,
-            &self.powrewardv1_zk.provingkey,
+            &self.registry.powrewardv1_zk.zkbin,
+            &self.registry.powrewardv1_zk.provingkey,
             self.validator.consensus.module.read().await.target,
             self.validator.verify_fees,
         )
@@ -301,9 +301,11 @@ impl DarkfiNode {
 
         // Now we have the blocktemplate. We'll mark it down in memory,
         // and then ship it to RPC.
-        let blockhash = blocktemplate.header.template_hash();
-        mm_blocktemplates
-            .insert(address_bytes.clone(), (blocktemplate, difficulty, block_signing_secret));
+        let blockhash = block.header.template_hash();
+        mm_blocktemplates.insert(
+            address_bytes.clone(),
+            MmBlockTemplate { block, difficulty, secret: block_signing_secret },
+        );
         info!(
             target: "darkfid::rpc_xmr::xmr_merge_mining_get_aux_block",
             "[RPC-XMR] Created new blocktemplate: address={recipient_str}, spend_hook={spend_hook_str}, user_data={user_data_str}, aux_hash={blockhash}, height={height}, prev_id={prev_id}"
@@ -406,7 +408,7 @@ impl DarkfiNode {
         };
 
         // If we don't know about this job, we can just abort here.
-        let mut mm_blocktemplates = self.mm_blocktemplates.lock().await;
+        let mut mm_blocktemplates = self.registry.mm_blocktemplates.lock().await;
         if !mm_blocktemplates.contains_key(&address_bytes) {
             return server_error(RpcError::MinerUnknownJob, id, None)
         }
@@ -487,10 +489,10 @@ impl DarkfiNode {
         };
 
         // Append MoneroPowData to the DarkFi block and sign it
-        let (block, _, secret) = &mm_blocktemplates.get(&address_bytes).unwrap();
-        let mut block = block.clone();
+        let blocktemplate = &mm_blocktemplates.get(&address_bytes).unwrap();
+        let mut block = blocktemplate.block.clone();
         block.header.pow_data = PowData::Monero(monero_pow_data);
-        block.sign(secret);
+        block.sign(&blocktemplate.secret);
 
         // At this point we should be able to remove the submitted job.
         // We still won't release the lock in hope of proposing the block

+ 5 - 5
bin/darkfid/src/task/consensus.rs

@@ -224,8 +224,8 @@ async fn consensus_task(
 /// active forks or last confirmed block.
 async fn clean_blocktemplates(node: &DarkfiNodePtr) -> Result<()> {
     // Grab a lock over node mining templates
-    let mut blocktemplates = node.blocktemplates.lock().await;
-    let mut mm_blocktemplates = node.mm_blocktemplates.lock().await;
+    let mut blocktemplates = node.registry.blocktemplates.lock().await;
+    let mut mm_blocktemplates = node.registry.mm_blocktemplates.lock().await;
 
     // Early return if no mining block templates exist
     if blocktemplates.is_empty() && mm_blocktemplates.is_empty() {
@@ -268,20 +268,20 @@ async fn clean_blocktemplates(node: &DarkfiNodePtr) -> Result<()> {
 
     // Loop through merge mining templates to find which can be dropped
     let mut dropped_templates = vec![];
-    'outer: for (key, (block, _, _)) in mm_blocktemplates.iter() {
+    'outer: for (key, blocktemplate) in mm_blocktemplates.iter() {
         // Loop through all the forks
         for fork in forks.iter() {
             // Traverse fork proposals sequence in reverse
             for p_hash in fork.proposals.iter().rev() {
                 // Check if job extends this fork
-                if &block.header.previous == p_hash {
+                if &blocktemplate.block.header.previous == p_hash {
                     continue 'outer
                 }
             }
         }
 
         // Check if it extends last confirmed block
-        if block.header.previous == last_confirmed {
+        if blocktemplate.block.header.previous == last_confirmed {
             continue
         }
 

+ 5 - 2
bin/darkfid/src/tests/harness.rs

@@ -51,6 +51,7 @@ use url::Url;
 
 use crate::{
     proto::{DarkfidP2pHandler, ProposalMessage},
+    registry::DarkfiMinersRegistry,
     task::sync::sync_task,
     DarkfiNode, DarkfiNodePtr,
 };
@@ -292,16 +293,18 @@ pub async fn generate_node(
     subscribers.insert("dnet", JsonSubscriber::new("dnet.subscribe_events"));
 
     let p2p_handler = DarkfidP2pHandler::init(settings, ex).await?;
+    let registry = DarkfiMinersRegistry::init(&validator)?;
     let node = DarkfiNode::new(
         Network::Mainnet,
-        p2p_handler.clone(),
         validator.clone(),
+        p2p_handler.clone(),
+        registry,
         50,
         subscribers.clone(),
     )
     .await?;
 
-    p2p_handler.clone().start(ex, &validator, &subscribers).await?;
+    p2p_handler.start(ex, &validator, &subscribers).await?;
 
     node.validator.consensus.generate_empty_fork().await?;
 

+ 3 - 13
bin/darkfid/src/tests/mod.rs

@@ -280,14 +280,10 @@ fn darkfid_programmatic_control() -> Result<()> {
                     checkpoint_height: None,
                     checkpoint: None,
                 };
-                let main_rpc_settings = RpcSettings {
+                let rpc_settings = RpcSettings {
                     listen: Url::parse("tcp://127.0.0.1:8240").unwrap(),
                     ..RpcSettings::default()
                 };
-                let stratum_rpc_settings = RpcSettings {
-                    listen: Url::parse("tcp://127..0.0.1:8241").unwrap(),
-                    ..RpcSettings::default()
-                };
 
                 // Initialize a daemon
                 let daemon = crate::Darkfid::init(
@@ -302,19 +298,13 @@ fn darkfid_programmatic_control() -> Result<()> {
                 .unwrap();
 
                 // Start it
-                daemon
-                    .start(&ex, &main_rpc_settings, &stratum_rpc_settings, &None, &consensus_config)
-                    .await
-                    .unwrap();
+                daemon.start(&ex, &rpc_settings, &None, &None, &consensus_config).await.unwrap();
 
                 // Stop it
                 daemon.stop().await.unwrap();
 
                 // Start it again
-                daemon
-                    .start(&ex, &main_rpc_settings, &stratum_rpc_settings, &None, &consensus_config)
-                    .await
-                    .unwrap();
+                daemon.start(&ex, &rpc_settings, &None, &None, &consensus_config).await.unwrap();
 
                 // Stop it
                 daemon.stop().await.unwrap();