1use darkfi::{
20 blockchain::{BlockInfo, Header},
21 rpc::{jsonrpc::JsonNotification, util::JsonValue},
22 system::{ExecutorPtr, StoppableTask, Subscription},
23 tx::{ContractCallLeaf, Transaction, TransactionBuilder},
24 util::{encoding::base64, time::Timestamp},
25 validator::{
26 consensus::{Fork, Proposal},
27 utils::best_fork_index,
28 },
29 zk::{empty_witnesses, ProvingKey, ZkCircuit},
30 zkas::ZkBinary,
31 Error, Result,
32};
33use darkfi_money_contract::{
34 client::pow_reward_v1::PoWRewardCallBuilder, MoneyFunction, MONEY_CONTRACT_ZKAS_MINT_NS_V1,
35};
36use darkfi_sdk::{
37 crypto::{poseidon_hash, FuncId, PublicKey, SecretKey, MONEY_CONTRACT_ID},
38 pasta::pallas,
39 ContractCall,
40};
41use darkfi_serial::{serialize_async, Encodable};
42use log::{error, info};
43use num_bigint::BigUint;
44use rand::rngs::OsRng;
45use smol::channel::{Receiver, Sender};
46
47use crate::{proto::ProposalMessage, task::garbage_collect_task, DarkfiNodePtr};
48
49pub struct MinerRewardsRecipientConfig {
51 pub recipient: PublicKey,
52 pub spend_hook: Option<FuncId>,
53 pub user_data: Option<pallas::Base>,
54}
55
56pub async fn miner_task(
68 node: &DarkfiNodePtr,
69 recipient_config: &MinerRewardsRecipientConfig,
70 skip_sync: bool,
71 ex: &ExecutorPtr,
72) -> Result<()> {
73 info!(target: "darkfid::task::miner_task", "Starting miner task...");
75
76 info!(target: "darkfid::task::miner_task", "Generating zkas bin and proving keys...");
78 let (zkbin, _) = node.validator.blockchain.contracts.get_zkas(
79 &node.validator.blockchain.sled_db,
80 &MONEY_CONTRACT_ID,
81 MONEY_CONTRACT_ZKAS_MINT_NS_V1,
82 )?;
83 let circuit = ZkCircuit::new(empty_witnesses(&zkbin)?, &zkbin);
84 let pk = ProvingKey::build(zkbin.k, &circuit);
85
86 info!(target: "darkfid::task::miner_task", "Generating signing key...");
91 let mut secret = SecretKey::random(&mut OsRng);
92
93 let block_sub = node.subscribers.get("blocks").unwrap();
95
96 let proposals_sub = node.subscribers.get("proposals").unwrap();
98 let subscription = proposals_sub.publisher.clone().subscribe().await;
99
100 if !skip_sync {
102 info!(target: "darkfid::task::miner_task", "Waiting for next confirmation...");
103 loop {
104 subscription.receive().await;
105
106 let confirmed = node.validator.confirmation().await?;
108
109 if confirmed.is_empty() {
110 continue
111 }
112
113 let mut notif_blocks = Vec::with_capacity(confirmed.len());
114 for block in confirmed {
115 notif_blocks
116 .push(JsonValue::String(base64::encode(&serialize_async(&block).await)));
117 }
118 block_sub.notify(JsonValue::Array(notif_blocks)).await;
119 break;
120 }
121 }
122
123 let (sender, stop_signal) = smol::channel::bounded(1);
125
126 let gc_task = StoppableTask::new();
128 gc_task.clone().start(
129 async { Ok(()) },
130 |_| async { },
131 Error::GarbageCollectionTaskStopped,
132 ex.clone(),
133 );
134
135 info!(target: "darkfid::task::miner_task", "Miner initialized successfully!");
136
137 loop {
139 let forks = node.validator.consensus.forks.read().await;
141 let index = match best_fork_index(&forks) {
142 Ok(i) => i,
143 Err(e) => {
144 error!(
145 target: "darkfid::task::miner_task",
146 "Finding best fork index failed: {e}"
147 );
148 continue
149 }
150 };
151 let extended_fork = match forks[index].full_clone() {
152 Ok(f) => f,
153 Err(e) => {
154 error!(
155 target: "darkfid::task::miner_task",
156 "Fork full clone creation failed: {e}"
157 );
158 continue
159 }
160 };
161 drop(forks);
162
163 match smol::future::or(
165 listen_to_network(node, &extended_fork, &subscription, &sender),
166 mine(
167 node,
168 &extended_fork,
169 &mut secret,
170 recipient_config,
171 &zkbin,
172 &pk,
173 &stop_signal,
174 skip_sync,
175 ),
176 )
177 .await
178 {
179 Ok(_) => { }
180 Err(Error::NetworkNotConnected) => {
181 error!(target: "darkfid::task::miner_task", "Node disconnected from the network");
182 subscription.unsubscribe().await;
183 return Err(Error::NetworkNotConnected)
184 }
185 Err(e) => {
186 error!(
187 target: "darkfid::task::miner_task",
188 "Error during listen_to_network() or mine(): {e}"
189 );
190 continue
191 }
192 }
193
194 let confirmed = match node.validator.confirmation().await {
196 Ok(f) => f,
197 Err(e) => {
198 error!(
199 target: "darkfid::task::miner_task",
200 "Confirmation failed: {e}"
201 );
202 continue
203 }
204 };
205
206 if confirmed.is_empty() {
207 continue
208 }
209
210 let mut notif_blocks = Vec::with_capacity(confirmed.len());
211 for block in confirmed {
212 notif_blocks.push(JsonValue::String(base64::encode(&serialize_async(&block).await)));
213 }
214 block_sub.notify(JsonValue::Array(notif_blocks)).await;
215
216 gc_task.clone().stop().await;
218 gc_task.clone().start(
219 garbage_collect_task(node.clone()),
220 |res| async {
221 match res {
222 Ok(()) | Err(Error::GarbageCollectionTaskStopped) => { }
223 Err(e) => {
224 error!(target: "darkfid", "Failed starting garbage collection task: {}", e)
225 }
226 }
227 },
228 Error::GarbageCollectionTaskStopped,
229 ex.clone(),
230 );
231 }
232}
233
234async fn listen_to_network(
236 node: &DarkfiNodePtr,
237 extended_fork: &Fork,
238 subscription: &Subscription<JsonNotification>,
239 sender: &Sender<()>,
240) -> Result<()> {
241 let last_proposal_hash = extended_fork.last_proposal()?.hash;
243 loop {
244 subscription.receive().await;
246
247 let forks = node.validator.consensus.forks.read().await;
249
250 let index = best_fork_index(&forks)?;
252
253 if forks[index].last_proposal()?.hash != last_proposal_hash {
255 drop(forks);
256 break
257 }
258
259 drop(forks);
260 }
261
262 sender.send(()).await?;
264 if let Err(e) = node.miner_daemon_request("abort", &JsonValue::Array(vec![])).await {
265 error!(target: "darkfid::task::miner::listen_to_network", "Failed to execute miner daemon abort request: {}", e);
266 }
267
268 Ok(())
269}
270
271#[allow(clippy::too_many_arguments)]
274async fn mine(
275 node: &DarkfiNodePtr,
276 extended_fork: &Fork,
277 secret: &mut SecretKey,
278 recipient_config: &MinerRewardsRecipientConfig,
279 zkbin: &ZkBinary,
280 pk: &ProvingKey,
281 stop_signal: &Receiver<()>,
282 skip_sync: bool,
283) -> Result<()> {
284 smol::future::or(
285 wait_stop_signal(stop_signal),
286 mine_next_block(node, extended_fork, secret, recipient_config, zkbin, pk, skip_sync),
287 )
288 .await
289}
290
291pub async fn wait_stop_signal(stop_signal: &Receiver<()>) -> Result<()> {
293 if stop_signal.is_full() {
295 stop_signal.recv().await?;
296 }
297
298 stop_signal.recv().await?;
300
301 Ok(())
302}
303
304async fn mine_next_block(
306 node: &DarkfiNodePtr,
307 extended_fork: &Fork,
308 secret: &mut SecretKey,
309 recipient_config: &MinerRewardsRecipientConfig,
310 zkbin: &ZkBinary,
311 pk: &ProvingKey,
312 skip_sync: bool,
313) -> Result<()> {
314 let (next_target, mut next_block) = generate_next_block(
316 extended_fork,
317 secret,
318 recipient_config,
319 zkbin,
320 pk,
321 node.validator.consensus.module.read().await.target,
322 node.validator.verify_fees,
323 )
324 .await?;
325
326 let target = JsonValue::String(next_target.to_string());
328 let block = JsonValue::String(base64::encode(&serialize_async(&next_block).await));
329 let response =
330 node.miner_daemon_request_with_retry("mine", &JsonValue::Array(vec![target, block])).await;
331 next_block.header.nonce = *response.get::<f64>().unwrap() as u64;
332
333 next_block.sign(secret);
335
336 extended_fork.module.verify_current_block(&next_block)?;
338
339 if !skip_sync && !node.p2p_handler.p2p.is_connected() {
341 return Err(Error::NetworkNotConnected)
342 }
343
344 let proposal = Proposal::new(next_block);
346 node.validator.append_proposal(&proposal).await?;
347
348 let message = ProposalMessage(proposal);
350 node.p2p_handler.p2p.broadcast(&message).await;
351
352 Ok(())
353}
354
355async fn generate_next_block(
357 extended_fork: &Fork,
358 secret: &mut SecretKey,
359 recipient_config: &MinerRewardsRecipientConfig,
360 zkbin: &ZkBinary,
361 pk: &ProvingKey,
362 block_target: u32,
363 verify_fees: bool,
364) -> Result<(BigUint, BlockInfo)> {
365 let last_proposal = extended_fork.last_proposal()?;
367
368 let next_block_height = last_proposal.block.header.height + 1;
370
371 let (mut txs, _, fees) = extended_fork
373 .unproposed_txs(&extended_fork.blockchain, next_block_height, block_target, verify_fees)
374 .await?;
375
376 let prefix = pallas::Base::from_raw([4, 0, 0, 0]);
380 let next_secret = poseidon_hash([prefix, secret.inner(), (next_block_height as u64).into()]);
381 *secret = SecretKey::from(next_secret);
382
383 let tx = generate_transaction(next_block_height, fees, secret, recipient_config, zkbin, pk)?;
385 txs.push(tx);
386
387 let header = Header::new(last_proposal.hash, next_block_height, Timestamp::current_time(), 0);
389
390 let mut next_block = BlockInfo::new_empty(header);
392
393 next_block.append_txs(txs);
395
396 let target = extended_fork.module.next_mine_target()?;
398
399 Ok((target, next_block))
400}
401
402fn generate_transaction(
404 block_height: u32,
405 fees: u64,
406 secret: &SecretKey,
407 recipient_config: &MinerRewardsRecipientConfig,
408 zkbin: &ZkBinary,
409 pk: &ProvingKey,
410) -> Result<Transaction> {
411 let debris = PoWRewardCallBuilder {
413 signature_public: PublicKey::from_secret(*secret),
414 block_height,
415 fees,
416 recipient: Some(recipient_config.recipient),
417 spend_hook: recipient_config.spend_hook,
418 user_data: recipient_config.user_data,
419 mint_zkbin: zkbin.clone(),
420 mint_pk: pk.clone(),
421 }
422 .build()?;
423
424 let mut data = vec![MoneyFunction::PoWRewardV1 as u8];
426 debris.params.encode(&mut data)?;
427 let call = ContractCall { contract_id: *MONEY_CONTRACT_ID, data };
428 let mut tx_builder =
429 TransactionBuilder::new(ContractCallLeaf { call, proofs: debris.proofs }, vec![])?;
430 let mut tx = tx_builder.build()?;
431 let sigs = tx.create_sigs(&[*secret])?;
432 tx.signatures = vec![sigs];
433
434 Ok(tx)
435}