1use std::{collections::HashMap, str::FromStr};
20
21use darkfi::{
22 blockchain::{BlockInfo, Header, HeaderHash},
23 rpc::jsonrpc::{ErrorCode, ErrorCode::InvalidParams, JsonError, JsonResponse, JsonResult},
24 tx::{ContractCallLeaf, Transaction, TransactionBuilder},
25 util::{encoding::base64, time::Timestamp},
26 validator::{
27 consensus::{Fork, Proposal},
28 pow::{RANDOMX_KEY_CHANGE_DELAY, RANDOMX_KEY_CHANGING_HEIGHT},
29 verification::apply_producer_transaction,
30 },
31 zk::ProvingKey,
32 zkas::ZkBinary,
33 Error, Result,
34};
35use darkfi_money_contract::{client::pow_reward_v1::PoWRewardCallBuilder, MoneyFunction};
36use darkfi_sdk::{
37 crypto::{
38 pasta_prelude::PrimeField, FuncId, Keypair, MerkleTree, PublicKey, SecretKey,
39 MONEY_CONTRACT_ID,
40 },
41 pasta::pallas,
42 ContractCall,
43};
44use darkfi_serial::{serialize_async, Encodable};
45use num_bigint::BigUint;
46use rand::rngs::OsRng;
47use tinyjson::JsonValue;
48use tracing::{error, info};
49
50use crate::{proto::ProposalMessage, server_error, DarkfiNode, RpcError};
51
52pub struct MinerRewardsRecipientConfig {
54 pub recipient: PublicKey,
56 pub spend_hook: Option<FuncId>,
58 pub user_data: Option<pallas::Base>,
61}
62
63pub struct BlockTemplate {
65 pub block: BlockInfo,
67 randomx_key: String,
69 next_randomx_key: String,
71 target: String,
73 secret: SecretKey,
75}
76
77impl DarkfiNode {
78 pub async fn miner_get_current_randomx_keys(&self, id: u16, params: JsonValue) -> JsonResult {
94 let Some(params) = params.get::<Vec<JsonValue>>() else {
96 return JsonError::new(InvalidParams, None, id).into()
97 };
98 if !params.is_empty() {
99 return JsonError::new(InvalidParams, None, id).into()
100 }
101
102 let (randomx_key, next_randomx_key) = match self.validator.current_randomx_keys().await {
104 Ok(keys) => keys,
105 Err(e) => {
106 error!(
107 target: "darkfid::rpc::miner_get_current_randomx_keys",
108 "[RPC] Retrieving current RandomX keys failed: {e}",
109 );
110 return JsonError::new(ErrorCode::InternalError, None, id).into()
111 }
112 };
113
114 let response = JsonValue::Array(vec![
116 JsonValue::String(base64::encode(&serialize_async(&randomx_key).await)),
117 JsonValue::String(base64::encode(&serialize_async(&next_randomx_key).await)),
118 ]);
119
120 JsonResponse::new(response, id).into()
121 }
122
123 pub async fn miner_get_header(&self, id: u16, params: JsonValue) -> JsonResult {
144 if !*self.validator.synced.read().await {
146 return server_error(RpcError::NotSynced, id, None)
147 }
148
149 let Some(params) = params.get::<HashMap<String, JsonValue>>() else {
151 return JsonError::new(InvalidParams, None, id).into()
152 };
153 if params.len() < 2 || params.len() > 4 {
154 return JsonError::new(InvalidParams, None, id).into()
155 }
156
157 let Some(header_hash) = params.get("header") else {
159 return server_error(RpcError::MinerMissingHeader, id, None)
160 };
161 let Some(header_hash) = header_hash.get::<String>() else {
162 return server_error(RpcError::MinerInvalidHeader, id, None)
163 };
164 let Ok(header_hash) = HeaderHash::from_str(header_hash) else {
165 return server_error(RpcError::MinerInvalidHeader, id, None)
166 };
167
168 let Some(recipient) = params.get("recipient") else {
170 return server_error(RpcError::MinerMissingRecipient, id, None)
171 };
172 let Some(recipient) = recipient.get::<String>() else {
173 return server_error(RpcError::MinerInvalidRecipient, id, None)
174 };
175 let Ok(recipient) = PublicKey::from_str(recipient) else {
176 return server_error(RpcError::MinerInvalidRecipient, id, None)
177 };
178
179 let spend_hook = match params.get("spend_hook") {
181 Some(spend_hook) => {
182 let Some(spend_hook) = spend_hook.get::<String>() else {
183 return server_error(RpcError::MinerInvalidSpendHook, id, None)
184 };
185 let Ok(spend_hook) = FuncId::from_str(spend_hook) else {
186 return server_error(RpcError::MinerInvalidSpendHook, id, None)
187 };
188 Some(spend_hook)
189 }
190 None => None,
191 };
192
193 let user_data: Option<pallas::Base> = match params.get("user_data") {
195 Some(user_data) => {
196 let Some(user_data) = user_data.get::<String>() else {
197 return server_error(RpcError::MinerInvalidUserData, id, None)
198 };
199 let Ok(bytes) = bs58::decode(&user_data).into_vec() else {
200 return server_error(RpcError::MinerInvalidUserData, id, None)
201 };
202 let bytes: [u8; 32] = match bytes.try_into() {
203 Ok(b) => b,
204 Err(_) => return server_error(RpcError::MinerInvalidUserData, id, None),
205 };
206 let Some(user_data) = pallas::Base::from_repr(bytes).into() else {
207 return server_error(RpcError::MinerInvalidUserData, id, None)
208 };
209 Some(user_data)
210 }
211 None => None,
212 };
213
214 let address_bytes = serialize_async(&(recipient, spend_hook, user_data)).await;
226 let mut blocktemplates = self.blocktemplates.lock().await;
227 let mut extended_fork = match self.validator.best_current_fork().await {
228 Ok(f) => f,
229 Err(e) => {
230 error!(
231 target: "darkfid::rpc::miner_get_header",
232 "[RPC] Finding best fork index failed: {e}",
233 );
234 return JsonError::new(ErrorCode::InternalError, None, id).into()
235 }
236 };
237 if let Some(blocktemplate) = blocktemplates.get(&address_bytes) {
238 let last_proposal = match extended_fork.last_proposal() {
239 Ok(p) => p,
240 Err(e) => {
241 error!(
242 target: "darkfid::rpc::miner_get_header",
243 "[RPC] Retrieving best fork last proposal failed: {e}",
244 );
245 return JsonError::new(ErrorCode::InternalError, None, id).into()
246 }
247 };
248 if last_proposal.hash == blocktemplate.block.header.previous {
249 return if blocktemplate.block.header.hash() != header_hash {
250 JsonResponse::new(
251 JsonValue::Array(vec![
252 JsonValue::String(blocktemplate.randomx_key.clone()),
253 JsonValue::String(blocktemplate.next_randomx_key.clone()),
254 JsonValue::String(blocktemplate.target.clone()),
255 JsonValue::String(base64::encode(
256 &serialize_async(&blocktemplate.block.header).await,
257 )),
258 ]),
259 id,
260 )
261 .into()
262 } else {
263 JsonResponse::new(JsonValue::Array(vec![]), id).into()
264 }
265 }
266 blocktemplates.remove(&address_bytes);
267 }
268
269 let recipient_str = format!("{recipient}");
273 let spend_hook_str = match spend_hook {
274 Some(spend_hook) => format!("{spend_hook}"),
275 None => String::from("-"),
276 };
277 let user_data_str = match user_data {
278 Some(user_data) => bs58::encode(user_data.to_repr()).into_string(),
279 None => String::from("-"),
280 };
281 let recipient_config = MinerRewardsRecipientConfig { recipient, spend_hook, user_data };
282
283 let (target, block, secret) = match generate_next_block(
285 &mut extended_fork,
286 &recipient_config,
287 &self.powrewardv1_zk.zkbin,
288 &self.powrewardv1_zk.provingkey,
289 self.validator.consensus.module.read().await.target,
290 self.validator.verify_fees,
291 )
292 .await
293 {
294 Ok(v) => v,
295 Err(e) => {
296 error!(
297 target: "darkfid::rpc::miner_get_header",
298 "[RPC] Failed to generate next blocktemplate: {e}",
299 );
300 return JsonError::new(ErrorCode::InternalError, None, id).into()
301 }
302 };
303
304 let randomx_key = if block.header.height > RANDOMX_KEY_CHANGING_HEIGHT &&
308 block.header.height % RANDOMX_KEY_CHANGING_HEIGHT == RANDOMX_KEY_CHANGE_DELAY
309 {
310 base64::encode(&serialize_async(&extended_fork.module.darkfi_rx_keys.1).await)
311 } else {
312 base64::encode(&serialize_async(&extended_fork.module.darkfi_rx_keys.0).await)
313 };
314
315 let next_randomx_key =
318 base64::encode(&serialize_async(&extended_fork.module.darkfi_rx_keys.1).await);
319
320 let target = base64::encode(&target.to_bytes_le());
322
323 let blocktemplate = BlockTemplate {
325 block,
326 randomx_key: randomx_key.clone(),
327 next_randomx_key: next_randomx_key.clone(),
328 target: target.clone(),
329 secret,
330 };
331
332 let header_hash = blocktemplate.block.header.hash().to_string();
335 let header = base64::encode(&serialize_async(&blocktemplate.block.header).await);
336 blocktemplates.insert(address_bytes, blocktemplate);
337 info!(
338 target: "darkfid::rpc::miner_get_header",
339 "[RPC] Created new blocktemplate: address={recipient_str}, spend_hook={spend_hook_str}, user_data={user_data_str}, hash={header_hash}"
340 );
341
342 let response = JsonValue::Array(vec![
343 JsonValue::String(randomx_key),
344 JsonValue::String(next_randomx_key),
345 JsonValue::String(target),
346 JsonValue::String(header),
347 ]);
348
349 JsonResponse::new(response, id).into()
350 }
351
352 pub async fn miner_submit_solution(&self, id: u16, params: JsonValue) -> JsonResult {
368 if !*self.validator.synced.read().await {
370 return server_error(RpcError::NotSynced, id, None)
371 }
372
373 let Some(params) = params.get::<HashMap<String, JsonValue>>() else {
375 return JsonError::new(InvalidParams, None, id).into()
376 };
377 if params.len() < 2 || params.len() > 4 {
378 return JsonError::new(InvalidParams, None, id).into()
379 }
380
381 let Some(recipient) = params.get("recipient") else {
383 return server_error(RpcError::MinerMissingRecipient, id, None)
384 };
385 let Some(recipient) = recipient.get::<String>() else {
386 return server_error(RpcError::MinerInvalidRecipient, id, None)
387 };
388 let Ok(recipient) = PublicKey::from_str(recipient) else {
389 return server_error(RpcError::MinerInvalidRecipient, id, None)
390 };
391
392 let spend_hook = match params.get("spend_hook") {
394 Some(spend_hook) => {
395 let Some(spend_hook) = spend_hook.get::<String>() else {
396 return server_error(RpcError::MinerInvalidSpendHook, id, None)
397 };
398 let Ok(spend_hook) = FuncId::from_str(spend_hook) else {
399 return server_error(RpcError::MinerInvalidSpendHook, id, None)
400 };
401 Some(spend_hook)
402 }
403 None => None,
404 };
405
406 let user_data: Option<pallas::Base> = match params.get("user_data") {
408 Some(user_data) => {
409 let Some(user_data) = user_data.get::<String>() else {
410 return server_error(RpcError::MinerInvalidUserData, id, None)
411 };
412 let Ok(bytes) = bs58::decode(&user_data).into_vec() else {
413 return server_error(RpcError::MinerInvalidUserData, id, None)
414 };
415 let bytes: [u8; 32] = match bytes.try_into() {
416 Ok(b) => b,
417 Err(_) => return server_error(RpcError::MinerInvalidUserData, id, None),
418 };
419 let Some(user_data) = pallas::Base::from_repr(bytes).into() else {
420 return server_error(RpcError::MinerInvalidUserData, id, None)
421 };
422 Some(user_data)
423 }
424 None => None,
425 };
426
427 let Some(nonce) = params.get("nonce") else {
429 return server_error(RpcError::MinerMissingNonce, id, None)
430 };
431 let Some(nonce) = nonce.get::<f64>() else {
432 return server_error(RpcError::MinerInvalidNonce, id, None)
433 };
434
435 let address_bytes = serialize_async(&(recipient, spend_hook, user_data)).await;
437 let mut blocktemplates = self.blocktemplates.lock().await;
438 let Some(blocktemplate) = blocktemplates.get(&address_bytes) else {
439 return server_error(RpcError::MinerUnknownJob, id, None)
440 };
441
442 info!(
443 target: "darkfid::rpc::miner_submit_solution",
444 "[RPC] Got solution submission for block template: {}", blocktemplate.block.header.hash(),
445 );
446
447 let mut block = blocktemplate.block.clone();
449 block.header.nonce = *nonce as u64;
450 block.sign(&blocktemplate.secret);
451 info!(
452 target: "darkfid::rpc::miner_submit_solution",
453 "[RPC] Mined block header hash: {}", blocktemplate.block.header.hash(),
454 );
455
456 blocktemplates.remove(&address_bytes);
460
461 info!(
463 target: "darkfid::rpc::miner_submit_solution",
464 "[RPC] Proposing new block to network",
465 );
466 let proposal = Proposal::new(block);
467 if let Err(e) = self.validator.append_proposal(&proposal).await {
468 error!(
469 target: "darkfid::rpc::miner_submit_solution",
470 "[RPC] Error proposing new block: {e}",
471 );
472 return JsonResponse::new(JsonValue::String(String::from("rejected")), id).into()
473 }
474
475 let proposals_sub = self.subscribers.get("proposals").unwrap();
476 let enc_prop = JsonValue::String(base64::encode(&serialize_async(&proposal).await));
477 proposals_sub.notify(vec![enc_prop].into()).await;
478
479 info!(
480 target: "darkfid::rpc::miner_submit_solution",
481 "[RPC] Broadcasting new block to network",
482 );
483 let message = ProposalMessage(proposal);
484 self.p2p_handler.p2p.broadcast(&message).await;
485
486 JsonResponse::new(JsonValue::String(String::from("accepted")), id).into()
487 }
488}
489
490pub async fn generate_next_block(
492 extended_fork: &mut Fork,
493 recipient_config: &MinerRewardsRecipientConfig,
494 zkbin: &ZkBinary,
495 pk: &ProvingKey,
496 block_target: u32,
497 verify_fees: bool,
498) -> Result<(BigUint, BlockInfo, SecretKey)> {
499 let last_proposal = extended_fork.last_proposal()?;
501
502 let next_block_height = last_proposal.block.header.height + 1;
504
505 let (mut txs, _, fees, overlay) = extended_fork
507 .unproposed_txs(&extended_fork.blockchain, next_block_height, block_target, verify_fees)
508 .await?;
509
510 let block_signing_keypair = Keypair::random(&mut OsRng);
515
516 let tx = generate_transaction(
518 next_block_height,
519 fees,
520 &block_signing_keypair,
521 recipient_config,
522 zkbin,
523 pk,
524 )?;
525
526 let _ = apply_producer_transaction(
528 &overlay,
529 next_block_height,
530 block_target,
531 &tx,
532 &mut MerkleTree::new(1),
533 )
534 .await?;
535 txs.push(tx);
536
537 overlay.lock().unwrap().contracts.update_state_monotree(&mut extended_fork.state_monotree)?;
539 let Some(state_root) = extended_fork.state_monotree.get_headroot()? else {
540 return Err(Error::ContractsStatesRootNotFoundError);
541 };
542
543 overlay.lock().unwrap().overlay.lock().unwrap().purge_new_trees()?;
545
546 let mut header =
548 Header::new(last_proposal.hash, next_block_height, Timestamp::current_time(), 0);
549 header.state_root = state_root;
550
551 let mut next_block = BlockInfo::new_empty(header);
553
554 next_block.append_txs(txs);
556
557 let target = extended_fork.module.next_mine_target()?;
559
560 Ok((target, next_block, block_signing_keypair.secret))
561}
562
563fn generate_transaction(
565 block_height: u32,
566 fees: u64,
567 block_signing_keypair: &Keypair,
568 recipient_config: &MinerRewardsRecipientConfig,
569 zkbin: &ZkBinary,
570 pk: &ProvingKey,
571) -> Result<Transaction> {
572 let debris = PoWRewardCallBuilder {
574 signature_keypair: *block_signing_keypair,
575 block_height,
576 fees,
577 recipient: Some(recipient_config.recipient),
578 spend_hook: recipient_config.spend_hook,
579 user_data: recipient_config.user_data,
580 mint_zkbin: zkbin.clone(),
581 mint_pk: pk.clone(),
582 }
583 .build()?;
584
585 let mut data = vec![MoneyFunction::PoWRewardV1 as u8];
587 debris.params.encode(&mut data)?;
588 let call = ContractCall { contract_id: *MONEY_CONTRACT_ID, data };
589 let mut tx_builder =
590 TransactionBuilder::new(ContractCallLeaf { call, proofs: debris.proofs }, vec![])?;
591 let mut tx = tx_builder.build()?;
592 let sigs = tx.create_sigs(&[block_signing_keypair.secret])?;
593 tx.signatures = vec![sigs];
594
595 Ok(tx)
596}