saito_core/core/
mining_thread.rs

1use std::sync::Arc;
2use std::time::Duration;
3
4use async_trait::async_trait;
5use log::info;
6use tokio::sync::mpsc::Sender;
7use tokio::sync::RwLock;
8
9use crate::core::consensus::golden_ticket::GoldenTicket;
10use crate::core::consensus::wallet::Wallet;
11use crate::core::consensus_thread::ConsensusEvent;
12use crate::core::defs::{
13    BlockId, PrintForLog, SaitoHash, SaitoPublicKey, StatVariable, Timestamp, CHANNEL_SAFE_BUFFER,
14};
15use crate::core::io::network_event::NetworkEvent;
16use crate::core::process::keep_time::Timer;
17use crate::core::process::process_event::ProcessEvent;
18use crate::core::util::configuration::Configuration;
19use crate::core::util::crypto::{generate_random_bytes, hash};
20
21#[derive(Debug)]
22pub enum MiningEvent {
23    LongestChainBlockAdded {
24        hash: SaitoHash,
25        difficulty: u64,
26        block_id: BlockId,
27    },
28}
29
30/// Manages the miner
31pub struct MiningThread {
32    pub wallet_lock: Arc<RwLock<Wallet>>,
33    pub sender_to_mempool: Sender<ConsensusEvent>,
34    pub timer: Timer,
35    pub miner_active: bool,
36    pub target: SaitoHash,
37    pub target_id: BlockId,
38    pub difficulty: u64,
39    pub public_key: SaitoPublicKey,
40    pub mined_golden_tickets: u64,
41    pub stat_sender: Sender<String>,
42    pub config_lock: Arc<RwLock<dyn Configuration + Send + Sync>>,
43    // todo : make this private and init using configs
44    pub enabled: bool,
45    pub mining_iterations: u32,
46    pub mining_start: Timestamp,
47}
48
49impl MiningThread {
50    pub async fn mine(&mut self) -> Option<GoldenTicket> {
51        // assert!(self.miner_active, "Miner is not active");
52        if self.public_key == [0; 33] {
53            let wallet = self.wallet_lock.read().await;
54            if wallet.public_key == [0; 33] {
55                // wallet not initialized yet
56                return None;
57            }
58            self.public_key = wallet.public_key;
59            info!("node public key = {:?}", self.public_key.to_base58());
60        }
61
62        let random_bytes = hash(&generate_random_bytes(32).await);
63        // The new way of validation will be wasting a GT instance if the validation fails
64        // old way used a static method instead
65        let gt = GoldenTicket::create(self.target, random_bytes, self.public_key);
66        if gt.validate(self.difficulty) {
67            info!(
68                "golden ticket found. sending to mempool. previous block : {:?}:{:?}\n random : {:?} key : {:?} solution : {:?}\n for difficulty : {:?}\n spent_time : {:?}",
69                self.target_id,
70                gt.target.to_hex(),
71                gt.random.to_hex(),
72                gt.public_key.to_base58(),
73                hash(&gt.serialize_for_net()).to_hex(),
74                self.difficulty,
75                self.timer.get_timestamp_in_ms()-self.mining_start
76            );
77
78            return Some(gt);
79        }
80        None
81    }
82}
83
84#[async_trait]
85impl ProcessEvent<MiningEvent> for MiningThread {
86    async fn process_network_event(&mut self, _event: NetworkEvent) -> Option<()> {
87        unreachable!();
88    }
89
90    async fn process_timer_event(&mut self, _duration: Duration) -> Option<()> {
91        if !self.enabled {
92            return None;
93        }
94        if self.enabled && self.miner_active {
95            for _ in 0..self.mining_iterations {
96                if let Some(gt) = self.mine().await {
97                    self.miner_active = false;
98                    self.mined_golden_tickets += 1;
99                    info!(
100                        "sending mined gt target: {:?} to consensus thread. channel_capacity : {:?}",
101                        gt.target.to_hex(),
102                        self.sender_to_mempool.capacity()
103                    );
104                    self.sender_to_mempool
105                        .send(ConsensusEvent::NewGoldenTicket { golden_ticket: gt })
106                        .await
107                        .expect("sending to mempool failed");
108                    return Some(());
109                }
110            }
111            return Some(());
112        }
113
114        None
115    }
116
117    async fn process_event(&mut self, event: MiningEvent) -> Option<()> {
118        if !self.enabled {
119            return None;
120        }
121        match event {
122            MiningEvent::LongestChainBlockAdded {
123                hash,
124                difficulty,
125                block_id,
126            } => {
127                info!(
128                    "Activating miner with hash : {:?} and difficulty : {:?} for block_id : {:?}",
129                    hash.to_hex(),
130                    difficulty,
131                    block_id
132                );
133                self.difficulty = difficulty;
134                self.target = hash;
135                self.target_id = block_id;
136                self.miner_active = true;
137                self.mining_start = self.timer.get_timestamp_in_ms();
138                Some(())
139            }
140        }
141    }
142
143    async fn on_init(&mut self) {
144        let configs = self.config_lock.read().await;
145        info!("is spv mode = {:?}", configs.is_spv_mode());
146        self.enabled = !configs.is_spv_mode();
147        info!("miner is enabled = {:?}", self.enabled);
148        let wallet = self.wallet_lock.read().await;
149        self.public_key = wallet.public_key;
150        info!("node public key = {:?}", self.public_key.to_base58());
151    }
152
153    async fn on_stat_interval(&mut self, current_time: Timestamp) {
154        if !self.enabled {
155            return;
156        }
157
158        let stat = format!("{} - {} - total : {:?}, current difficulty : {:?}, miner_active : {:?}, current target : {:?} ",
159                           StatVariable::format_timestamp(current_time),
160                           format!("{:width$}", "mining::golden_tickets", width = 40),
161                           self.mined_golden_tickets,
162                           self.difficulty,
163                           self.miner_active,
164                           self.target.to_hex());
165        self.stat_sender.send(stat).await.unwrap();
166    }
167
168    fn is_ready_to_process(&self) -> bool {
169        self.sender_to_mempool.capacity() > CHANNEL_SAFE_BUFFER
170    }
171}