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 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 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 (zkbin, _) = validator.blockchain.contracts.get_zkas(
241 &validator.blockchain.sled_db,
242 &MONEY_CONTRACT_ID,
243 MONEY_CONTRACT_ZKAS_MINT_NS_V1,
244 )?;
245
246 let circuit = ZkCircuit::new(empty_witnesses(&zkbin)?, &zkbin);
247 let provingkey = ProvingKey::build(zkbin.k, &circuit);
248
249 Ok(Self { zkbin, provingkey })
250 }
251}
252
253pub async fn generate_next_block_template(
256 extended_fork: &mut Fork,
257 recipient_config: &MinerRewardsRecipientConfig,
258 zkbin: &ZkBinary,
259 pk: &ProvingKey,
260 verify_fees: bool,
261) -> Result<BlockTemplate> {
262 let last_proposal = extended_fork.last_proposal()?;
264
265 let next_block_height = last_proposal.block.header.height + 1;
267
268 let randomx_keys = if next_block_height > RANDOMX_KEY_CHANGING_HEIGHT &&
270 next_block_height % RANDOMX_KEY_CHANGING_HEIGHT == RANDOMX_KEY_CHANGE_DELAY
271 {
272 (extended_fork.module.darkfi_rx_keys.1.unwrap(), None)
274 } else {
275 extended_fork.module.darkfi_rx_keys
276 };
277
278 let (target, difficulty) = extended_fork.module.next_mine_target_and_difficulty()?;
280
281 let target_bytes = target.to_bytes_le();
283 let mut padded = [0u8; 32];
284 let len = target_bytes.len().min(32);
285 padded[..len].copy_from_slice(&target_bytes[..len]);
286 let target = padded[24..32].to_vec();
287
288 let difficulty = difficulty.to_string().parse()?;
290
291 let (mut txs, _, fees) = extended_fork.unproposed_txs(next_block_height, verify_fees).await?;
293
294 let block_signing_keypair = Keypair::random(&mut OsRng);
299
300 let tx = generate_transaction(
302 next_block_height,
303 fees,
304 &block_signing_keypair,
305 recipient_config,
306 zkbin,
307 pk,
308 )?;
309
310 let _ = apply_producer_transaction(
312 &extended_fork.overlay,
313 next_block_height,
314 extended_fork.module.target,
315 &tx,
316 &mut MerkleTree::new(1),
317 )
318 .await?;
319 txs.push(tx);
320
321 let diff =
323 extended_fork.overlay.lock().unwrap().overlay.lock().unwrap().diff(&extended_fork.diffs)?;
324 let state_root =
325 extended_fork.overlay.lock().unwrap().contracts.update_state_monotree(&diff)?;
326
327 let mut header =
329 Header::new(last_proposal.hash, next_block_height, 0, Timestamp::current_time());
330 header.state_root = state_root;
331
332 let mut next_block = BlockInfo::new_empty(header);
334
335 next_block.append_txs(txs);
337
338 Ok(BlockTemplate::new(
339 next_block,
340 diff.new_trees(),
341 randomx_keys,
342 target,
343 difficulty,
344 block_signing_keypair.secret,
345 ))
346}
347
348fn generate_transaction(
350 block_height: u32,
351 fees: u64,
352 block_signing_keypair: &Keypair,
353 recipient_config: &MinerRewardsRecipientConfig,
354 zkbin: &ZkBinary,
355 pk: &ProvingKey,
356) -> Result<Transaction> {
357 let debris = PoWRewardCallBuilder {
359 signature_keypair: *block_signing_keypair,
360 block_height,
361 fees,
362 recipient: Some(*recipient_config.recipient.public_key()),
363 spend_hook: recipient_config.spend_hook,
364 user_data: recipient_config.user_data,
365 mint_zkbin: zkbin.clone(),
366 mint_pk: pk.clone(),
367 }
368 .build()?;
369
370 let mut data = vec![MoneyFunction::PoWRewardV1 as u8];
372 debris.params.encode(&mut data)?;
373 let call = ContractCall { contract_id: *MONEY_CONTRACT_ID, data };
374 let mut tx_builder =
375 TransactionBuilder::new(ContractCallLeaf { call, proofs: debris.proofs }, vec![])?;
376 let mut tx = tx_builder.build()?;
377 let sigs = tx.create_sigs(&[block_signing_keypair.secret])?;
378 tx.signatures = vec![sigs];
379
380 Ok(tx)
381}