1use std::{collections::HashMap, str::FromStr};
20
21use rand::rngs::OsRng;
22use sled_overlay::sled::IVec;
23use tinyjson::JsonValue;
24use tracing::info;
25
26use darkfi::{
27 blockchain::{BlockInfo, Header, HeaderHash},
28 rpc::jsonrpc::JsonSubscriber,
29 tx::{ContractCallLeaf, Transaction, TransactionBuilder},
30 util::{
31 encoding::base64,
32 time::{NanoTimestamp, Timestamp},
33 },
34 validator::{
35 consensus::Fork,
36 pow::{RANDOMX_KEY_CHANGE_DELAY, RANDOMX_KEY_CHANGING_HEIGHT},
37 verification::apply_producer_transaction,
38 ValidatorPtr,
39 },
40 zk::{empty_witnesses, ProvingKey, ZkCircuit},
41 zkas::ZkBinary,
42 Error, Result,
43};
44use darkfi_money_contract::{
45 client::pow_reward_v1::PoWRewardCallBuilder, MoneyFunction, MONEY_CONTRACT_ZKAS_MINT_NS_V1,
46};
47use darkfi_sdk::{
48 crypto::{
49 keypair::{Address, Keypair, Network, SecretKey},
50 pasta_prelude::PrimeField,
51 FuncId, MerkleTree, MONEY_CONTRACT_ID,
52 },
53 pasta::pallas,
54 ContractCall,
55};
56use darkfi_serial::{deserialize_async, Encodable};
57
58use crate::error::RpcError;
59
60#[derive(Debug, Clone)]
62pub struct MinerRewardsRecipientConfig {
63 pub recipient: Address,
65 pub spend_hook: Option<FuncId>,
67 pub user_data: Option<pallas::Base>,
70}
71
72impl MinerRewardsRecipientConfig {
73 pub fn new(
74 recipient: Address,
75 spend_hook: Option<FuncId>,
76 user_data: Option<pallas::Base>,
77 ) -> Self {
78 Self { recipient, spend_hook, user_data }
79 }
80
81 pub async fn from_str(network: &Network, address: &str) -> std::result::Result<Self, RpcError> {
86 if let Ok(recipient) = Address::from_str(address) {
88 if recipient.network() != *network {
89 return Err(RpcError::MinerInvalidRecipientPrefix)
90 }
91 return Ok(Self { recipient, spend_hook: None, user_data: None })
92 }
93
94 let Some(address_bytes) = base64::decode(address) else {
97 return Err(RpcError::MinerInvalidWalletConfig)
98 };
99 let Ok((recipient, spend_hook, user_data)) =
100 deserialize_async::<(String, Option<String>, Option<String>)>(&address_bytes).await
101 else {
102 return Err(RpcError::MinerInvalidWalletConfig)
103 };
104 let Ok(recipient) = Address::from_str(&recipient) else {
105 return Err(RpcError::MinerInvalidRecipient)
106 };
107 if recipient.network() != *network {
108 return Err(RpcError::MinerInvalidRecipientPrefix)
109 }
110 let spend_hook = match spend_hook {
111 Some(s) => match FuncId::from_str(&s) {
112 Ok(s) => Some(s),
113 Err(_) => return Err(RpcError::MinerInvalidSpendHook),
114 },
115 None => None,
116 };
117 let user_data: Option<pallas::Base> = match user_data {
118 Some(u) => {
119 let Ok(bytes) = bs58::decode(&u).into_vec() else {
120 return Err(RpcError::MinerInvalidUserData)
121 };
122 let bytes: [u8; 32] = match bytes.try_into() {
123 Ok(b) => b,
124 Err(_) => return Err(RpcError::MinerInvalidUserData),
125 };
126 match pallas::Base::from_repr(bytes).into() {
127 Some(v) => Some(v),
128 None => return Err(RpcError::MinerInvalidUserData),
129 }
130 }
131 None => None,
132 };
133
134 Ok(Self { recipient, spend_hook, user_data })
135 }
136}
137
138#[derive(Debug, Clone)]
140pub struct BlockTemplate {
141 pub block: BlockInfo,
143 pub new_trees: Vec<IVec>,
145 pub randomx_keys: (HeaderHash, Option<HeaderHash>),
147 pub target: Vec<u8>,
149 pub difficulty: f64,
151 pub secret: SecretKey,
153 pub submitted: bool,
155}
156
157impl BlockTemplate {
158 fn new(
159 block: BlockInfo,
160 new_trees: Vec<IVec>,
161 randomx_keys: (HeaderHash, Option<HeaderHash>),
162 target: Vec<u8>,
163 difficulty: f64,
164 secret: SecretKey,
165 ) -> Self {
166 Self { block, new_trees, randomx_keys, target, difficulty, secret, submitted: false }
167 }
168
169 pub fn job_notification(&self) -> (String, JsonValue) {
170 let block_hash = hex::encode(self.block.header.hash().inner()).to_string();
171 let mut job = HashMap::from([
172 (
173 "blob".to_string(),
174 JsonValue::from(hex::encode(self.block.header.to_block_hashing_blob()).to_string()),
175 ),
176 ("job_id".to_string(), JsonValue::from(block_hash.clone())),
177 ("height".to_string(), JsonValue::from(self.block.header.height as f64)),
178 ("target".to_string(), JsonValue::from(hex::encode(&self.target))),
179 ("algo".to_string(), JsonValue::from(String::from("rx/0"))),
180 (
181 "seed_hash".to_string(),
182 JsonValue::from(hex::encode(self.randomx_keys.0.inner()).to_string()),
183 ),
184 ]);
185 if let Some(next_randomx_key) = self.randomx_keys.1 {
186 job.insert(
187 "next_seed_hash".to_string(),
188 JsonValue::from(hex::encode(next_randomx_key.inner()).to_string()),
189 );
190 }
191 (block_hash, JsonValue::from(job))
192 }
193}
194
195#[derive(Debug, Clone)]
197pub struct MinerClient {
198 pub wallet: String,
200 pub config: MinerRewardsRecipientConfig,
202 pub job: String,
204 pub publisher: JsonSubscriber,
206}
207
208impl MinerClient {
209 pub fn new(wallet: &str, config: &MinerRewardsRecipientConfig, job: &str) -> (String, Self) {
210 let mut hasher = blake3::Hasher::new();
211 hasher.update(wallet.as_bytes());
212 hasher.update(&NanoTimestamp::current_time().inner().to_le_bytes());
213 let client_id = hex::encode(hasher.finalize().as_bytes()).to_string();
214 let publisher = JsonSubscriber::new("job");
215 (
216 client_id,
217 Self {
218 wallet: String::from(wallet),
219 config: config.clone(),
220 job: job.to_owned(),
221 publisher,
222 },
223 )
224 }
225}
226
227pub struct PowRewardV1Zk {
229 pub zkbin: ZkBinary,
230 pub provingkey: ProvingKey,
231}
232
233impl PowRewardV1Zk {
234 pub async fn new(validator: &ValidatorPtr) -> Result<Self> {
235 info!(
236 target: "darkfid::registry::model::PowRewardV1Zk::new",
237 "Generating PowRewardV1 ZkCircuit and ProvingKey...",
238 );
239
240 let validator = validator.read().await;
241 let (zkbin, _) = validator.blockchain.contracts.get_zkas(
242 &validator.blockchain.sled_db,
243 &MONEY_CONTRACT_ID,
244 MONEY_CONTRACT_ZKAS_MINT_NS_V1,
245 )?;
246
247 let circuit = ZkCircuit::new(empty_witnesses(&zkbin)?, &zkbin);
248 let provingkey = ProvingKey::build(zkbin.k, &circuit);
249
250 Ok(Self { zkbin, provingkey })
251 }
252}
253
254pub async fn generate_next_block_template(
257 extended_fork: &mut Fork,
258 recipient_config: &MinerRewardsRecipientConfig,
259 zkbin: &ZkBinary,
260 pk: &ProvingKey,
261 verify_fees: bool,
262) -> Result<BlockTemplate> {
263 let last_proposal = extended_fork.last_proposal()?;
265
266 let next_block_height = last_proposal.block.header.height + 1;
268
269 let randomx_keys = if next_block_height > RANDOMX_KEY_CHANGING_HEIGHT &&
271 next_block_height % RANDOMX_KEY_CHANGING_HEIGHT == RANDOMX_KEY_CHANGE_DELAY
272 {
273 (
274 extended_fork
275 .module
276 .darkfi_rx_keys
277 .1
278 .ok_or_else(|| Error::ParseFailed("darkfi_rx_keys.1 unwrap() error"))?,
279 None,
280 )
281 } else {
282 extended_fork.module.darkfi_rx_keys
283 };
284
285 let (target, difficulty) = extended_fork.module.next_mine_target_and_difficulty()?;
287
288 let target_bytes = target.to_bytes_le();
290 let mut padded = [0u8; 32];
291 let len = target_bytes.len().min(32);
292 padded[..len].copy_from_slice(&target_bytes[..len]);
293 let target = padded[24..32].to_vec();
294
295 let difficulty = difficulty.to_string().parse()?;
297
298 let (mut txs, _, fees) = extended_fork.unproposed_txs(next_block_height, verify_fees).await?;
300
301 let block_signing_keypair = Keypair::random(&mut OsRng);
306
307 let tx = generate_transaction(
309 next_block_height,
310 fees,
311 &block_signing_keypair,
312 recipient_config,
313 zkbin,
314 pk,
315 )?;
316
317 let _ = apply_producer_transaction(
319 &extended_fork.overlay,
320 next_block_height,
321 extended_fork.module.target,
322 &tx,
323 &mut MerkleTree::new(1),
324 )
325 .await?;
326 txs.push(tx);
327
328 let diff =
330 extended_fork.overlay.lock().unwrap().overlay.lock().unwrap().diff(&extended_fork.diffs)?;
331 let state_root =
332 extended_fork.overlay.lock().unwrap().contracts.update_state_monotree(&diff)?;
333
334 let mut header =
336 Header::new(last_proposal.hash, next_block_height, 0, Timestamp::current_time());
337 header.state_root = state_root;
338
339 let mut next_block = BlockInfo::new_empty(header);
341
342 next_block.append_txs(txs);
344
345 Ok(BlockTemplate::new(
346 next_block,
347 diff.new_trees(),
348 randomx_keys,
349 target,
350 difficulty,
351 block_signing_keypair.secret,
352 ))
353}
354
355fn generate_transaction(
357 block_height: u32,
358 fees: u64,
359 block_signing_keypair: &Keypair,
360 recipient_config: &MinerRewardsRecipientConfig,
361 zkbin: &ZkBinary,
362 pk: &ProvingKey,
363) -> Result<Transaction> {
364 let debris = PoWRewardCallBuilder {
366 signature_keypair: *block_signing_keypair,
367 block_height,
368 fees,
369 recipient: Some(*recipient_config.recipient.public_key()),
370 spend_hook: recipient_config.spend_hook,
371 user_data: recipient_config.user_data,
372 mint_zkbin: zkbin.clone(),
373 mint_pk: pk.clone(),
374 }
375 .build()?;
376
377 let mut data = vec![MoneyFunction::PoWRewardV1 as u8];
379 debris.params.encode(&mut data)?;
380 let call = ContractCall { contract_id: *MONEY_CONTRACT_ID, data };
381 let mut tx_builder =
382 TransactionBuilder::new(ContractCallLeaf { call, proofs: debris.proofs }, vec![])?;
383 let mut tx = tx_builder.build()?;
384 let sigs = tx.create_sigs(&[block_signing_keypair.secret])?;
385 tx.signatures = vec![sigs];
386
387 Ok(tx)
388}