saito_core/core/
mining_thread.rs1use 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
30pub 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 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 if self.public_key == [0; 33] {
53 let wallet = self.wallet_lock.read().await;
54 if wallet.public_key == [0; 33] {
55 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 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(>.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}