darkfi_contract_test_harness/
money_pow_reward.rs

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 */
18
19use darkfi::{
20    blockchain::{BlockInfo, Header},
21    tx::{ContractCallLeaf, Transaction, TransactionBuilder},
22    Result,
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;
35
36use super::{Holder, TestHarness};
37
38impl 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`].
43    async 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)> {
50        let wallet = self.holders.get(holder).unwrap();
51
52        let (mint_pk, mint_zkbin) = self.proving_keys.get(MONEY_CONTRACT_ZKAS_MINT_NS_V1).unwrap();
53
54        // Reference the last block in the holder's blockchain
55        let last_block = wallet.validator.blockchain.last_block()?;
56
57        // If there's a set reward recipient, use it, otherwise reward the holder
58        let recipient = if let Some(holder) = recipient {
59            Some(self.holders.get(holder).unwrap().keypair.public)
60        } else {
61            None
62        };
63
64        // If there's fees paid, use them, otherwise set to zero
65        let fees = fees.unwrap_or_default();
66
67        // Build the transaction
68        let 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        };
78
79        let debris = match reward {
80            Some(value) => builder.build_with_custom_reward(value)?,
81            None => builder.build()?,
82        };
83
84        // Encode the transaction
85        let mut data = vec![MoneyFunction::PoWRewardV1 as u8];
86        debris.params.encode_async(&mut data).await?;
87        let call = ContractCall { contract_id: *MONEY_CONTRACT_ID, data };
88        let mut tx_builder =
89            TransactionBuilder::new(ContractCallLeaf { call, proofs: debris.proofs }, vec![])?;
90        let mut tx = tx_builder.build()?;
91        let sigs = tx.create_sigs(&[wallet.keypair.secret])?;
92        tx.signatures = vec![sigs];
93
94        Ok((tx, debris.params))
95    }
96
97    /// 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.
101    pub async fn generate_block(
102        &mut self,
103        miner: &Holder,
104        holders: &[Holder],
105    ) -> Result<Vec<OwnCoin>> {
106        // Build the POW reward transaction
107        info!("Building PoWReward transaction for {:?}", miner);
108        let (tx, params) = self.pow_reward(miner, None, None, None).await?;
109
110        // Fetch the last block in the blockchain
111        let wallet = self.holders.get(miner).unwrap();
112        let previous = wallet.validator.blockchain.last_block()?;
113
114        // We increment timestamp so we don't have to use sleep
115        let timestamp = previous.header.timestamp.checked_add(1.into())?;
116
117        // Generate block header
118        let header = Header::new(
119            previous.hash(),
120            previous.header.height + 1,
121            timestamp,
122            previous.header.nonce,
123        );
124
125        // Generate the block
126        let mut block = BlockInfo::new_empty(header);
127
128        // Add producer transaction to the block
129        block.append_txs(vec![tx]);
130
131        // Attach signature
132        block.sign(&wallet.keypair.secret);
133
134        // For all holders, append the block
135        let mut found_owncoins = vec![];
136        for holder in holders {
137            let 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()));
140
141            // Attempt to decrypt the note to see if this is a coin for the holder
142            let Ok(note) = params.output.note.decrypt::<MoneyNote>(&wallet.keypair.secret) else {
143                continue
144            };
145
146            let 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            };
152
153            wallet.unspent_money_coins.push(owncoin.clone());
154            found_owncoins.push(owncoin);
155        }
156
157        Ok(found_owncoins)
158    }
159}