1use darkfi::{
20 blockchain::{BlockInfo, Header, HeaderHash},
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 verification::apply_producer_transaction,
29 },
30 zk::{empty_witnesses, ProvingKey, ZkCircuit},
31 zkas::ZkBinary,
32 Error, Result,
33};
34use darkfi_money_contract::{
35 client::pow_reward_v1::PoWRewardCallBuilder, MoneyFunction, MONEY_CONTRACT_ZKAS_MINT_NS_V1,
36};
37use darkfi_sdk::{
38 crypto::{poseidon_hash, FuncId, MerkleTree, PublicKey, SecretKey, MONEY_CONTRACT_ID},
39 pasta::pallas,
40 ContractCall,
41};
42use darkfi_serial::{serialize_async, Encodable};
43use log::{error, info};
44use num_bigint::BigUint;
45use rand::rngs::OsRng;
46use smol::channel::{Receiver, Sender};
47
48use crate::{proto::ProposalMessage, task::garbage_collect_task, DarkfiNodePtr};
49
50pub struct MinerRewardsRecipientConfig {
52 pub recipient: PublicKey,
53 pub spend_hook: Option<FuncId>,
54 pub user_data: Option<pallas::Base>,
55}
56
57pub async fn miner_task(
69 node: &DarkfiNodePtr,
70 recipient_config: &MinerRewardsRecipientConfig,
71 skip_sync: bool,
72 ex: &ExecutorPtr,
73) -> Result<()> {
74 info!(target: "darkfid::task::miner_task", "Starting miner task...");
76
77 info!(target: "darkfid::task::miner_task", "Generating zkas bin and proving keys...");
79 let (zkbin, _) = node.validator.blockchain.contracts.get_zkas(
80 &node.validator.blockchain.sled_db,
81 &MONEY_CONTRACT_ID,
82 MONEY_CONTRACT_ZKAS_MINT_NS_V1,
83 )?;
84 let circuit = ZkCircuit::new(empty_witnesses(&zkbin)?, &zkbin);
85 let pk = ProvingKey::build(zkbin.k, &circuit);
86
87 info!(target: "darkfid::task::miner_task", "Generating signing key...");
92 let mut secret = SecretKey::random(&mut OsRng);
93
94 let block_sub = node.subscribers.get("blocks").unwrap();
96
97 let proposals_sub = node.subscribers.get("proposals").unwrap();
99 let subscription = proposals_sub.publisher.clone().subscribe().await;
100
101 if !skip_sync {
103 info!(target: "darkfid::task::miner_task", "Waiting for next confirmation...");
104 loop {
105 subscription.receive().await;
106
107 let confirmed = node.validator.confirmation().await?;
109
110 if confirmed.is_empty() {
111 continue
112 }
113
114 let mut notif_blocks = Vec::with_capacity(confirmed.len());
115 for block in confirmed {
116 notif_blocks
117 .push(JsonValue::String(base64::encode(&serialize_async(&block).await)));
118 }
119 block_sub.notify(JsonValue::Array(notif_blocks)).await;
120 break;
121 }
122 }
123
124 let (sender, stop_signal) = smol::channel::bounded(1);
126
127 let gc_task = StoppableTask::new();
129 gc_task.clone().start(
130 async { Ok(()) },
131 |_| async { },
132 Error::GarbageCollectionTaskStopped,
133 ex.clone(),
134 );
135
136 info!(target: "darkfid::task::miner_task", "Miner initialized successfully!");
137
138 loop {
140 let forks = node.validator.consensus.forks.read().await;
142 let index = match best_fork_index(&forks) {
143 Ok(i) => i,
144 Err(e) => {
145 error!(
146 target: "darkfid::task::miner_task",
147 "Finding best fork index failed: {e}"
148 );
149 continue
150 }
151 };
152 let extended_fork = match forks[index].full_clone() {
153 Ok(f) => f,
154 Err(e) => {
155 error!(
156 target: "darkfid::task::miner_task",
157 "Fork full clone creation failed: {e}"
158 );
159 continue
160 }
161 };
162 drop(forks);
163
164 let last_proposal_hash = extended_fork.last_proposal()?.hash;
166
167 match smol::future::or(
169 listen_to_network(node, last_proposal_hash, &subscription, &sender),
170 mine(
171 node,
172 extended_fork,
173 &mut secret,
174 recipient_config,
175 &zkbin,
176 &pk,
177 &stop_signal,
178 skip_sync,
179 ),
180 )
181 .await
182 {
183 Ok(_) => { }
184 Err(Error::NetworkNotConnected) => {
185 error!(target: "darkfid::task::miner_task", "Node disconnected from the network");
186 subscription.unsubscribe().await;
187 return Err(Error::NetworkNotConnected)
188 }
189 Err(e) => {
190 error!(
191 target: "darkfid::task::miner_task",
192 "Error during listen_to_network() or mine(): {e}"
193 );
194 continue
195 }
196 }
197
198 let confirmed = match node.validator.confirmation().await {
200 Ok(f) => f,
201 Err(e) => {
202 error!(
203 target: "darkfid::task::miner_task",
204 "Confirmation failed: {e}"
205 );
206 continue
207 }
208 };
209
210 if confirmed.is_empty() {
211 continue
212 }
213
214 let mut notif_blocks = Vec::with_capacity(confirmed.len());
215 for block in confirmed {
216 notif_blocks.push(JsonValue::String(base64::encode(&serialize_async(&block).await)));
217 }
218 block_sub.notify(JsonValue::Array(notif_blocks)).await;
219
220 gc_task.clone().stop().await;
222 gc_task.clone().start(
223 garbage_collect_task(node.clone()),
224 |res| async {
225 match res {
226 Ok(()) | Err(Error::GarbageCollectionTaskStopped) => { }
227 Err(e) => {
228 error!(target: "darkfid", "Failed starting garbage collection task: {e}")
229 }
230 }
231 },
232 Error::GarbageCollectionTaskStopped,
233 ex.clone(),
234 );
235 }
236}
237
238async fn listen_to_network(
240 node: &DarkfiNodePtr,
241 last_proposal_hash: HeaderHash,
242 subscription: &Subscription<JsonNotification>,
243 sender: &Sender<()>,
244) -> Result<()> {
245 loop {
246 subscription.receive().await;
248
249 let forks = node.validator.consensus.forks.read().await;
251
252 let index = best_fork_index(&forks)?;
254
255 if forks[index].last_proposal()?.hash != last_proposal_hash {
257 drop(forks);
258 break
259 }
260
261 drop(forks);
262 }
263
264 sender.send(()).await?;
266 if let Err(e) = node.miner_daemon_request("abort", &JsonValue::Array(vec![])).await {
267 error!(target: "darkfid::task::miner::listen_to_network", "Failed to execute miner daemon abort request: {e}");
268 }
269
270 Ok(())
271}
272
273#[allow(clippy::too_many_arguments)]
276async fn mine(
277 node: &DarkfiNodePtr,
278 extended_fork: Fork,
279 secret: &mut SecretKey,
280 recipient_config: &MinerRewardsRecipientConfig,
281 zkbin: &ZkBinary,
282 pk: &ProvingKey,
283 stop_signal: &Receiver<()>,
284 skip_sync: bool,
285) -> Result<()> {
286 smol::future::or(
287 wait_stop_signal(stop_signal),
288 mine_next_block(node, extended_fork, secret, recipient_config, zkbin, pk, skip_sync),
289 )
290 .await
291}
292
293pub async fn wait_stop_signal(stop_signal: &Receiver<()>) -> Result<()> {
295 if stop_signal.is_full() {
297 stop_signal.recv().await?;
298 }
299
300 stop_signal.recv().await?;
302
303 Ok(())
304}
305
306async fn mine_next_block(
308 node: &DarkfiNodePtr,
309 mut extended_fork: Fork,
310 secret: &mut SecretKey,
311 recipient_config: &MinerRewardsRecipientConfig,
312 zkbin: &ZkBinary,
313 pk: &ProvingKey,
314 skip_sync: bool,
315) -> Result<()> {
316 let (next_target, mut next_block) = generate_next_block(
318 &mut extended_fork,
319 secret,
320 recipient_config,
321 zkbin,
322 pk,
323 node.validator.consensus.module.read().await.target,
324 node.validator.verify_fees,
325 )
326 .await?;
327
328 let target = JsonValue::String(next_target.to_string());
330 let block = JsonValue::String(base64::encode(&serialize_async(&next_block).await));
331 let response =
332 node.miner_daemon_request_with_retry("mine", &JsonValue::Array(vec![target, block])).await;
333 next_block.header.nonce = *response.get::<f64>().unwrap() as u64;
334
335 next_block.sign(secret);
337
338 extended_fork.module.verify_current_block(&next_block)?;
340
341 if !skip_sync && !node.p2p_handler.p2p.is_connected() {
343 return Err(Error::NetworkNotConnected)
344 }
345
346 let proposal = Proposal::new(next_block);
348 node.validator.append_proposal(&proposal).await?;
349
350 let message = ProposalMessage(proposal);
352 node.p2p_handler.p2p.broadcast(&message).await;
353
354 Ok(())
355}
356
357async fn generate_next_block(
359 extended_fork: &mut Fork,
360 secret: &mut SecretKey,
361 recipient_config: &MinerRewardsRecipientConfig,
362 zkbin: &ZkBinary,
363 pk: &ProvingKey,
364 block_target: u32,
365 verify_fees: bool,
366) -> Result<(BigUint, BlockInfo)> {
367 let last_proposal = extended_fork.last_proposal()?;
369
370 let next_block_height = last_proposal.block.header.height + 1;
372
373 let (mut txs, _, fees, overlay) = extended_fork
375 .unproposed_txs(&extended_fork.blockchain, next_block_height, block_target, verify_fees)
376 .await?;
377
378 let prefix = pallas::Base::from_raw([4, 0, 0, 0]);
382 let next_secret = poseidon_hash([prefix, secret.inner(), (next_block_height as u64).into()]);
383 *secret = SecretKey::from(next_secret);
384
385 let tx = generate_transaction(next_block_height, fees, secret, recipient_config, zkbin, pk)?;
387
388 let _ = apply_producer_transaction(
390 &overlay,
391 next_block_height,
392 block_target,
393 &tx,
394 &mut MerkleTree::new(1),
395 )
396 .await?;
397 txs.push(tx);
398
399 overlay.lock().unwrap().contracts.update_state_monotree(&mut extended_fork.state_monotree)?;
401 let Some(state_root) = extended_fork.state_monotree.get_headroot()? else {
402 return Err(Error::ContractsStatesRootNotFoundError);
403 };
404
405 overlay.lock().unwrap().overlay.lock().unwrap().purge_new_trees()?;
407
408 let mut header =
410 Header::new(last_proposal.hash, next_block_height, Timestamp::current_time(), 0);
411 header.state_root = state_root;
412
413 let mut next_block = BlockInfo::new_empty(header);
415
416 next_block.append_txs(txs);
418
419 let target = extended_fork.module.next_mine_target()?;
421
422 Ok((target, next_block))
423}
424
425fn generate_transaction(
427 block_height: u32,
428 fees: u64,
429 secret: &SecretKey,
430 recipient_config: &MinerRewardsRecipientConfig,
431 zkbin: &ZkBinary,
432 pk: &ProvingKey,
433) -> Result<Transaction> {
434 let debris = PoWRewardCallBuilder {
436 signature_public: PublicKey::from_secret(*secret),
437 block_height,
438 fees,
439 recipient: Some(recipient_config.recipient),
440 spend_hook: recipient_config.spend_hook,
441 user_data: recipient_config.user_data,
442 mint_zkbin: zkbin.clone(),
443 mint_pk: pk.clone(),
444 }
445 .build()?;
446
447 let mut data = vec![MoneyFunction::PoWRewardV1 as u8];
449 debris.params.encode(&mut data)?;
450 let call = ContractCall { contract_id: *MONEY_CONTRACT_ID, data };
451 let mut tx_builder =
452 TransactionBuilder::new(ContractCallLeaf { call, proofs: debris.proofs }, vec![])?;
453 let mut tx = tx_builder.build()?;
454 let sigs = tx.create_sigs(&[*secret])?;
455 tx.signatures = vec![sigs];
456
457 Ok(tx)
458}