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