1/* This file is part of DarkFi (https://dark.fi)
2 *
3 * Copyright (C) 2020-2025 Dyne.org foundation
4 *
5 * This program is free software: you can redistribute it and/or modify
6 * it under the terms of the GNU Affero General Public License as
7 * published by the Free Software Foundation, either version 3 of the
8 * License, or (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU Affero General Public License for more details.
14 *
15 * You should have received a copy of the GNU Affero General Public License
16 * along with this program. If not, see <https://www.gnu.org/licenses/>.
17 */
1819use darkfi::{
20 blockchain::{BlockInfo, Header},
21 tx::{ContractCallLeaf, Transaction, TransactionBuilder},
22Result,
23};
24use darkfi_money_contract::{
25 client::{pow_reward_v1::PoWRewardCallBuilder, MoneyNote, OwnCoin},
26 model::MoneyPoWRewardParamsV1,
27 MoneyFunction, MONEY_CONTRACT_ZKAS_MINT_NS_V1,
28};
29use darkfi_sdk::{
30 crypto::{contract_id::MONEY_CONTRACT_ID, MerkleNode},
31 ContractCall,
32};
33use darkfi_serial::AsyncEncodable;
34use log::info;
3536use super::{Holder, TestHarness};
3738impl TestHarness {
39/// Create a `Money::PoWReward` transaction for a given [`Holder`].
40 ///
41 /// Optionally takes a specific reward recipient and a nonstandard reward value.
42 /// Returns the created [`Transaction`] and [`MoneyPoWRewardParamsV1`].
43async fn pow_reward(
44&mut self,
45 holder: &Holder,
46 recipient: Option<&Holder>,
47 reward: Option<u64>,
48 fees: Option<u64>,
49 ) -> Result<(Transaction, MoneyPoWRewardParamsV1)> {
50let wallet = self.holders.get(holder).unwrap();
5152let (mint_pk, mint_zkbin) = self.proving_keys.get(MONEY_CONTRACT_ZKAS_MINT_NS_V1).unwrap();
5354// Reference the last block in the holder's blockchain
55let last_block = wallet.validator.blockchain.last_block()?;
5657// If there's a set reward recipient, use it, otherwise reward the holder
58let recipient = if let Some(holder) = recipient {
59Some(self.holders.get(holder).unwrap().keypair.public)
60 } else {
61None
62};
6364// If there's fees paid, use them, otherwise set to zero
65let fees = fees.unwrap_or_default();
6667// Build the transaction
68let builder = PoWRewardCallBuilder {
69 signature_public: wallet.keypair.public,
70 block_height: last_block.header.height + 1,
71 fees,
72 recipient,
73 spend_hook: None,
74 user_data: None,
75 mint_zkbin: mint_zkbin.clone(),
76 mint_pk: mint_pk.clone(),
77 };
7879let debris = match reward {
80Some(value) => builder.build_with_custom_reward(value)?,
81None => builder.build()?,
82 };
8384// Encode the transaction
85let mut data = vec![MoneyFunction::PoWRewardV1 as u8];
86 debris.params.encode_async(&mut data).await?;
87let call = ContractCall { contract_id: *MONEY_CONTRACT_ID, data };
88let mut tx_builder =
89 TransactionBuilder::new(ContractCallLeaf { call, proofs: debris.proofs }, vec![])?;
90let mut tx = tx_builder.build()?;
91let sigs = tx.create_sigs(&[wallet.keypair.secret])?;
92 tx.signatures = vec![sigs];
9394Ok((tx, debris.params))
95 }
9697/// Generate and add an empty block to the given [`Holder`]s blockchains.
98 /// The `miner` holder will produce the block and receive the reward.
99 ///
100 /// Returns any found [`OwnCoin`]s.
101pub async fn generate_block(
102&mut self,
103 miner: &Holder,
104 holders: &[Holder],
105 ) -> Result<Vec<OwnCoin>> {
106// Build the POW reward transaction
107info!("Building PoWReward transaction for {:?}", miner);
108let (tx, params) = self.pow_reward(miner, None, None, None).await?;
109110// Fetch the last block in the blockchain
111let wallet = self.holders.get(miner).unwrap();
112let previous = wallet.validator.blockchain.last_block()?;
113114// We increment timestamp so we don't have to use sleep
115let timestamp = previous.header.timestamp.checked_add(1.into())?;
116117// Generate block header
118let header = Header::new(
119 previous.hash(),
120 previous.header.height + 1,
121 timestamp,
122 previous.header.nonce,
123 );
124125// Generate the block
126let mut block = BlockInfo::new_empty(header);
127128// Add producer transaction to the block
129block.append_txs(vec![tx]);
130131// Attach signature
132block.sign(&wallet.keypair.secret);
133134// For all holders, append the block
135let mut found_owncoins = vec![];
136for holder in holders {
137let wallet = self.holders.get_mut(holder).unwrap();
138 wallet.validator.add_test_blocks(&[block.clone()]).await?;
139 wallet.money_merkle_tree.append(MerkleNode::from(params.output.coin.inner()));
140141// Attempt to decrypt the note to see if this is a coin for the holder
142let Ok(note) = params.output.note.decrypt::<MoneyNote>(&wallet.keypair.secret) else {
143continue
144};
145146let owncoin = OwnCoin {
147 coin: params.output.coin,
148 note: note.clone(),
149 secret: wallet.keypair.secret,
150 leaf_position: wallet.money_merkle_tree.mark().unwrap(),
151 };
152153 wallet.unspent_money_coins.push(owncoin.clone());
154 found_owncoins.push(owncoin);
155 }
156157Ok(found_owncoins)
158 }
159}