From 8828438d8f5723cb68d5b0089d8d15e3c61039f6 Mon Sep 17 00:00:00 2001 From: parazyd Date: Fri, 9 Feb 2024 13:47:04 +0100 Subject: [PATCH] contract/test-harness: Cleanup and addition of tx fees. --- Cargo.lock | 1 - src/contract/dao/src/model.rs | 4 +- src/contract/money/src/client/fee_v1.rs | 2 +- src/contract/money/src/client/mod.rs | 23 +- .../money/src/client/transfer_v1/builder.rs | 7 +- src/contract/test-harness/Cargo.toml | 2 +- src/contract/test-harness/src/benchmarks.rs | 74 ---- .../test-harness/src/contract_deploy.rs | 100 ++++- src/contract/test-harness/src/dao_exec.rs | 199 ++++++---- src/contract/test-harness/src/dao_mint.rs | 120 ++++-- src/contract/test-harness/src/dao_propose.rs | 129 +++++-- src/contract/test-harness/src/dao_vote.rs | 117 ++++-- src/contract/test-harness/src/lib.rs | 358 +++++------------- src/contract/test-harness/src/money_fee.rs | 317 ++++++++++++++++ .../test-harness/src/money_genesis_mint.rs | 80 ++-- .../test-harness/src/money_otc_swap.rs | 150 +++++--- .../test-harness/src/money_pow_reward.rs | 102 ++--- src/contract/test-harness/src/money_token.rs | 336 +++++++++++----- .../test-harness/src/money_transfer.rs | 175 +++++---- src/contract/test-harness/src/vks.rs | 146 +++---- src/validator/mod.rs | 18 +- 21 files changed, 1539 insertions(+), 921 deletions(-) delete mode 100644 src/contract/test-harness/src/benchmarks.rs create mode 100644 src/contract/test-harness/src/money_fee.rs diff --git a/Cargo.lock b/Cargo.lock index 2e6f18124..650b58234 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1926,7 +1926,6 @@ name = "darkfi-contract-test-harness" version = "0.4.1" dependencies = [ "blake3 1.5.0", - "bs58", "darkfi", "darkfi-sdk", "darkfi-serial", diff --git a/src/contract/dao/src/model.rs b/src/contract/dao/src/model.rs index cd4efccf7..9bf2dcba7 100644 --- a/src/contract/dao/src/model.rs +++ b/src/contract/dao/src/model.rs @@ -23,7 +23,7 @@ use darkfi_sdk::{ crypto::{ note::{AeadEncryptedNote, ElGamalEncryptedNote}, pasta_prelude::*, - poseidon_hash, BaseBlind, MerkleNode, PublicKey, + poseidon_hash, ContractId, MerkleNode, Nullifier, PublicKey, TokenId, }, error::ContractError, pasta::pallas, @@ -108,7 +108,7 @@ darkfi_sdk::ty_from_fp!(DaoBulla); #[derive(Debug, Clone, SerialEncodable, SerialDecodable)] // ANCHOR: dao-auth-call pub struct DaoAuthCall { - pub contract_id: pallas::Base, + pub contract_id: ContractId, pub function_code: u8, pub auth_data: Vec, } diff --git a/src/contract/money/src/client/fee_v1.rs b/src/contract/money/src/client/fee_v1.rs index b42e130ed..4dcb9dbe5 100644 --- a/src/contract/money/src/client/fee_v1.rs +++ b/src/contract/money/src/client/fee_v1.rs @@ -122,7 +122,7 @@ pub async fn append_fee_call( let input_value_blind = Blind::random(&mut OsRng); let fee_value_blind = Blind::random(&mut OsRng); - let output_value_blind = compute_remainder_blind(&[], &[input_value_blind], &[fee_value_blind]); + let output_value_blind = compute_remainder_blind(&[input_value_blind], &[fee_value_blind]); let signature_secret = SecretKey::random(&mut OsRng); diff --git a/src/contract/money/src/client/mod.rs b/src/contract/money/src/client/mod.rs index 5dfea768d..5906457c4 100644 --- a/src/contract/money/src/client/mod.rs +++ b/src/contract/money/src/client/mod.rs @@ -30,7 +30,10 @@ use std::hash::{Hash, Hasher}; use darkfi_sdk::{ bridgetree, - crypto::{BaseBlind, Blind, FuncId, ScalarBlind, SecretKey}, + crypto::{ + pasta_prelude::{Field, PrimeField}, + poseidon_hash, BaseBlind, Blind, FuncId, Nullifier, ScalarBlind, SecretKey, TokenId, + }, pasta::pallas, }; use darkfi_serial::{async_trait, SerialDecodable, SerialEncodable}; @@ -99,12 +102,17 @@ pub struct OwnCoin { pub note: MoneyNote, /// Coin's secret key pub secret: SecretKey, - /// Coin's nullifier - pub nullifier: Nullifier, /// Coin's leaf position in the Merkle tree of coins pub leaf_position: bridgetree::Position, } +impl OwnCoin { + /// Derive the [`Nullifier`] for this [`OwnCoin`] + pub fn nullifier(&self) -> Nullifier { + Nullifier::from(poseidon_hash([self.secret.inner(), self.coin.inner()])) + } +} + impl Hash for OwnCoin { fn hash(&self, state: &mut H) { self.coin.inner().to_repr().hash(state); @@ -112,15 +120,10 @@ impl Hash for OwnCoin { } pub fn compute_remainder_blind( - clear_inputs: &[crate::model::ClearInput], input_blinds: &[ScalarBlind], output_blinds: &[ScalarBlind], -) -> ScalarBlind { - let mut total = pallas::Scalar::zero(); - - for input in clear_inputs { - total += input.value_blind.inner(); - } +) -> pallas::Scalar { + let mut total = pallas::Scalar::ZERO; for input_blind in input_blinds { total += input_blind.inner(); diff --git a/src/contract/money/src/client/transfer_v1/builder.rs b/src/contract/money/src/client/transfer_v1/builder.rs index 95e728078..d2fdcd6d7 100644 --- a/src/contract/money/src/client/transfer_v1/builder.rs +++ b/src/contract/money/src/client/transfer_v1/builder.rs @@ -22,9 +22,7 @@ use darkfi::{ }; use darkfi_sdk::{ bridgetree, - crypto::{ - note::AeadEncryptedNote, pasta_prelude::*, MerkleNode, Nullifier, SecretKey, TokenId, - }, + crypto::{note::AeadEncryptedNote, pasta_prelude::*, MerkleNode, SecretKey, TokenId}, pasta::pallas, }; use log::{debug, info}; @@ -122,7 +120,7 @@ impl TransferCallBuilder { for (i, output) in self.outputs.iter().enumerate() { let value_blind = if i == self.outputs.len() - 1 { - compute_remainder_blind(&[], &input_blinds, &output_blinds) + compute_remainder_blind(&input_blinds, &output_blinds) } else { Blind::random(&mut OsRng) }; @@ -202,7 +200,6 @@ impl TransferCallSecrets { coin: output.coin, note: output_note.clone(), secret: SecretKey::from(pallas::Base::ZERO), - nullifier: Nullifier::from(pallas::Base::ZERO), leaf_position: 0.into(), }); } diff --git a/src/contract/test-harness/Cargo.toml b/src/contract/test-harness/Cargo.toml index e9e9587ca..bf72d8e70 100644 --- a/src/contract/test-harness/Cargo.toml +++ b/src/contract/test-harness/Cargo.toml @@ -9,13 +9,13 @@ edition = "2021" darkfi = {path = "../../../", features = ["validator"]} darkfi-sdk = {path = "../../../src/sdk"} darkfi-serial = {path = "../../../src/serial", features = ["crypto"]} + darkfi_dao_contract = {path = "../dao", features = ["client", "no-entrypoint"]} darkfi_money_contract = {path = "../money", features = ["client", "no-entrypoint"]} darkfi_deployooor_contract = {path = "../deployooor", features = ["client", "no-entrypoint"]} num-bigint = "0.4.4" blake3 = "1.5.0" -bs58 = "0.5.0" log = "0.4.20" rand = "0.8.5" simplelog = "0.12.1" diff --git a/src/contract/test-harness/src/benchmarks.rs b/src/contract/test-harness/src/benchmarks.rs deleted file mode 100644 index 49d72d96f..000000000 --- a/src/contract/test-harness/src/benchmarks.rs +++ /dev/null @@ -1,74 +0,0 @@ -/* This file is part of DarkFi (https://dark.fi) - * - * Copyright (C) 2020-2024 Dyne.org foundation - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - */ - -use log::info; -use std::time::Duration; - -use crate::TxAction; - -/// Auxiliary struct to calculate transaction actions benchmarks -pub struct TxActionBenchmarks { - /// Vector holding each transaction size in Bytes - pub sizes: Vec, - /// Vector holding each transaction broadcasted size in Bytes - pub broadcasted_sizes: Vec, - /// Vector holding each transaction creation time - pub creation_times: Vec, - /// Vector holding each transaction verify time - pub verify_times: Vec, -} - -impl TxActionBenchmarks { - pub fn new() -> Self { - Self { - sizes: vec![], - broadcasted_sizes: vec![], - creation_times: vec![], - verify_times: vec![], - } - } - - pub fn statistics(&self, action: &TxAction) { - if !self.sizes.is_empty() { - let avg = self.sizes.iter().sum::(); - let avg = avg / self.sizes.len(); - info!("Average {:?} size: {:?} Bytes", action, avg); - } - if !self.broadcasted_sizes.is_empty() { - let avg = self.broadcasted_sizes.iter().sum::(); - let avg = avg / self.broadcasted_sizes.len(); - info!("Average {:?} broadcasted size: {:?} Bytes", action, avg); - } - if !self.creation_times.is_empty() { - let avg = self.creation_times.iter().sum::(); - let avg = avg / self.creation_times.len() as u32; - info!("Average {:?} creation time: {:?}", action, avg); - } - if !self.verify_times.is_empty() { - let avg = self.verify_times.iter().sum::(); - let avg = avg / self.verify_times.len() as u32; - info!("Average {:?} verification time: {:?}", action, avg); - } - } -} - -impl Default for TxActionBenchmarks { - fn default() -> Self { - Self::new() - } -} diff --git a/src/contract/test-harness/src/contract_deploy.rs b/src/contract/test-harness/src/contract_deploy.rs index 24d5a0507..de669291c 100644 --- a/src/contract/test-harness/src/contract_deploy.rs +++ b/src/contract/test-harness/src/contract_deploy.rs @@ -23,24 +23,38 @@ use darkfi::{ use darkfi_deployooor_contract::{ client::deploy_v1::DeployCallBuilder, DeployFunction, DEPLOY_CONTRACT_ZKAS_DERIVE_NS_V1, }; -use darkfi_sdk::{crypto::DEPLOYOOOR_CONTRACT_ID, deploy::DeployParamsV1, ContractCall}; -use darkfi_serial::Encodable; +use darkfi_money_contract::{ + client::{MoneyNote, OwnCoin}, + model::MoneyFeeParamsV1, +}; +use darkfi_sdk::{ + crypto::{contract_id::DEPLOYOOOR_CONTRACT_ID, MerkleNode}, + deploy::DeployParamsV1, + ContractCall, +}; +use darkfi_serial::AsyncEncodable; +use log::debug; use rand::rngs::OsRng; use super::{Holder, TestHarness}; impl TestHarness { - pub fn deploy_contract( + /// Create a `Deployooor::Deploy` transaction with the given WASM bincode. + /// + /// Returns the [`Transaction`], and necessary parameters. + pub async fn deploy_contract( &mut self, holder: &Holder, wasm_bincode: Vec, - ) -> Result<(Transaction, DeployParamsV1)> { + block_height: u64, + ) -> Result<(Transaction, DeployParamsV1, Option)> { let wallet = self.holders.get(holder).unwrap(); let deploy_keypair = wallet.contract_deploy_authority; let (derivecid_pk, derivecid_zkbin) = self.proving_keys.get(&DEPLOY_CONTRACT_ZKAS_DERIVE_NS_V1.to_string()).unwrap(); + // Build the contract call let builder = DeployCallBuilder { deploy_keypair, wasm_bincode, @@ -48,32 +62,96 @@ impl TestHarness { derivecid_zkbin: derivecid_zkbin.clone(), derivecid_pk: derivecid_pk.clone(), }; - let debris = builder.build()?; + // Encode the call let mut data = vec![DeployFunction::DeployV1 as u8]; - debris.params.encode(&mut data)?; + debris.params.encode_async(&mut data).await?; let call = ContractCall { contract_id: *DEPLOYOOOR_CONTRACT_ID, data }; let mut tx_builder = TransactionBuilder::new(ContractCallLeaf { call, proofs: debris.proofs }, vec![])?; + + // If we have tx fees enabled, make an offering + let mut fee_params = None; + let mut fee_signature_secrets = None; + if self.verify_fees { + let mut tx = tx_builder.build()?; + let sigs = tx.create_sigs(&mut OsRng, &[deploy_keypair.secret])?; + tx.signatures = vec![sigs]; + + let (fee_call, fee_proofs, fee_secrets, _spent_fee_coins, fee_call_params) = + self.append_fee_call(holder, tx, block_height, &[]).await?; + + // Append the fee call to the transaction + tx_builder.append(ContractCallLeaf { call: fee_call, proofs: fee_proofs }, vec![])?; + fee_signature_secrets = Some(fee_secrets); + fee_params = Some(fee_call_params); + } + + // Now build the actual transaction and sign it with necessary keys. let mut tx = tx_builder.build()?; let sigs = tx.create_sigs(&mut OsRng, &[deploy_keypair.secret])?; tx.signatures = vec![sigs]; + if let Some(fee_signature_secrets) = fee_signature_secrets { + let sigs = tx.create_sigs(&mut OsRng, &fee_signature_secrets)?; + tx.signatures.push(sigs); + } - Ok((tx, debris.params)) + Ok((tx, debris.params, fee_params)) } + /// Execute the transaction created by `deploy_contract()` for a given [`Holder`]. + /// + /// Returns any found [`OwnCoin`]s. pub async fn execute_deploy_tx( &mut self, holder: &Holder, - tx: &Transaction, + tx: Transaction, _params: &DeployParamsV1, + fee_params: &Option, block_height: u64, - ) -> Result<()> { + append: bool, + ) -> Result> { let wallet = self.holders.get_mut(holder).unwrap(); - wallet.validator.add_transactions(&[tx.clone()], block_height, true).await?; + // Execute the transaction + wallet.validator.add_transactions(&[tx], block_height, true, self.verify_fees).await?; - Ok(()) + if !append { + return Ok(vec![]) + } + + if let Some(ref fee_params) = fee_params { + if let Some(spent_coin) = wallet + .unspent_money_coins + .iter() + .find(|x| x.nullifier() == fee_params.input.nullifier) + .cloned() + { + debug!("Found spent OwnCoin({}) for {:?}", spent_coin.coin, holder); + wallet.unspent_money_coins.retain(|x| x.nullifier() != fee_params.input.nullifier); + wallet.spent_money_coins.push(spent_coin.clone()); + } + + wallet.money_merkle_tree.append(MerkleNode::from(fee_params.output.coin.inner())); + + let Ok(note) = fee_params.output.note.decrypt::(&wallet.keypair.secret) + else { + return Ok(vec![]) + }; + + let owncoin = OwnCoin { + coin: fee_params.output.coin, + note: note.clone(), + secret: wallet.keypair.secret, + leaf_position: wallet.money_merkle_tree.mark().unwrap(), + }; + + debug!("Found new OwnCoin({}) for {:?}", owncoin.coin, holder); + wallet.unspent_money_coins.push(owncoin.clone()); + return Ok(vec![owncoin]) + } + + Ok(vec![]) } } diff --git a/src/contract/test-harness/src/dao_exec.rs b/src/contract/test-harness/src/dao_exec.rs index 1d6f57fb9..7a0650b38 100644 --- a/src/contract/test-harness/src/dao_exec.rs +++ b/src/contract/test-harness/src/dao_exec.rs @@ -16,9 +16,11 @@ * along with this program. If not, see . */ -use std::time::Instant; - -use darkfi::{tx::Transaction, Result}; +use darkfi::{ + tx::{ContractCallLeaf, Transaction, TransactionBuilder}, + zk::halo2::Field, + Result, +}; use darkfi_dao_contract::{ client::{DaoAuthMoneyTransferCall, DaoExecCall}, model::{Dao, DaoBulla, DaoExecParams, DaoProposal}, @@ -26,27 +28,29 @@ use darkfi_dao_contract::{ DAO_CONTRACT_ZKAS_DAO_AUTH_MONEY_TRANSFER_NS, DAO_CONTRACT_ZKAS_DAO_EXEC_NS, }; use darkfi_money_contract::{ - client::transfer_v1 as xfer, - model::{CoinAttributes, MoneyTransferParamsV1}, + client::{transfer_v1 as xfer, MoneyNote, OwnCoin}, + model::{CoinAttributes, MoneyFeeParamsV1, MoneyTransferParamsV1}, MoneyFunction, MONEY_CONTRACT_ZKAS_BURN_NS_V1, MONEY_CONTRACT_ZKAS_MINT_NS_V1, }; use darkfi_sdk::{ - crypto::{ - pedersen_commitment_u64, Blind, FuncRef, MerkleNode, ScalarBlind, SecretKey, - DAO_CONTRACT_ID, MONEY_CONTRACT_ID, - }, - dark_tree::DarkLeaf, - ContractCall, + contract_id::{DAO_CONTRACT_ID, MONEY_CONTRACT_ID}, + crypto::{DAO_CONTRACT_ID, MONEY_CONTRACT_ID}, + dark_tree::{DarkLeaf, DarkTree}, + pasta::pallas, + pedersen_commitment_u64, ContractCall, FuncRef, MerkleNode, SecretKey, }; -use darkfi_serial::{serialize, Encodable}; +use darkfi_serial::AsyncEncodable; +use log::debug; use rand::rngs::OsRng; -use super::{Holder, TestHarness, TxAction}; +use super::{Holder, TestHarness}; impl TestHarness { + /// Create a `Dao::Exec` transaction. #[allow(clippy::too_many_arguments)] - pub fn dao_exec( + pub async fn dao_exec( &mut self, + holder: &Holder, dao: &Dao, dao_bulla: &DaoBulla, proposal: &DaoProposal, @@ -55,7 +59,8 @@ impl TestHarness { all_vote_value: u64, yes_vote_blind: ScalarBlind, all_vote_blind: ScalarBlind, - ) -> Result<(Transaction, MoneyTransferParamsV1, DaoExecParams)> { + block_height: u64, + ) -> Result<(Transaction, MoneyTransferParamsV1, DaoExecParams, Option)> { let dao_wallet = self.holders.get(&Holder::Dao).unwrap(); let (mint_pk, mint_zkbin) = @@ -73,9 +78,6 @@ impl TestHarness { .get(&DAO_CONTRACT_ZKAS_DAO_AUTH_MONEY_TRANSFER_ENC_COIN_NS.to_string()) .unwrap(); - let tx_action_benchmark = self.tx_action_benchmarks.get_mut(&TxAction::DaoExec).unwrap(); - let timer = Instant::now(); - let input_user_data_blind = Blind::random(&mut OsRng); let exec_signature_secret = SecretKey::random(&mut OsRng); @@ -139,7 +141,7 @@ impl TestHarness { let (xfer_params, xfer_secrets) = xfer_builder.build()?; let mut data = vec![MoneyFunction::TransferV1 as u8]; - xfer_params.encode(&mut data)?; + xfer_params.encode_async(&mut data).await?; let xfer_call = ContractCall { contract_id: *MONEY_CONTRACT_ID, data }; // We need to extract stuff from the inputs and outputs that we'll also @@ -171,7 +173,7 @@ impl TestHarness { let (exec_params, exec_proofs) = exec_builder.make(dao_exec_zkbin, dao_exec_pk)?; let mut data = vec![DaoFunction::Exec as u8]; - exec_params.encode(&mut data)?; + exec_params.encode_async(&mut data).await?; let exec_call = ContractCall { contract_id: *DAO_CONTRACT_ID, data }; // Auth module @@ -189,7 +191,7 @@ impl TestHarness { dao_auth_xfer_enc_coin_pk, )?; let mut data = vec![DaoFunction::AuthMoneyTransfer as u8]; - auth_xfer_params.encode(&mut data)?; + auth_xfer_params.encode_async(&mut data).await?; let auth_xfer_call = ContractCall { contract_id: *DAO_CONTRACT_ID, data }; // We need to construct this tree, where exec is the parent: @@ -199,61 +201,122 @@ impl TestHarness { // xfer // - //let mut tx_builder = TransactionBuilder::new( - // ContractCallLeaf { call: exec_call, proofs: exec_proofs }, - // vec![], - //)?; - //tx_builder - // .append(ContractCallLeaf { call: auth_xfer_call, proofs: auth_xfer_proofs }, vec![])?; - //tx_builder - //let mut tx = tx_builder.build()?; - - let mut tx = Transaction { - calls: vec![ - DarkLeaf { data: auth_xfer_call, parent_index: Some(2), children_indexes: vec![] }, - DarkLeaf { data: xfer_call, parent_index: Some(2), children_indexes: vec![] }, - DarkLeaf { data: exec_call, parent_index: None, children_indexes: vec![0, 1] }, + let mut tx_builder = TransactionBuilder::new( + ContractCallLeaf { call: exec_call, proofs: exec_proofs }, + vec![ + DarkTree::new( + ContractCallLeaf { call: auth_xfer_call, proofs: auth_xfer_proofs }, + vec![], + None, + None, + ), + DarkTree::new( + ContractCallLeaf { call: xfer_call, proofs: xfer_secrets.proofs }, + vec![], + None, + None, + ), ], - proofs: vec![auth_xfer_proofs, xfer_secrets.proofs, exec_proofs], - signatures: vec![], - }; + )?; + + // If fees are enabled, make an offering + let mut fee_params = None; + let mut fee_signature_secrets = None; + if self.verify_fees { + let mut tx = tx_builder.build()?; + let auth_xfer_sigs = vec![]; + let xfer_sigs = tx.create_sigs(&mut OsRng, &xfer_secrets.signature_secrets)?; + let exec_sigs = tx.create_sigs(&mut OsRng, &[exec_signature_secret])?; + tx.signatures = vec![auth_xfer_sigs, xfer_sigs, exec_sigs]; + + let (fee_call, fee_proofs, fee_secrets, _spent_fee_coins, fee_call_params) = + self.append_fee_call(holder, tx, block_height, &[]).await?; + + // Append the fee call to the transaction + tx_builder.append(ContractCallLeaf { call: fee_call, proofs: fee_proofs }, vec![])?; + fee_signature_secrets = Some(fee_secrets); + fee_params = Some(fee_call_params); + } + + // Now build the actual transaction and sign it with necessary keys. + let mut tx = tx_builder.build()?; let auth_xfer_sigs = vec![]; let xfer_sigs = tx.create_sigs(&mut OsRng, &xfer_secrets.signature_secrets)?; let exec_sigs = tx.create_sigs(&mut OsRng, &[exec_signature_secret])?; tx.signatures = vec![auth_xfer_sigs, xfer_sigs, exec_sigs]; - tx_action_benchmark.creation_times.push(timer.elapsed()); - // Calculate transaction sizes - let encoded: Vec = serialize(&tx); - let size = std::mem::size_of_val(&*encoded); - tx_action_benchmark.sizes.push(size); - let base58 = bs58::encode(&encoded).into_string(); - let size = std::mem::size_of_val(&*base58); - tx_action_benchmark.broadcasted_sizes.push(size); - - Ok((tx, xfer_params, exec_params)) - } - - pub async fn execute_dao_exec_tx( - &mut self, - holder: &Holder, - tx: &Transaction, - xfer_params: &MoneyTransferParamsV1, - _exec_params: &DaoExecParams, - block_height: u64, - ) -> Result<()> { - let wallet = self.holders.get_mut(holder).unwrap(); - let tx_action_benchmark = self.tx_action_benchmarks.get_mut(&TxAction::DaoExec).unwrap(); - let timer = Instant::now(); - - wallet.validator.add_transactions(&[tx.clone()], block_height, true).await?; - - for output in &xfer_params.outputs { - wallet.money_merkle_tree.append(MerkleNode::from(output.coin.inner())); + if let Some(fee_signature_secrets) = fee_signature_secrets { + let sigs = tx.create_sigs(&mut OsRng, &fee_signature_secrets)?; + tx.signatures.push(sigs); } - tx_action_benchmark.verify_times.push(timer.elapsed()); + Ok((tx, xfer_params, exec_params, fee_params)) + } - Ok(()) + /// Execute the transaction made by `dao_exec()` for a given [`Holder`]. + /// + /// Returns any found [`OwnCoin`]s. + #[allow(clippy::too_many_arguments)] + pub async fn excecute_dao_exec_tx( + &mut self, + holder: &Holder, + tx: Transaction, + xfer_params: &MoneyTransferParamsV1, + _exec_params: &DaoExecParams, + fee_params: &Option, + block_height: u64, + append: bool, + ) -> Result> { + let wallet = self.holders.get_mut(holder).unwrap(); + + // Execute the transaction + wallet.validator.add_transactions(&[tx], block_height, true, self.verify_fees).await?; + + if !append { + return Ok(vec![]) + } + + let mut inputs = xfer_params.inputs.to_vec(); + let mut outputs = xfer_params.outputs.to_vec(); + + if let Some(ref fee_params) = fee_params { + inputs.push(fee_params.input.clone()); + outputs.push(fee_params.output.clone()); + } + + for input in inputs { + if let Some(spent_coin) = wallet + .unspent_money_coins + .iter() + .find(|x| x.nullifier() == input.nullifier) + .cloned() + { + debug!("Found spent OwnCoin({}) for {:?}", spent_coin.coin, holder); + wallet.unspent_money_coins.retain(|x| x.nullifier() != input.nullifier); + wallet.spent_money_coins.push(spent_coin.clone()); + } + } + + let mut found_owncoins = vec![]; + for output in outputs { + wallet.money_merkle_tree.append(MerkleNode::from(output.coin.inner())); + + let Ok(note) = output.note.decrypt::(&wallet.keypair.secret) else { + continue + }; + + let owncoin = OwnCoin { + coin: output.coin, + note: note.clone(), + secret: wallet.keypair.secret, + leaf_position: wallet.money_merkle_tree.mark().unwrap(), + }; + + debug!("Found new OwnCoin({}) for {:?}", owncoin.coin, holder); + wallet.unspent_money_coins.push(owncoin.clone()); + found_owncoins.push(owncoin); + } + + Ok(found_owncoins) } } diff --git a/src/contract/test-harness/src/dao_mint.rs b/src/contract/test-harness/src/dao_mint.rs index d4af7544f..a0bdba199 100644 --- a/src/contract/test-harness/src/dao_mint.rs +++ b/src/contract/test-harness/src/dao_mint.rs @@ -16,79 +16,139 @@ * along with this program. If not, see . */ -use std::time::Instant; - use darkfi::{ tx::{ContractCallLeaf, Transaction, TransactionBuilder}, Result, }; use darkfi_dao_contract::{ - client, + client::make_mint_call, model::{Dao, DaoMintParams}, DaoFunction, DAO_CONTRACT_ZKAS_DAO_MINT_NS, }; +use darkfi_money_contract::{ + client::{MoneyNote, OwnCoin}, + model::MoneyFeeParamsV1, +}; use darkfi_sdk::{ - crypto::{Keypair, MerkleNode, DAO_CONTRACT_ID}, + crypto::{contract_id::DAO_CONTRACT_ID, Keypair, MerkleNode}, ContractCall, }; -use darkfi_serial::{serialize, Encodable}; +use darkfi_serial::AsyncEncodable; +use log::debug; use rand::rngs::OsRng; -use super::{Holder, TestHarness, TxAction}; +use super::{Holder, TestHarness}; impl TestHarness { - pub fn dao_mint( + /// Create a `Dao::Mint` transaction with the given [`Dao`] info and a keypair. + /// Takes a [`Holder`] for optionally paying the transaction fee. + /// + /// Returns the [`Transaction`] and any relevant parameters. + pub async fn dao_mint( &mut self, + holder: &Holder, dao_info: &Dao, dao_kp: &Keypair, - ) -> Result<(Transaction, DaoMintParams)> { + block_height: u64, + ) -> Result<(Transaction, DaoMintParams, Option)> { let (dao_mint_pk, dao_mint_zkbin) = self.proving_keys.get(&DAO_CONTRACT_ZKAS_DAO_MINT_NS.to_string()).unwrap(); - let tx_action_benchmark = self.tx_action_benchmarks.get_mut(&TxAction::DaoMint).unwrap(); - let timer = Instant::now(); - + // Create the call let (params, proofs) = - client::make_mint_call(dao_info, &dao_kp.secret, dao_mint_zkbin, dao_mint_pk)?; + make_mint_call(dao_info, &dao_kp.secret, dao_mint_zkbin, dao_mint_pk)?; + // Encode the call let mut data = vec![DaoFunction::Mint as u8]; - params.encode(&mut data)?; + params.encode_async(&mut data).await?; let call = ContractCall { contract_id: *DAO_CONTRACT_ID, data }; let mut tx_builder = TransactionBuilder::new(ContractCallLeaf { call, proofs }, vec![])?; + + // If fees are enabled, make an offering + let mut fee_params = None; + let mut fee_signature_secrets = None; + if self.verify_fees { + let mut tx = tx_builder.build()?; + let sigs = tx.create_sigs(&mut OsRng, &[dao_kp.secret])?; + tx.signatures = vec![sigs]; + + let (fee_call, fee_proofs, fee_secrets, _spent_fee_coins, fee_call_params) = + self.append_fee_call(holder, tx, block_height, &[]).await?; + + // Append the fee call to the transaction + tx_builder.append(ContractCallLeaf { call: fee_call, proofs: fee_proofs }, vec![])?; + fee_signature_secrets = Some(fee_secrets); + fee_params = Some(fee_call_params); + } + + // Now build the actual transaction and sign it with necessary keys. let mut tx = tx_builder.build()?; let sigs = tx.create_sigs(&mut OsRng, &[dao_kp.secret])?; tx.signatures = vec![sigs]; - tx_action_benchmark.creation_times.push(timer.elapsed()); + if let Some(fee_signature_secrets) = fee_signature_secrets { + let sigs = tx.create_sigs(&mut OsRng, &fee_signature_secrets)?; + tx.signatures.push(sigs); + } - // Calculate transaction sizes - let encoded: Vec = serialize(&tx); - let size = std::mem::size_of_val(&*encoded); - tx_action_benchmark.sizes.push(size); - let base58 = bs58::encode(&encoded).into_string(); - let size = std::mem::size_of_val(&*base58); - tx_action_benchmark.broadcasted_sizes.push(size); - - Ok((tx, params)) + Ok((tx, params, fee_params)) } + /// Execute the transaction created by `dao_mint()` for a given [`Holder`]. + /// + /// Returns any found [`OwnCoin`]s. pub async fn execute_dao_mint_tx( &mut self, holder: &Holder, - tx: &Transaction, + tx: Transaction, params: &DaoMintParams, + fee_params: &Option, block_height: u64, - ) -> Result<()> { + append: bool, + ) -> Result> { let wallet = self.holders.get_mut(holder).unwrap(); - let tx_action_benchmark = self.tx_action_benchmarks.get_mut(&TxAction::DaoMint).unwrap(); - let timer = Instant::now(); - wallet.validator.add_transactions(&[tx.clone()], block_height, true).await?; + // Execute the transaction + wallet.validator.add_transactions(&[tx], block_height, true, self.verify_fees).await?; + + if !append { + return Ok(vec![]) + } + wallet.dao_merkle_tree.append(MerkleNode::from(params.dao_bulla.inner())); let leaf_pos = wallet.dao_merkle_tree.mark().unwrap(); wallet.dao_leafs.insert(params.dao_bulla, leaf_pos); - tx_action_benchmark.verify_times.push(timer.elapsed()); + if let Some(ref fee_params) = fee_params { + if let Some(spent_coin) = wallet + .unspent_money_coins + .iter() + .find(|x| x.nullifier() == fee_params.input.nullifier) + .cloned() + { + debug!("Found spent OwnCoin({}) for {:?}", spent_coin.coin, holder); + wallet.unspent_money_coins.retain(|x| x.nullifier() != fee_params.input.nullifier); + wallet.spent_money_coins.push(spent_coin.clone()); + } - Ok(()) + wallet.money_merkle_tree.append(MerkleNode::from(fee_params.output.coin.inner())); + + let Ok(note) = fee_params.output.note.decrypt::(&wallet.keypair.secret) + else { + return Ok(vec![]) + }; + + let owncoin = OwnCoin { + coin: fee_params.output.coin, + note: note.clone(), + secret: wallet.keypair.secret, + leaf_position: wallet.money_merkle_tree.mark().unwrap(), + }; + + debug!("Found new OwnCoin({}) for {:?}", owncoin.coin, holder); + wallet.unspent_money_coins.push(owncoin.clone()); + return Ok(vec![owncoin]) + } + + Ok(vec![]) } } diff --git a/src/contract/test-harness/src/dao_propose.rs b/src/contract/test-harness/src/dao_propose.rs index a396e104b..1e7bf92fa 100644 --- a/src/contract/test-harness/src/dao_propose.rs +++ b/src/contract/test-harness/src/dao_propose.rs @@ -16,10 +16,9 @@ * along with this program. If not, see . */ -use std::time::Instant; - use darkfi::{ tx::{ContractCallLeaf, Transaction, TransactionBuilder}, + zk::halo2::Field, Result, }; use darkfi_dao_contract::{ @@ -28,37 +27,44 @@ use darkfi_dao_contract::{ model::{Dao, DaoAuthCall, DaoBulla, DaoProposal, DaoProposeParams}, DaoFunction, DAO_CONTRACT_ZKAS_DAO_PROPOSE_INPUT_NS, DAO_CONTRACT_ZKAS_DAO_PROPOSE_MAIN_NS, }; -use darkfi_money_contract::{client::OwnCoin, model::CoinAttributes, MoneyFunction}; +use darkfi_money_contract::{ + client::{MoneyNote, OwnCoin}, + model::{CoinAttributes, MoneyFeeParamsV1}, + MoneyFunction, +}; use darkfi_sdk::{ - crypto::{Blind, MerkleNode, SecretKey, DAO_CONTRACT_ID, MONEY_CONTRACT_ID}, + crypto::{ + contract_id::{DAO_CONTRACT_ID, MONEY_CONTRACT_ID}, + Blind, MerkleNode, SecretKey, DAO_CONTRACT_ID, MONEY_CONTRACT_ID, + }, pasta::pallas, ContractCall, }; -use darkfi_serial::{serialize, Encodable}; +use darkfi_serial::AsyncEncodable; +use log::debug; use rand::rngs::OsRng; -use super::{Holder, TestHarness, TxAction}; +use super::{Holder, TestHarness}; impl TestHarness { - pub fn dao_propose( + /// Create a `Dao::Propose` transaction. + pub async fn dao_propose( &mut self, proposer: &Holder, - proposal_coinattrs: &Vec, + proposal_coinattrs: &[CoinAttributes], user_data: pallas::Base, dao: &Dao, dao_bulla: &DaoBulla, block_height: u64, - ) -> Result<(Transaction, DaoProposeParams, DaoProposal)> { + ) -> Result<(Transaction, (DaoProposeParams, Option), DaoProposal)> { let wallet = self.holders.get(proposer).unwrap(); let (dao_propose_burn_pk, dao_propose_burn_zkbin) = self.proving_keys.get(&DAO_CONTRACT_ZKAS_DAO_PROPOSE_INPUT_NS.to_string()).unwrap(); + let (dao_propose_main_pk, dao_propose_main_zkbin) = self.proving_keys.get(&DAO_CONTRACT_ZKAS_DAO_PROPOSE_MAIN_NS.to_string()).unwrap(); - let tx_action_benchmark = self.tx_action_benchmarks.get_mut(&TxAction::DaoPropose).unwrap(); - let timer = Instant::now(); - let propose_owncoin: OwnCoin = wallet .unspent_money_coins .iter() @@ -67,6 +73,7 @@ impl TestHarness { .clone(); let signature_secret = SecretKey::random(&mut OsRng); + let input = DaoProposeStakeInput { secret: wallet.keypair.secret, note: propose_owncoin.note.clone(), @@ -84,16 +91,17 @@ impl TestHarness { proposal_coins.push(coin_params.to_coin()); } let mut proposal_data = vec![]; - proposal_coins.encode(&mut proposal_data).unwrap(); + proposal_coins.encode_async(&mut proposal_data).await?; + // Create Auth calls let auth_calls = vec![ DaoAuthCall { - contract_id: DAO_CONTRACT_ID.inner(), + contract_id: *DAO_CONTRACT_ID, function_code: DaoFunction::AuthMoneyTransfer as u8, auth_data: proposal_data, }, DaoAuthCall { - contract_id: MONEY_CONTRACT_ID.inner(), + contract_id: *MONEY_CONTRACT_ID, function_code: MoneyFunction::TransferV1 as u8, auth_data: vec![], }, @@ -128,47 +136,98 @@ impl TestHarness { dao_propose_main_pk, )?; + // Encode the call let mut data = vec![DaoFunction::Propose as u8]; - params.encode(&mut data)?; + params.encode_async(&mut data).await?; let call = ContractCall { contract_id: *DAO_CONTRACT_ID, data }; let mut tx_builder = TransactionBuilder::new(ContractCallLeaf { call, proofs }, vec![])?; + + // If fees are enabled, make an offering + let mut fee_params = None; + let mut fee_signature_secrets = None; + if self.verify_fees { + let mut tx = tx_builder.build()?; + let sigs = tx.create_sigs(&mut OsRng, &[signature_secret])?; + tx.signatures = vec![sigs]; + + let (fee_call, fee_proofs, fee_secrets, _spent_fee_coins, fee_call_params) = + self.append_fee_call(proposer, tx, block_height, &[]).await?; + + // Append the fee call to the transaction + tx_builder.append(ContractCallLeaf { call: fee_call, proofs: fee_proofs }, vec![])?; + fee_signature_secrets = Some(fee_secrets); + fee_params = Some(fee_call_params); + } + + // Now build the actual transaction and sign it with necessary keys. let mut tx = tx_builder.build()?; let sigs = tx.create_sigs(&mut OsRng, &[signature_secret])?; tx.signatures = vec![sigs]; - tx_action_benchmark.creation_times.push(timer.elapsed()); + if let Some(fee_signature_secrets) = fee_signature_secrets { + let sigs = tx.create_sigs(&mut OsRng, &fee_signature_secrets)?; + tx.signatures.push(sigs); + } - // Calculate transaction sizes - let encoded: Vec = serialize(&tx); - let size = std::mem::size_of_val(&*encoded); - tx_action_benchmark.sizes.push(size); - let base58 = bs58::encode(&encoded).into_string(); - let size = std::mem::size_of_val(&*base58); - tx_action_benchmark.broadcasted_sizes.push(size); - - Ok((tx, params, proposal)) + Ok((tx, (params, fee_params), proposal)) } + /// Execute the transaction created by `dao_propose()` for a given [`Holder`]. + /// + /// Returns any found [`OwnCoin`]s. pub async fn execute_dao_propose_tx( &mut self, holder: &Holder, - tx: &Transaction, + tx: Transaction, params: &DaoProposeParams, + fee_params: &Option, block_height: u64, - ) -> Result<()> { + append: bool, + ) -> Result> { let wallet = self.holders.get_mut(holder).unwrap(); - let tx_action_benchmark = self.tx_action_benchmarks.get_mut(&TxAction::DaoPropose).unwrap(); - let timer = Instant::now(); - wallet.validator.add_transactions(&[tx.clone()], block_height, true).await?; + // Execute the transaction + wallet.validator.add_transactions(&[tx], block_height, true, self.verify_fees).await?; + + if !append { + return Ok(vec![]) + } + wallet.dao_proposals_tree.append(MerkleNode::from(params.proposal_bulla.inner())); - let prop_leaf_pos = wallet.dao_proposals_tree.mark().unwrap(); let prop_money_snapshot = wallet.money_merkle_tree.clone(); - wallet.dao_prop_leafs.insert(params.proposal_bulla, (prop_leaf_pos, prop_money_snapshot)); - tx_action_benchmark.verify_times.push(timer.elapsed()); + if let Some(ref fee_params) = fee_params { + if let Some(spent_coin) = wallet + .unspent_money_coins + .iter() + .find(|x| x.nullifier() == fee_params.input.nullifier) + .cloned() + { + debug!("Found spent OwnCoin({}) for {:?}", spent_coin.coin, holder); + wallet.unspent_money_coins.retain(|x| x.nullifier() != fee_params.input.nullifier); + wallet.spent_money_coins.push(spent_coin.clone()); + } - Ok(()) + wallet.money_merkle_tree.append(MerkleNode::from(fee_params.output.coin.inner())); + + let Ok(note) = fee_params.output.note.decrypt::(&wallet.keypair.secret) + else { + return Ok(vec![]) + }; + + let owncoin = OwnCoin { + coin: fee_params.output.coin, + note: note.clone(), + secret: wallet.keypair.secret, + leaf_position: wallet.money_merkle_tree.mark().unwrap(), + }; + + debug!("Found new OwnCoin({}) for {:?}:", owncoin.coin, holder); + wallet.unspent_money_coins.push(owncoin.clone()); + return Ok(vec![owncoin]) + } + + Ok(vec![]) } } diff --git a/src/contract/test-harness/src/dao_vote.rs b/src/contract/test-harness/src/dao_vote.rs index 84969b670..9505c66a9 100644 --- a/src/contract/test-harness/src/dao_vote.rs +++ b/src/contract/test-harness/src/dao_vote.rs @@ -2,7 +2,7 @@ * * Copyright (C) 2020-2024 Dyne.org foundation * - * This program is free software: you can redistribute it and/or modify + * This program is free software: you can redistributemoney it and/or modify * it under the terms of the GNU Affero General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version. @@ -16,8 +16,6 @@ * along with this program. If not, see . */ -use std::time::Instant; - use darkfi::{ tx::{ContractCallLeaf, Transaction, TransactionBuilder}, Result, @@ -28,19 +26,24 @@ use darkfi_dao_contract::{ model::{Dao, DaoProposal, DaoProposalBulla, DaoVoteParams}, DaoFunction, DAO_CONTRACT_ZKAS_DAO_VOTE_INPUT_NS, DAO_CONTRACT_ZKAS_DAO_VOTE_MAIN_NS, }; -use darkfi_money_contract::client::OwnCoin; +use darkfi_money_contract::{ + client::{MoneyNote, OwnCoin}, + model::MoneyFeeParamsV1, +}; use darkfi_sdk::{ - crypto::{Keypair, SecretKey, DAO_CONTRACT_ID}, + crypto::{contract_id::DAO_CONTRACT_ID, Keypair, MerkleNode, SecretKey}, ContractCall, }; -use darkfi_serial::{serialize, Encodable}; +use darkfi_serial::AsyncEncodable; +use log::debug; use rand::rngs::OsRng; -use super::{Holder, TestHarness, TxAction}; +use super::{Holder, TestHarness}; impl TestHarness { + /// Create a `Dao::Vote` transaction. #[allow(clippy::too_many_arguments)] - pub fn dao_vote( + pub async fn dao_vote( &mut self, voter: &Holder, vote_option: bool, @@ -49,7 +52,7 @@ impl TestHarness { proposal: &DaoProposal, proposal_bulla: &DaoProposalBulla, block_height: u64, - ) -> Result<(Transaction, DaoVoteParams)> { + ) -> Result<(Transaction, DaoVoteParams, Option)> { let wallet = self.holders.get(voter).unwrap(); let (dao_vote_burn_pk, dao_vote_burn_zkbin) = @@ -58,10 +61,7 @@ impl TestHarness { let (dao_vote_main_pk, dao_vote_main_zkbin) = self.proving_keys.get(&DAO_CONTRACT_ZKAS_DAO_VOTE_MAIN_NS.to_string()).unwrap(); - let tx_action_benchmark = self.tx_action_benchmarks.get_mut(&TxAction::DaoVote).unwrap(); - let timer = Instant::now(); - - let (_proposal_leaf_pos, money_merkle_tree) = + let (_proposal_leaf_pos, snapshot_money_merkle_tree) = wallet.dao_prop_leafs.get(proposal_bulla).unwrap(); let vote_owncoin: OwnCoin = wallet @@ -72,11 +72,12 @@ impl TestHarness { .clone(); let signature_secret = SecretKey::random(&mut OsRng); + let input = DaoVoteInput { secret: wallet.keypair.secret, note: vote_owncoin.note.clone(), leaf_position: vote_owncoin.leaf_position, - merkle_path: money_merkle_tree.witness(vote_owncoin.leaf_position, 0).unwrap(), + merkle_path: snapshot_money_merkle_tree.witness(vote_owncoin.leaf_position, 0).unwrap(), signature_secret, }; @@ -97,41 +98,93 @@ impl TestHarness { dao_vote_main_pk, )?; + // Encode the call let mut data = vec![DaoFunction::Vote as u8]; - params.encode(&mut data)?; + params.encode_async(&mut data).await?; let call = ContractCall { contract_id: *DAO_CONTRACT_ID, data }; let mut tx_builder = TransactionBuilder::new(ContractCallLeaf { call, proofs }, vec![])?; + + // If fees are enabled, make an offering + let mut fee_params = None; + let mut fee_signature_secrets = None; + if self.verify_fees { + let mut tx = tx_builder.build()?; + let sigs = tx.create_sigs(&mut OsRng, &[signature_secret])?; + tx.signatures = vec![sigs]; + + let (fee_call, fee_proofs, fee_secrets, _spent_fee_coins, fee_call_params) = + self.append_fee_call(voter, tx, block_height, &[]).await?; + + // Append the fee call to the transaction + tx_builder.append(ContractCallLeaf { call: fee_call, proofs: fee_proofs }, vec![])?; + fee_signature_secrets = Some(fee_secrets); + fee_params = Some(fee_call_params); + } + + // Now build the actual transaction and sign it with necessary keys. let mut tx = tx_builder.build()?; let sigs = tx.create_sigs(&mut OsRng, &[signature_secret])?; tx.signatures = vec![sigs]; - tx_action_benchmark.creation_times.push(timer.elapsed()); + if let Some(fee_signature_secrets) = fee_signature_secrets { + let sigs = tx.create_sigs(&mut OsRng, &fee_signature_secrets)?; + tx.signatures.push(sigs); + } - // Calculate transaction sizes - let encoded: Vec = serialize(&tx); - let size = std::mem::size_of_val(&*encoded); - tx_action_benchmark.sizes.push(size); - let base58 = bs58::encode(&encoded).into_string(); - let size = std::mem::size_of_val(&*base58); - tx_action_benchmark.broadcasted_sizes.push(size); - - Ok((tx, params)) + Ok((tx, params, fee_params)) } + /// Execute the transaction made by `dao_vote()` for a given [`Holder`]. + /// + /// Returns any found [`OwnCoin`]s. pub async fn execute_dao_vote_tx( &mut self, holder: &Holder, - tx: &Transaction, + tx: Transaction, _params: &DaoVoteParams, + fee_params: &Option, block_height: u64, - ) -> Result<()> { + append: bool, + ) -> Result> { let wallet = self.holders.get_mut(holder).unwrap(); - let tx_action_benchmark = self.tx_action_benchmarks.get_mut(&TxAction::DaoVote).unwrap(); - let timer = Instant::now(); - wallet.validator.add_transactions(&[tx.clone()], block_height, true).await?; + // Execute the transaction + wallet.validator.add_transactions(&[tx], block_height, true, self.verify_fees).await?; - tx_action_benchmark.verify_times.push(timer.elapsed()); + if !append { + return Ok(vec![]) + } - Ok(()) + if let Some(ref fee_params) = fee_params { + if let Some(spent_coin) = wallet + .unspent_money_coins + .iter() + .find(|x| x.nullifier() == fee_params.input.nullifier) + .cloned() + { + debug!("Found spent OwnCoin({}) for {:?}", spent_coin.coin, holder); + wallet.unspent_money_coins.retain(|x| x.nullifier() != fee_params.input.nullifier); + wallet.spent_money_coins.push(spent_coin.clone()); + } + + wallet.money_merkle_tree.append(MerkleNode::from(fee_params.output.coin.inner())); + + let Ok(note) = fee_params.output.note.decrypt::(&wallet.keypair.secret) + else { + return Ok(vec![]) + }; + + let owncoin = OwnCoin { + coin: fee_params.output.coin, + note: note.clone(), + secret: wallet.keypair.secret, + leaf_position: wallet.money_merkle_tree.mark().unwrap(), + }; + + debug!("Found new OwnCoin({}) for {:?}", owncoin.coin, holder); + wallet.unspent_money_coins.push(owncoin.clone()); + return Ok(vec![owncoin]) + } + + Ok(vec![]) } } diff --git a/src/contract/test-harness/src/lib.rs b/src/contract/test-harness/src/lib.rs index 220e4f4d0..293f16790 100644 --- a/src/contract/test-harness/src/lib.rs +++ b/src/contract/test-harness/src/lib.rs @@ -16,14 +16,13 @@ * along with this program. If not, see . */ -use std::{collections::HashMap, io::Cursor, time::Instant}; +use std::{collections::HashMap, io::Cursor}; use darkfi::{ blockchain::BlockInfo, - tx::Transaction, util::{pcg::Pcg32, time::Timestamp}, validator::{Validator, ValidatorConfig, ValidatorPtr}, - zk::{empty_witnesses, ProvingKey, ZkCircuit}, + zk::{empty_witnesses, halo2::Field, ProvingKey, ZkCircuit}, zkas::ZkBinary, Result, }; @@ -34,33 +33,49 @@ use darkfi_money_contract::{ }; use darkfi_sdk::{ bridgetree, - crypto::{ - note::AeadEncryptedNote, pasta_prelude::Field, poseidon_hash, ContractId, Keypair, - MerkleNode, MerkleTree, Nullifier, SecretKey, - }, + crypto::{Keypair, MerkleNode, MerkleTree}, pasta::pallas, }; -use log::{info, warn}; +use log::debug; use num_bigint::BigUint; -use rand::rngs::OsRng; -mod benchmarks; -use benchmarks::TxActionBenchmarks; +/// Utility module for caching ZK proof PKs and VKs pub mod vks; -use vks::{read_or_gen_vks_and_pks, Vks}; -mod contract_deploy; -mod dao_exec; -mod dao_mint; -mod dao_propose; -mod dao_vote; -mod money_airdrop; -mod money_genesis_mint; -mod money_otc_swap; +/// `Money::PoWReward` functionality mod money_pow_reward; -mod money_token; + +/// `Money::Fee` functionality +mod money_fee; + +/// `Money::GenesisMint` functionality +mod money_genesis_mint; + +/// `Money::Transfer` functionality mod money_transfer; +/// `Money::TokenMint` functionality +mod money_token; + +/// `Money::OtcSwap` functionality +mod money_otc_swap; + +/// `Deployooor::Deploy` functionality +mod contract_deploy; + +/// `Dao::Mint` functionality +mod dao_mint; + +/// `Dao::Propose` functionality +mod dao_propose; + +/// `Dao::Vote` functionality +mod dao_vote; + +/// `Dao::Exec` functionality +mod dao_exec; + +/// Initialize the logging mechanism pub fn init_logger() { let mut cfg = simplelog::ConfigBuilder::new(); cfg.add_filter_ignore("sled".to_string()); @@ -78,118 +93,110 @@ pub fn init_logger() { ) .is_err() { - warn!(target: "test_harness", "Logger already initialized"); + debug!(target: "test_harness", "Logger initialized"); } } -/// Enum representing configured wallet holders -#[derive(Debug, Eq, Hash, PartialEq)] +/// Enum representing available wallet holders +#[derive(Clone, Copy, Eq, PartialEq, Hash, Debug)] pub enum Holder { - Faucet, Alice, Bob, Charlie, - Rachel, Dao, } -/// Enum representing transaction actions -#[derive(Debug, Eq, Hash, PartialEq)] -pub enum TxAction { - MoneyAirdrop, - MoneyTokenMint, - MoneyTokenFreeze, - MoneyGenesisMint, - MoneyTransfer, - MoneyOtcSwap, - MoneyPoWReward, - DaoMint, - DaoPropose, - DaoVote, - DaoExec, -} - +/// Wallet instance for a single [`Holder`] pub struct Wallet { + /// Main holder keypair pub keypair: Keypair, + /// Keypair for arbitrary token minting pub token_mint_authority: Keypair, - pub token_blind: BaseBlind, + /// Keypair for arbitrary contract deployment pub contract_deploy_authority: Keypair, + /// Holder's [`Validator`] instance pub validator: ValidatorPtr, + /// Holder's instance of the Merkle tree for the `Money` contract pub money_merkle_tree: MerkleTree, + /// Holder's instance of the Merkle tree for the `DAO` contract (holding DAO bullas) pub dao_merkle_tree: MerkleTree, + /// Holder's instance of the Merkle tree for the `DAO` contract (holding DAO proposals) pub dao_proposals_tree: MerkleTree, + /// Holder's set of unspent [`OwnCoin`]s from the `Money` contract pub unspent_money_coins: Vec, + /// Holder's set of spent [`OwnCoin`]s from the `Money` contract pub spent_money_coins: Vec, + /// Witnessed leaf positions of DAO bullas in the `dao_merkle_tree` pub dao_leafs: HashMap, - // Here the MerkleTree is the snapshotted Money tree at the time of proposal creation + /// Dao Proposal snapshots pub dao_prop_leafs: HashMap, } impl Wallet { + /// Instantiate a new [`Wallet`] instance pub async fn new( keypair: Keypair, - genesis_block: &BlockInfo, - vks: &Vks, + token_mint_authority: Keypair, + contract_deploy_authority: Keypair, + genesis_block: BlockInfo, + vks: &vks::Vks, verify_fees: bool, ) -> Result { + // Create an in-memory sled db instance for this wallet let sled_db = sled::Config::new().temporary(true).open()?; - // Use pregenerated vks and get pregenerated pks + // Inject the cached VKs into the database vks::inject(&sled_db, vks)?; - // Generate validator - // NOTE: we are not using consensus constants here so we - // don't get circular dependencies. - let config = ValidatorConfig { + // Create the `Validator` instance + let validator_config = ValidatorConfig { finalization_threshold: 3, pow_target: 90, pow_fixed_difficulty: Some(BigUint::from(1_u8)), - genesis_block: genesis_block.clone(), + genesis_block, verify_fees, }; - let validator = Validator::new(&sled_db, config).await?; + let validator = Validator::new(&sled_db, validator_config).await?; - // Create necessary Merkle trees for tracking + // The Merkle tree for the `Money` contract is initialized with a "null" + // leaf at position 0. let mut money_merkle_tree = MerkleTree::new(100); money_merkle_tree.append(MerkleNode::from(pallas::Base::ZERO)); - - let dao_merkle_tree = MerkleTree::new(100); - let dao_proposals_tree = MerkleTree::new(100); - - let unspent_money_coins = vec![]; - let spent_money_coins = vec![]; - - let token_mint_authority = Keypair::random(&mut OsRng); - let token_blind = Blind::random(&mut OsRng); - let contract_deploy_authority = Keypair::random(&mut OsRng); + money_merkle_tree.mark().unwrap(); Ok(Self { keypair, token_mint_authority, - token_blind, contract_deploy_authority, validator, money_merkle_tree, - dao_merkle_tree, - dao_proposals_tree, - unspent_money_coins, - spent_money_coins, + dao_merkle_tree: MerkleTree::new(100), + dao_proposals_tree: MerkleTree::new(100), + unspent_money_coins: vec![], + spent_money_coins: vec![], dao_leafs: HashMap::new(), dao_prop_leafs: HashMap::new(), }) } } +/// Native contract test harness instance pub struct TestHarness { + /// Initialized [`Holder`]s for this instance pub holders: HashMap, + /// Cached [`ProvingKey`]s for native contract ZK proving pub proving_keys: HashMap, - pub tx_action_benchmarks: HashMap, + /// The genesis block for this harness pub genesis_block: BlockInfo, + /// Marker to know if we're supposed to include tx fees + pub verify_fees: bool, } impl TestHarness { - pub async fn new(_contracts: &[String], verify_fees: bool) -> Result { - let mut holders = HashMap::new(); + /// Instantiate a new [`TestHarness`] given a slice of [`Holder`]s. + /// Additionally, a `verify_fees` boolean will enforce tx fee verification. + pub async fn new(holders: &[Holder], verify_fees: bool) -> Result { + // Create a genesis block let mut genesis_block = BlockInfo::default(); genesis_block.header.timestamp = Timestamp(1689772567); let producer_tx = genesis_block.txs.pop().unwrap(); @@ -198,191 +205,41 @@ impl TestHarness { // Deterministic PRNG let mut rng = Pcg32::new(42); - // Build or read precompiled zk pks and vks - let (pks, vks) = read_or_gen_vks_and_pks()?; - + // Build or read cached ZK PKs and VKs + let (pks, vks) = vks::get_cached_pks_and_vks()?; let mut proving_keys = HashMap::new(); for (bincode, namespace, pk) in pks { let mut reader = Cursor::new(pk); let zkbin = ZkBinary::decode(&bincode)?; let circuit = ZkCircuit::new(empty_witnesses(&zkbin)?, &zkbin); - let _pk = ProvingKey::read(&mut reader, circuit)?; - proving_keys.insert(namespace, (_pk, zkbin)); + let proving_key = ProvingKey::read(&mut reader, circuit)?; + proving_keys.insert(namespace, (proving_key, zkbin)); } - let faucet_kp = Keypair::random(&mut rng); - let faucet = Wallet::new(faucet_kp, &genesis_block, &vks, verify_fees).await?; - holders.insert(Holder::Faucet, faucet); + // Create `Wallet` instances + let mut holders_map = HashMap::new(); + for holder in holders { + let keypair = Keypair::random(&mut rng); + let token_mint_authority = Keypair::random(&mut rng); + let contract_deploy_authority = Keypair::random(&mut rng); - let alice_kp = Keypair::random(&mut rng); - let alice = Wallet::new(alice_kp, &genesis_block, &vks, verify_fees).await?; - holders.insert(Holder::Alice, alice); + let wallet = Wallet::new( + keypair, + token_mint_authority, + contract_deploy_authority, + genesis_block.clone(), + &vks, + verify_fees, + ) + .await?; - let bob_kp = Keypair::random(&mut rng); - let bob = Wallet::new(bob_kp, &genesis_block, &vks, verify_fees).await?; - holders.insert(Holder::Bob, bob); - - let charlie_kp = Keypair::random(&mut rng); - let charlie = Wallet::new(charlie_kp, &genesis_block, &vks, verify_fees).await?; - holders.insert(Holder::Charlie, charlie); - - let rachel_kp = Keypair::random(&mut rng); - let rachel = Wallet::new(rachel_kp, &genesis_block, &vks, verify_fees).await?; - holders.insert(Holder::Rachel, rachel); - - let dao_kp = Keypair::random(&mut rng); - let dao = Wallet::new(dao_kp, &genesis_block, &vks, verify_fees).await?; - holders.insert(Holder::Dao, dao); - - // Build benchmarks map - let mut tx_action_benchmarks = HashMap::new(); - tx_action_benchmarks.insert(TxAction::MoneyAirdrop, TxActionBenchmarks::default()); - tx_action_benchmarks.insert(TxAction::MoneyTokenMint, TxActionBenchmarks::default()); - tx_action_benchmarks.insert(TxAction::MoneyTokenFreeze, TxActionBenchmarks::default()); - tx_action_benchmarks.insert(TxAction::MoneyGenesisMint, TxActionBenchmarks::default()); - tx_action_benchmarks.insert(TxAction::MoneyOtcSwap, TxActionBenchmarks::default()); - tx_action_benchmarks.insert(TxAction::MoneyTransfer, TxActionBenchmarks::default()); - tx_action_benchmarks.insert(TxAction::MoneyPoWReward, TxActionBenchmarks::default()); - tx_action_benchmarks.insert(TxAction::DaoMint, TxActionBenchmarks::default()); - tx_action_benchmarks.insert(TxAction::DaoPropose, TxActionBenchmarks::default()); - tx_action_benchmarks.insert(TxAction::DaoVote, TxActionBenchmarks::default()); - tx_action_benchmarks.insert(TxAction::DaoExec, TxActionBenchmarks::default()); - - Ok(Self { holders, proving_keys, tx_action_benchmarks, genesis_block }) - } - - pub async fn execute_erroneous_txs( - &mut self, - action: TxAction, - holder: &Holder, - txs: &[Transaction], - block_height: u64, - erroneous: usize, - ) -> Result<()> { - let wallet = self.holders.get(holder).unwrap(); - let tx_action_benchmark = self.tx_action_benchmarks.get_mut(&action).unwrap(); - let timer = Instant::now(); - - let erroneous_txs = wallet - .validator - .add_transactions(txs, block_height, false) - .await - .err() - .unwrap() - .retrieve_erroneous_txs()?; - assert_eq!(erroneous_txs.len(), erroneous); - tx_action_benchmark.verify_times.push(timer.elapsed()); - - Ok(()) - } - - pub fn gather_owncoin( - &mut self, - holder: &Holder, - coin: &Coin, - note: &AeadEncryptedNote, - secret_key: Option, - ) -> Result { - let wallet = self.holders.get_mut(holder).unwrap(); - let leaf_position = wallet.money_merkle_tree.mark().unwrap(); - let secret_key = match secret_key { - Some(key) => key, - None => wallet.keypair.secret, - }; - - let note: MoneyNote = note.decrypt(&secret_key)?; - let oc = OwnCoin { - coin: *coin, - note: note.clone(), - secret: secret_key, - nullifier: Nullifier::from(poseidon_hash([ - wallet.keypair.secret.inner(), - coin.inner(), - ])), - leaf_position, - }; - - wallet.unspent_money_coins.push(oc.clone()); - - Ok(oc) - } - - pub fn gather_owncoin_from_output( - &mut self, - holder: &Holder, - output: &Output, - secret_key: Option, - ) -> Result { - self.gather_owncoin(holder, &output.coin, &output.note, secret_key) - } - - /// This should be used after transfer call, so we can mark the merkle tree - /// before each output coin. Assumes using wallet secret key. - pub fn gather_multiple_owncoins( - &mut self, - holder: &Holder, - outputs: &[Output], - ) -> Result> { - let wallet = self.holders.get_mut(holder).unwrap(); - let secret_key = wallet.keypair.secret; - let mut owncoins = vec![]; - for output in outputs { - wallet.money_merkle_tree.append(MerkleNode::from(output.coin.inner())); - let leaf_position = wallet.money_merkle_tree.mark().unwrap(); - - let note: MoneyNote = output.note.decrypt(&secret_key)?; - let oc = OwnCoin { - coin: output.coin, - note: note.clone(), - secret: secret_key, - nullifier: Nullifier::from(poseidon_hash([ - wallet.keypair.secret.inner(), - output.coin.inner(), - ])), - leaf_position, - }; - - wallet.unspent_money_coins.push(oc.clone()); - owncoins.push(oc); + holders_map.insert(*holder, wallet); } - Ok(owncoins) - } - - pub fn gather_owncoin_at_index( - &mut self, - holder: &Holder, - outputs: &[Output], - index: usize, - ) -> Result { - let wallet = self.holders.get_mut(holder).unwrap(); - let secret_key = wallet.keypair.secret; - let mut owncoin = None; - for (i, output) in outputs.iter().enumerate() { - wallet.money_merkle_tree.append(MerkleNode::from(output.coin.inner())); - if i == index { - let leaf_position = wallet.money_merkle_tree.mark().unwrap(); - - let note: MoneyNote = output.note.decrypt(&secret_key)?; - let oc = OwnCoin { - coin: output.coin, - note: note.clone(), - secret: secret_key, - nullifier: Nullifier::from(poseidon_hash([ - wallet.keypair.secret.inner(), - output.coin.inner(), - ])), - leaf_position, - }; - - wallet.unspent_money_coins.push(oc.clone()); - owncoin = Some(oc); - } - } - - Ok(owncoin.unwrap()) + Ok(Self { holders: holders_map, proving_keys, genesis_block, verify_fees }) } + /// Assert that all holders' trees are the same pub fn assert_trees(&self, holders: &[Holder]) { assert!(holders.len() > 1); // Gather wallets @@ -397,17 +254,4 @@ impl TestHarness { assert!(money_root == wallet.money_merkle_tree.root(0).unwrap()); } } - - pub fn contract_id(&self, holder: &Holder) -> ContractId { - let holder = self.holders.get(holder).unwrap(); - ContractId::derive_public(holder.contract_deploy_authority.public) - } - - pub fn statistics(&self) { - info!("==================== Statistics ===================="); - for (action, tx_action_benchmark) in &self.tx_action_benchmarks { - tx_action_benchmark.statistics(action); - } - info!("===================================================="); - } } diff --git a/src/contract/test-harness/src/money_fee.rs b/src/contract/test-harness/src/money_fee.rs new file mode 100644 index 000000000..1735663c7 --- /dev/null +++ b/src/contract/test-harness/src/money_fee.rs @@ -0,0 +1,317 @@ +/* This file is part of DarkFi (https://dark.fi) + * + * Copyright (C) 2020-2024 Dyne.org foundation + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +use std::{collections::HashSet, hash::RandomState}; + +use darkfi::{ + tx::{ContractCallLeaf, Transaction, TransactionBuilder}, + zk::{halo2::Field, Proof}, + Result, +}; +use darkfi_money_contract::{ + client::{ + compute_remainder_blind, + fee_v1::{create_fee_proof, FeeCallInput, FeeCallOutput, FEE_CALL_GAS}, + MoneyNote, OwnCoin, + }, + model::{Input, MoneyFeeParamsV1, Output}, + MoneyFunction, MONEY_CONTRACT_ZKAS_FEE_NS_V1, +}; +use darkfi_sdk::{ + crypto::{ + contract_id::MONEY_CONTRACT_ID, note::AeadEncryptedNote, token_id::DARK_TOKEN_ID, FuncId, + MerkleNode, SecretKey, + }, + pasta::pallas, + ContractCall, +}; +use darkfi_serial::AsyncEncodable; +use log::{debug, info}; +use rand::rngs::OsRng; + +use super::{Holder, TestHarness}; + +impl TestHarness { + /// Create an empty transaction that includes a `Money::Fee` call. + /// This is generally used to test the actual fee call, and also to + /// see the gas usage of the call without other parts. + pub async fn create_empty_fee_call( + &mut self, + holder: &Holder, + ) -> Result<(Transaction, MoneyFeeParamsV1)> { + let wallet = self.holders.get(holder).unwrap(); + + // Find a compatible OwnCoin + let coin = wallet + .unspent_money_coins + .iter() + .find(|x| x.note.token_id == *DARK_TOKEN_ID && x.note.value > FEE_CALL_GAS) + .unwrap(); + + // Input and output setup + let input = FeeCallInput { + leaf_position: coin.leaf_position, + merkle_path: wallet.money_merkle_tree.witness(coin.leaf_position, 0).unwrap(), + secret: coin.secret, + note: coin.note.clone(), + user_data_blind: pallas::Base::random(&mut OsRng), + }; + + let output = FeeCallOutput { + public_key: wallet.keypair.public, + value: coin.note.value - FEE_CALL_GAS, + token_id: coin.note.token_id, + blind: pallas::Base::random(&mut OsRng), + spend_hook: FuncId::none(), + user_data: pallas::Base::ZERO, + }; + + // Generate blinding factors + let token_blind = pallas::Base::random(&mut OsRng); + let input_value_blind = pallas::Scalar::random(&mut OsRng); + let fee_value_blind = pallas::Scalar::random(&mut OsRng); + let output_value_blind = compute_remainder_blind(&[input_value_blind], &[fee_value_blind]); + + // Generate an ephemeral signing key + let signature_secret = SecretKey::random(&mut OsRng); + + info!("Creting FeeV1 ZK proof"); + let (fee_pk, fee_zkbin) = + self.proving_keys.get(&MONEY_CONTRACT_ZKAS_FEE_NS_V1.to_string()).unwrap(); + + let (proof, public_inputs) = create_fee_proof( + fee_zkbin, + fee_pk, + &input, + input_value_blind, + &output, + output_value_blind, + output.spend_hook, + output.user_data, + output.blind, + token_blind, + signature_secret, + )?; + + // Encrypted note for the output + let note = MoneyNote { + coin_blind: output.blind, + value: output.value, + token_id: output.token_id, + spend_hook: output.spend_hook, + user_data: output.user_data, + value_blind: output_value_blind, + token_blind, + memo: vec![], + }; + + let encrypted_note = AeadEncryptedNote::encrypt(¬e, &output.public_key, &mut OsRng)?; + + let params = MoneyFeeParamsV1 { + input: Input { + value_commit: public_inputs.input_value_commit, + token_commit: public_inputs.token_commit, + nullifier: public_inputs.nullifier, + merkle_root: public_inputs.merkle_root, + user_data_enc: public_inputs.input_user_data_enc, + signature_public: public_inputs.signature_public, + }, + output: Output { + value_commit: public_inputs.output_value_commit, + token_commit: public_inputs.token_commit, + coin: public_inputs.output_coin, + note: encrypted_note, + }, + fee_value_blind, + token_blind, + }; + + let mut data = vec![MoneyFunction::FeeV1 as u8]; + FEE_CALL_GAS.encode_async(&mut data).await?; + params.encode_async(&mut data).await?; + let call = ContractCall { contract_id: *MONEY_CONTRACT_ID, data }; + let mut tx_builder = + TransactionBuilder::new(ContractCallLeaf { call, proofs: vec![proof] }, vec![])?; + let mut tx = tx_builder.build()?; + let sigs = tx.create_sigs(&mut OsRng, &[signature_secret])?; + tx.signatures = vec![sigs]; + + Ok((tx, params)) + } + + /// Execute the transaction created by `create_empty_fee_call()` for a given [`Holder`] + /// + /// Returns any found [`OwnCoin`]s. + pub async fn execute_empty_fee_call_tx( + &mut self, + holder: &Holder, + tx: Transaction, + params: &MoneyFeeParamsV1, + block_height: u64, + ) -> Result> { + let wallet = self.holders.get_mut(holder).unwrap(); + + wallet.validator.add_transactions(&[tx], block_height, true, self.verify_fees).await?; + wallet.money_merkle_tree.append(MerkleNode::from(params.output.coin.inner())); + + // Attempt to decrypt the output note to see if this is a coin for the holder + let Ok(note) = params.output.note.decrypt::(&wallet.keypair.secret) else { + return Ok(vec![]) + }; + + let owncoin = OwnCoin { + coin: params.output.coin, + note: note.clone(), + secret: wallet.keypair.secret, + leaf_position: wallet.money_merkle_tree.mark().unwrap(), + }; + + let spent_coin = wallet + .unspent_money_coins + .iter() + .find(|x| x.nullifier() == params.input.nullifier) + .unwrap() + .clone(); + + debug!("Found spent OwnCoin({}) for {:?}", spent_coin.coin, holder); + debug!("Found new OwnCoin({}) for {:?}", owncoin.coin, holder); + + wallet.unspent_money_coins.retain(|x| x.nullifier() != params.input.nullifier); + wallet.spent_money_coins.push(spent_coin); + wallet.unspent_money_coins.push(owncoin.clone()); + + Ok(vec![owncoin]) + } + + /// Create and append a `Money::Fee` call to a given [`Transaction`] for + /// a given [`Holder`]. + /// + /// Additionally takes a set of spent coins in order not to reuse them here. + /// + /// Returns the `Fee` call, and all necessary data and parameters related. + pub async fn append_fee_call( + &mut self, + holder: &Holder, + tx: Transaction, + block_height: u64, + spent_coins: &[OwnCoin], + ) -> Result<(ContractCall, Vec, Vec, Vec, MoneyFeeParamsV1)> { + // First we verify the fee-less transaction to see how much gas it uses for execution + // and verification. + let wallet = self.holders.get(holder).unwrap(); + let mut gas_used = FEE_CALL_GAS; + gas_used += wallet.validator.add_transactions(&[tx], block_height, false, false).await?; + + // Knowing the total gas, we can now find an OwnCoin of enough value + // so that we can create a valid Money::Fee call. + let spent_coins: HashSet<&OwnCoin, RandomState> = HashSet::from_iter(spent_coins); + let mut available_coins = wallet.unspent_money_coins.clone(); + available_coins.retain(|x| x.note.token_id == *DARK_TOKEN_ID && x.note.value > gas_used); + available_coins.retain(|x| !spent_coins.contains(x)); + assert!(!available_coins.is_empty()); + + let coin = &available_coins[0]; + let change_value = coin.note.value - gas_used; + + // Input and output setup + let input = FeeCallInput { + leaf_position: coin.leaf_position, + merkle_path: wallet.money_merkle_tree.witness(coin.leaf_position, 0).unwrap(), + secret: coin.secret, + note: coin.note.clone(), + user_data_blind: pallas::Base::random(&mut OsRng), + }; + + let output = FeeCallOutput { + public_key: wallet.keypair.public, + value: change_value, + token_id: coin.note.token_id, + blind: pallas::Base::random(&mut OsRng), + spend_hook: FuncId::none(), + user_data: pallas::Base::ZERO, + }; + + // Create blinding factors + let token_blind = pallas::Base::random(&mut OsRng); + let input_value_blind = pallas::Scalar::random(&mut OsRng); + let fee_value_blind = pallas::Scalar::random(&mut OsRng); + let output_value_blind = compute_remainder_blind(&[input_value_blind], &[fee_value_blind]); + + // Create an ephemeral signing key + let signature_secret = SecretKey::random(&mut OsRng); + + info!("Creating FeeV1 ZK proof"); + let (fee_pk, fee_zkbin) = + self.proving_keys.get(&MONEY_CONTRACT_ZKAS_FEE_NS_V1.to_string()).unwrap(); + + let (proof, public_inputs) = create_fee_proof( + fee_zkbin, + fee_pk, + &input, + input_value_blind, + &output, + output_value_blind, + output.spend_hook, + output.user_data, + output.blind, + token_blind, + signature_secret, + )?; + + // Encrypted note for the output + let note = MoneyNote { + coin_blind: output.blind, + value: output.value, + token_id: output.token_id, + spend_hook: output.spend_hook, + user_data: output.user_data, + value_blind: output_value_blind, + token_blind, + memo: vec![], + }; + + let encrypted_note = AeadEncryptedNote::encrypt(¬e, &output.public_key, &mut OsRng)?; + + let params = MoneyFeeParamsV1 { + input: Input { + value_commit: public_inputs.input_value_commit, + token_commit: public_inputs.token_commit, + nullifier: public_inputs.nullifier, + merkle_root: public_inputs.merkle_root, + user_data_enc: public_inputs.input_user_data_enc, + signature_public: public_inputs.signature_public, + }, + output: Output { + value_commit: public_inputs.output_value_commit, + token_commit: public_inputs.token_commit, + coin: public_inputs.output_coin, + note: encrypted_note, + }, + fee_value_blind, + token_blind, + }; + + // Encode the contract call + let mut data = vec![MoneyFunction::FeeV1 as u8]; + gas_used.encode_async(&mut data).await?; + params.encode_async(&mut data).await?; + let call = ContractCall { contract_id: *MONEY_CONTRACT_ID, data }; + + Ok((call, vec![proof], vec![signature_secret], vec![coin.clone()], params)) + } +} diff --git a/src/contract/test-harness/src/money_genesis_mint.rs b/src/contract/test-harness/src/money_genesis_mint.rs index b0336c9b9..08310ed04 100644 --- a/src/contract/test-harness/src/money_genesis_mint.rs +++ b/src/contract/test-harness/src/money_genesis_mint.rs @@ -16,28 +16,32 @@ * along with this program. If not, see . */ -use std::time::Instant; - use darkfi::{ tx::{ContractCallLeaf, Transaction, TransactionBuilder}, + zk::halo2::Field, Result, }; use darkfi_money_contract::{ - client::genesis_mint_v1::GenesisMintCallBuilder, model::MoneyGenesisMintParamsV1, + client::{genesis_mint_v1::GenesisMintCallBuilder, MoneyNote, OwnCoin}, + model::MoneyGenesisMintParamsV1, MoneyFunction, MONEY_CONTRACT_ZKAS_MINT_NS_V1, }; use darkfi_sdk::{ - crypto::{FuncId, MerkleNode, MONEY_CONTRACT_ID}, + crypto::{contract_id::MONEY_CONTRACT_ID, FuncId, MerkleNode}, pasta::pallas, ContractCall, }; -use darkfi_serial::{serialize, Encodable}; +use darkfi_serial::AsyncEncodable; +use log::debug; use rand::rngs::OsRng; -use super::{Holder, TestHarness, TxAction}; +use super::{Holder, TestHarness}; impl TestHarness { - pub fn genesis_mint( + /// Create a `Money::GenesisMint` transaction for a given [`Holder`]. + /// + /// Returns the created [`Transaction`] and its parameters. + pub async fn genesis_mint( &mut self, holder: &Holder, amount: u64, @@ -47,63 +51,67 @@ impl TestHarness { let (mint_pk, mint_zkbin) = self.proving_keys.get(&MONEY_CONTRACT_ZKAS_MINT_NS_V1.to_string()).unwrap(); - let tx_action_benchmark = - self.tx_action_benchmarks.get_mut(&TxAction::MoneyGenesisMint).unwrap(); - - let timer = Instant::now(); - - // We're just going to be using a zero spend-hook and user-data - let spend_hook = FuncId::none(); - let user_data = pallas::Base::zero(); - + // Build the contract call let builder = GenesisMintCallBuilder { keypair: wallet.keypair, amount, - spend_hook, - user_data, + spend_hook: FuncId::none(), + user_data: pallas::Base::ZERO, mint_zkbin: mint_zkbin.clone(), mint_pk: mint_pk.clone(), }; let debris = builder.build()?; + // Encode and build the transaction let mut data = vec![MoneyFunction::GenesisMintV1 as u8]; - debris.params.encode(&mut data)?; + debris.params.encode_async(&mut data).await?; let call = ContractCall { contract_id: *MONEY_CONTRACT_ID, data }; let mut tx_builder = TransactionBuilder::new(ContractCallLeaf { call, proofs: debris.proofs }, vec![])?; let mut tx = tx_builder.build()?; let sigs = tx.create_sigs(&mut OsRng, &[wallet.keypair.secret])?; tx.signatures = vec![sigs]; - tx_action_benchmark.creation_times.push(timer.elapsed()); - - // Calculate transaction sizes - let encoded: Vec = serialize(&tx); - let size = std::mem::size_of_val(&*encoded); - tx_action_benchmark.sizes.push(size); - let base58 = bs58::encode(&encoded).into_string(); - let size = std::mem::size_of_val(&*base58); - tx_action_benchmark.broadcasted_sizes.push(size); Ok((tx, debris.params)) } + /// Execute the [`Transaction`] created by `genesis_mint()`. + /// + /// Returns any found [`OwnCoin`]s. pub async fn execute_genesis_mint_tx( &mut self, holder: &Holder, - tx: &Transaction, + tx: Transaction, params: &MoneyGenesisMintParamsV1, block_height: u64, - ) -> Result<()> { + append: bool, + ) -> Result> { let wallet = self.holders.get_mut(holder).unwrap(); - let tx_action_benchmark = - self.tx_action_benchmarks.get_mut(&TxAction::MoneyGenesisMint).unwrap(); - let timer = Instant::now(); - wallet.validator.add_transactions(&[tx.clone()], block_height, true).await?; + // Execute the transaction + wallet.validator.add_transactions(&[tx], block_height, true, self.verify_fees).await?; + + if !append { + return Ok(vec![]) + } + wallet.money_merkle_tree.append(MerkleNode::from(params.output.coin.inner())); - tx_action_benchmark.verify_times.push(timer.elapsed()); - Ok(()) + let Ok(note) = params.output.note.decrypt::(&wallet.keypair.secret) else { + return Ok(vec![]) + }; + + let owncoin = OwnCoin { + coin: params.output.coin, + note: note.clone(), + secret: wallet.keypair.secret, + leaf_position: wallet.money_merkle_tree.mark().unwrap(), + }; + + debug!("Found new OwnCoin({}) for {:?}", owncoin.coin, holder); + wallet.unspent_money_coins.push(owncoin.clone()); + + Ok(vec![owncoin]) } } diff --git a/src/contract/test-harness/src/money_otc_swap.rs b/src/contract/test-harness/src/money_otc_swap.rs index 857aa0386..b4916833b 100644 --- a/src/contract/test-harness/src/money_otc_swap.rs +++ b/src/contract/test-harness/src/money_otc_swap.rs @@ -16,36 +16,39 @@ * along with this program. If not, see . */ -use std::time::Instant; - use darkfi::{ tx::{ContractCallLeaf, Transaction, TransactionBuilder}, zk::halo2::Field, Result, }; use darkfi_money_contract::{ - client::{swap_v1::SwapCallBuilder, OwnCoin}, - model::MoneyTransferParamsV1, + client::{swap_v1::SwapCallBuilder, MoneyNote, OwnCoin}, + model::{MoneyFeeParamsV1, MoneyTransferParamsV1}, MoneyFunction, MONEY_CONTRACT_ZKAS_BURN_NS_V1, MONEY_CONTRACT_ZKAS_MINT_NS_V1, }; use darkfi_sdk::{ - crypto::{Blind, FuncId, MerkleNode, MONEY_CONTRACT_ID}, + crypto::{contract_id::MONEY_CONTRACT_ID, Blind, FuncId, MerkleNode, MONEY_CONTRACT_ID}, pasta::pallas, ContractCall, }; -use darkfi_serial::{serialize, Encodable}; +use darkfi_serial::AsyncEncodable; +use log::debug; use rand::rngs::OsRng; -use super::{Holder, TestHarness, TxAction}; +use super::{Holder, TestHarness}; impl TestHarness { - pub fn otc_swap( + /// Create a `Money::OtcSwap` transaction with two given [`Holder`]s. + /// + /// Returns the [`Transaction`], and the transaction parameters. + pub async fn otc_swap( &mut self, holder0: &Holder, owncoin0: &OwnCoin, holder1: &Holder, owncoin1: &OwnCoin, - ) -> Result<(Transaction, MoneyTransferParamsV1)> { + block_height: u64, + ) -> Result<(Transaction, MoneyTransferParamsV1, Option)> { let wallet0 = self.holders.get(holder0).unwrap(); let wallet1 = self.holders.get(holder1).unwrap(); @@ -55,23 +58,18 @@ impl TestHarness { let (burn_pk, burn_zkbin) = self.proving_keys.get(&MONEY_CONTRACT_ZKAS_BURN_NS_V1.to_string()).unwrap(); - let tx_action_benchmark = - self.tx_action_benchmarks.get_mut(&TxAction::MoneyOtcSwap).unwrap(); - - let timer = Instant::now(); - - // We're just going to be using a zero spend-hook and user-data + // Use a zero spend_hook and user_data let rcpt_spend_hook = FuncId::none(); let rcpt_user_data = pallas::Base::ZERO; let rcpt_user_data_blind = Blind::random(&mut OsRng); - // Generating swap blinds + // Create blinding factors for commitments let value_send_blind = Blind::random(&mut OsRng); let value_recv_blind = Blind::random(&mut OsRng); - let token_send_blind = Blind::random(&mut OsRng); - let token_recv_blind = Blind::random(&mut OsRng); + let token_send_blind = BaseBlind::random(&mut OsRng); + let token_recv_blind = BaseBlind::random(&mut OsRng); - // Builder first holder part + // Build the first half of the swap for Holder0 let builder = SwapCallBuilder { pubkey: wallet0.keypair.public, value_send: owncoin0.note.value, @@ -92,11 +90,10 @@ impl TestHarness { }; let debris0 = builder.build()?; - assert!(debris0.params.inputs.len() == 1); assert!(debris0.params.outputs.len() == 1); - // Builder second holder part + // Build the second half of the swap for Holder1 let builder = SwapCallBuilder { pubkey: wallet1.keypair.public, value_send: owncoin1.note.value, @@ -117,11 +114,10 @@ impl TestHarness { }; let debris1 = builder.build()?; - assert!(debris1.params.inputs.len() == 1); assert!(debris1.params.outputs.len() == 1); - // Then second holder combines the halves + // Holder1 then combines the halves let swap_full_params = MoneyTransferParamsV1 { inputs: vec![debris0.params.inputs[0].clone(), debris1.params.inputs[0].clone()], outputs: vec![debris0.params.outputs[0].clone(), debris1.params.outputs[0].clone()], @@ -134,53 +130,113 @@ impl TestHarness { debris1.proofs[1].clone(), ]; - // And signs the transaction + // Encode the contract call let mut data = vec![MoneyFunction::OtcSwapV1 as u8]; - swap_full_params.encode(&mut data)?; + swap_full_params.encode_async(&mut data).await?; let call = ContractCall { contract_id: *MONEY_CONTRACT_ID, data }; let mut tx_builder = TransactionBuilder::new(ContractCallLeaf { call, proofs: swap_full_proofs }, vec![])?; + + // If we have tx fees enabled, make an offering + let mut fee_params = None; + let mut fee_signature_secrets = None; + if self.verify_fees { + let mut tx = tx_builder.build()?; + let sigs = tx.create_sigs(&mut OsRng, &[debris1.signature_secret])?; + tx.signatures = vec![sigs]; + + // First holder gets the partially signed transaction and adds their signature + let sigs = tx.create_sigs(&mut OsRng, &[debris0.signature_secret])?; + tx.signatures[0].insert(0, sigs[0]); + + let (fee_call, fee_proofs, fee_secrets, _spent_fee_coins, fee_call_params) = + self.append_fee_call(holder0, tx, block_height, &[owncoin0.clone()]).await?; + + // Append the fee call to the transaction + tx_builder.append(ContractCallLeaf { call: fee_call, proofs: fee_proofs }, vec![])?; + fee_signature_secrets = Some(fee_secrets); + fee_params = Some(fee_call_params); + } + + // Now build the actual transaction and sign it with necessary keys. let mut tx = tx_builder.build()?; let sigs = tx.create_sigs(&mut OsRng, &[debris1.signature_secret])?; tx.signatures = vec![sigs]; - // First holder gets the partially signed transaction and adds their signature let sigs = tx.create_sigs(&mut OsRng, &[debris0.signature_secret])?; tx.signatures[0].insert(0, sigs[0]); - tx_action_benchmark.creation_times.push(timer.elapsed()); - // Calculate transaction sizes - let encoded: Vec = serialize(&tx); - let size = std::mem::size_of_val(&*encoded); - tx_action_benchmark.sizes.push(size); - let base58 = bs58::encode(&encoded).into_string(); - let size = std::mem::size_of_val(&*base58); - tx_action_benchmark.broadcasted_sizes.push(size); + if let Some(fee_signature_secrets) = fee_signature_secrets { + let sigs = tx.create_sigs(&mut OsRng, &fee_signature_secrets)?; + tx.signatures.push(sigs); + } - Ok((tx, swap_full_params)) + Ok((tx, swap_full_params, fee_params)) } + /// Execute the transaction created by `otc_swap()` for a given [`Holder`]. + /// + /// Returns any found [`OwnCoin`]s. pub async fn execute_otc_swap_tx( &mut self, holder: &Holder, - tx: &Transaction, - params: &MoneyTransferParamsV1, + tx: Transaction, + swap_params: &MoneyTransferParamsV1, + fee_params: &Option, block_height: u64, append: bool, - ) -> Result<()> { + ) -> Result> { let wallet = self.holders.get_mut(holder).unwrap(); - let tx_action_benchmark = - self.tx_action_benchmarks.get_mut(&TxAction::MoneyOtcSwap).unwrap(); - let timer = Instant::now(); - wallet.validator.add_transactions(&[tx.clone()], block_height, true).await?; - if append { - for output in ¶ms.outputs { - wallet.money_merkle_tree.append(MerkleNode::from(output.coin.inner())); + // Execute the transaction + wallet.validator.add_transactions(&[tx], block_height, true, self.verify_fees).await?; + + let mut found_owncoins = vec![]; + + if !append { + return Ok(found_owncoins) + } + + let mut inputs = swap_params.inputs.to_vec(); + let mut outputs = swap_params.outputs.to_vec(); + if let Some(ref fee_params) = fee_params { + inputs.push(fee_params.input.clone()); + outputs.push(fee_params.output.clone()); + } + + for input in inputs { + if let Some(spent_coin) = wallet + .unspent_money_coins + .iter() + .find(|x| x.nullifier() == input.nullifier) + .cloned() + { + debug!("Found spent OwnCoin({}) for {:?}", spent_coin.coin, holder); + wallet.unspent_money_coins.retain(|x| x.nullifier() != input.nullifier); + wallet.spent_money_coins.push(spent_coin.clone()); } } - tx_action_benchmark.verify_times.push(timer.elapsed()); - Ok(()) + for output in outputs { + wallet.money_merkle_tree.append(MerkleNode::from(output.coin.inner())); + + // Attempt to decrypt the encrypted note + let Ok(note) = output.note.decrypt::(&wallet.keypair.secret) else { + continue + }; + + let owncoin = OwnCoin { + coin: output.coin, + note: note.clone(), + secret: wallet.keypair.secret, + leaf_position: wallet.money_merkle_tree.mark().unwrap(), + }; + + debug!("Found new OwnCoin({}) for {:?}", owncoin.coin, holder); + wallet.unspent_money_coins.push(owncoin.clone()); + found_owncoins.push(owncoin); + } + + Ok(found_owncoins) } } diff --git a/src/contract/test-harness/src/money_pow_reward.rs b/src/contract/test-harness/src/money_pow_reward.rs index d08e0191b..db333fd87 100644 --- a/src/contract/test-harness/src/money_pow_reward.rs +++ b/src/contract/test-harness/src/money_pow_reward.rs @@ -16,32 +16,35 @@ * along with this program. If not, see . */ -use std::time::Instant; - use darkfi::{ tx::{ContractCallLeaf, Transaction, TransactionBuilder}, + zk::halo2::Field, Result, }; use darkfi_money_contract::{ - client::pow_reward_v1::PoWRewardCallBuilder, model::MoneyPoWRewardParamsV1, MoneyFunction, - MONEY_CONTRACT_ZKAS_MINT_NS_V1, + client::{pow_reward_v1::PoWRewardCallBuilder, MoneyNote, OwnCoin}, + model::MoneyPoWRewardParamsV1, + MoneyFunction, MONEY_CONTRACT_ZKAS_MINT_NS_V1, }; use darkfi_sdk::{ - crypto::{FuncId, MerkleNode, MONEY_CONTRACT_ID}, + crypto::{contract_id::MONEY_CONTRACT_ID, FuncId, MerkleNode}, pasta::pallas, ContractCall, }; -use darkfi_serial::{serialize, Encodable}; +use darkfi_serial::AsyncEncodable; use rand::rngs::OsRng; -use super::{Holder, TestHarness, TxAction}; +use super::{Holder, TestHarness}; impl TestHarness { - pub fn pow_reward( + /// Create a `Money::PoWReward` transaction for a given [`Holder`]. + /// + /// Optionally takes a specific reward recipient and a nonstandard reward value. + /// Returns the created [`Transaction`] and [`MoneyPoWRewardParamsV1`]. + pub async fn pow_reward( &mut self, holder: &Holder, recipient: Option<&Holder>, - block_height: u64, reward: Option, ) -> Result<(Transaction, MoneyPoWRewardParamsV1)> { let wallet = self.holders.get(holder).unwrap(); @@ -49,34 +52,26 @@ impl TestHarness { let (mint_pk, mint_zkbin) = self.proving_keys.get(&MONEY_CONTRACT_ZKAS_MINT_NS_V1.to_string()).unwrap(); - let tx_action_benchmark = - self.tx_action_benchmarks.get_mut(&TxAction::MoneyPoWReward).unwrap(); - - let timer = Instant::now(); - - // Proposals always extend genesis block - let last_nonce = self.genesis_block.header.nonce; - let fork_previous_hash = self.genesis_block.header.previous; - - // We're just going to be using a zero spend-hook and user-data - let spend_hook = FuncId::none(); - let user_data = pallas::Base::zero(); + // Reference the last block in the holder's blockchain + let (block_height, fork_previous_hash) = wallet.validator.blockchain.last()?; + let last_block = wallet.validator.blockchain.last_block()?; + // If there's a set reward recipient, use it, otherwise reward the holder let recipient = if let Some(holder) = recipient { - let holder = self.holders.get(holder).unwrap(); - holder.keypair.public + self.holders.get(holder).unwrap().keypair.public } else { wallet.keypair.public }; + // Build the transaction let builder = PoWRewardCallBuilder { secret: wallet.keypair.secret, recipient, - block_height, - last_nonce, + block_height: block_height + 1, + last_nonce: last_block.header.nonce, fork_previous_hash, - spend_hook, - user_data, + spend_hook: FuncId::none(), + user_data: pallas::Base::ZERO, mint_zkbin: mint_zkbin.clone(), mint_pk: mint_pk.clone(), }; @@ -86,64 +81,47 @@ impl TestHarness { None => builder.build()?, }; + // Encode the transaction let mut data = vec![MoneyFunction::PoWRewardV1 as u8]; - debris.params.encode(&mut data)?; + debris.params.encode_async(&mut data).await?; let call = ContractCall { contract_id: *MONEY_CONTRACT_ID, data }; let mut tx_builder = TransactionBuilder::new(ContractCallLeaf { call, proofs: debris.proofs }, vec![])?; let mut tx = tx_builder.build()?; let sigs = tx.create_sigs(&mut OsRng, &[wallet.keypair.secret])?; tx.signatures = vec![sigs]; - tx_action_benchmark.creation_times.push(timer.elapsed()); - - // Calculate transaction sizes - let encoded: Vec = serialize(&tx); - let size = std::mem::size_of_val(&*encoded); - tx_action_benchmark.sizes.push(size); - let base58 = bs58::encode(&encoded).into_string(); - let size = std::mem::size_of_val(&*base58); - tx_action_benchmark.broadcasted_sizes.push(size); Ok((tx, debris.params)) } + /// Execute the transaction created by `pow_reward()` for a given [`Holder`]. + /// + /// Returns any gathered [`OwnCoin`]s from the transaction. pub async fn execute_pow_reward_tx( &mut self, holder: &Holder, tx: &Transaction, params: &MoneyPoWRewardParamsV1, block_height: u64, - ) -> Result<()> { + ) -> Result> { let wallet = self.holders.get_mut(holder).unwrap(); - let tx_action_benchmark = - self.tx_action_benchmarks.get_mut(&TxAction::MoneyPoWReward).unwrap(); - let timer = Instant::now(); wallet.validator.add_test_producer_transaction(tx, block_height, true).await?; wallet.money_merkle_tree.append(MerkleNode::from(params.output.coin.inner())); - tx_action_benchmark.verify_times.push(timer.elapsed()); - Ok(()) - } + // Attempt to decrypt the output note to see if this is a coin for the holder. + let Ok(note) = params.output.note.decrypt::(&wallet.keypair.secret) else { + return Ok(vec![]) + }; - pub async fn execute_erroneous_pow_reward_tx( - &mut self, - holder: &Holder, - tx: &Transaction, - block_height: u64, - ) -> Result<()> { - let wallet = self.holders.get_mut(holder).unwrap(); - let tx_action_benchmark = - self.tx_action_benchmarks.get_mut(&TxAction::MoneyPoWReward).unwrap(); - let timer = Instant::now(); + let owncoin = OwnCoin { + coin: params.output.coin, + note: note.clone(), + secret: wallet.keypair.secret, + leaf_position: wallet.money_merkle_tree.mark().unwrap(), + }; - assert!(wallet - .validator - .add_test_producer_transaction(tx, block_height, true) - .await - .is_err()); - tx_action_benchmark.verify_times.push(timer.elapsed()); - - Ok(()) + wallet.unspent_money_coins.push(owncoin.clone()); + Ok(vec![owncoin]) } } diff --git a/src/contract/test-harness/src/money_token.rs b/src/contract/test-harness/src/money_token.rs index 0ac8d51b1..ca9a2556b 100644 --- a/src/contract/test-harness/src/money_token.rs +++ b/src/contract/test-harness/src/money_token.rs @@ -16,8 +16,6 @@ * along with this program. If not, see . */ -use std::time::Instant; - use darkfi::{ tx::{ContractCallLeaf, Transaction, TransactionBuilder}, zk::halo2::Field, @@ -26,68 +24,72 @@ use darkfi::{ use darkfi_money_contract::{ client::{ auth_token_mint_v1::AuthTokenMintCallBuilder, token_freeze_v1::TokenFreezeCallBuilder, - token_mint_v1::TokenMintCallBuilder, + token_mint_v1::TokenMintCallBuilder, MoneyNote, OwnCoin, }, model::{ - CoinAttributes, MoneyAuthTokenMintParamsV1, MoneyTokenFreezeParamsV1, + CoinAttributes, MoneyAuthTokenMintParamsV1, MoneyFeeParamsV1, MoneyTokenFreezeParamsV1, MoneyTokenMintParamsV1, TokenAttributes, }, MoneyFunction, MONEY_CONTRACT_ZKAS_AUTH_TOKEN_MINT_NS_V1, MONEY_CONTRACT_ZKAS_TOKEN_FRZ_NS_V1, MONEY_CONTRACT_ZKAS_TOKEN_MINT_NS_V1, }; use darkfi_sdk::{ - crypto::{poseidon_hash, Blind, FuncId, FuncRef, MerkleNode, MONEY_CONTRACT_ID}, - dark_tree::DarkLeaf, + crypto::{ + contract_id::MONEY_CONTRACT_ID, poseidon_hash, Blind, FuncId, FuncRef, MerkleNode, + MONEY_CONTRACT_ID, + }, + dark_tree::{DarkLeaf, DarkTree}, pasta::pallas, ContractCall, }; -use darkfi_serial::{serialize, Encodable}; +use darkfi_serial::AsyncEncodable; +use log::debug; use rand::rngs::OsRng; -use super::{Holder, TestHarness, TxAction}; +use super::{Holder, TestHarness}; impl TestHarness { - pub fn token_mint( + /// Mint an arbitrary token for a given recipient using `Money::TokenMint` + pub async fn token_mint( &mut self, amount: u64, holder: &Holder, recipient: &Holder, spend_hook: Option, user_data: Option, - ) -> Result<(Transaction, MoneyTokenMintParamsV1, MoneyAuthTokenMintParamsV1)> { + block_height: u64, + ) -> Result<( + Transaction, + MoneyTokenMintParamsV1, + MoneyAuthTokenMintParamsV1, + Option, + )> { let wallet = self.holders.get(holder).unwrap(); let mint_authority = wallet.token_mint_authority; - let token_blind = wallet.token_blind; - let rcpt = self.holders.get(recipient).unwrap().keypair.public; - let (mint_pk, mint_zkbin) = self - .proving_keys - .get(&MONEY_CONTRACT_ZKAS_TOKEN_MINT_NS_V1.to_string()) - .unwrap() - .clone(); - let (auth_mint_pk, auth_mint_zkbin) = self - .proving_keys - .get(&MONEY_CONTRACT_ZKAS_AUTH_TOKEN_MINT_NS_V1.to_string()) - .unwrap() - .clone(); + let (token_mint_pk, token_mint_zkbin) = + self.proving_keys.get(&MONEY_CONTRACT_ZKAS_TOKEN_MINT_NS_V1.to_string()).unwrap(); - let tx_action_benchmark = - self.tx_action_benchmarks.get_mut(&TxAction::MoneyTokenMint).unwrap(); - - let timer = Instant::now(); + let (auth_mint_pk, auth_mint_zkbin) = + self.proving_keys.get(&MONEY_CONTRACT_ZKAS_AUTH_TOKEN_MINT_NS_V1.to_string()).unwrap(); + // Create the Auth FuncID let auth_func_id = FuncRef { contract_id: *MONEY_CONTRACT_ID, func_code: MoneyFunction::AuthTokenMintV1 as u8, } .to_func_id(); + let (mint_auth_x, mint_auth_y) = mint_authority.public.xy(); + let token_blind = pallas::Base::random(&mut OsRng); + let token_attrs = TokenAttributes { auth_parent: auth_func_id, - user_data: poseidon_hash([mint_authority.public.x(), mint_authority.public.y()]), + user_data: poseidon_hash([mint_auth_x, mint_auth_y]), blind: token_blind, }; + let token_id = token_attrs.to_token_id(); let coin_attrs = CoinAttributes { @@ -99,146 +101,280 @@ impl TestHarness { blind: Blind::random(&mut OsRng), }; + // Create the minting call let builder = TokenMintCallBuilder { coin_attrs: coin_attrs.clone(), token_attrs: token_attrs.clone(), - mint_zkbin, - mint_pk, + mint_zkbin: token_mint_zkbin.clone(), + mint_pk: token_mint_pk.clone(), }; let mint_debris = builder.build()?; let mut data = vec![MoneyFunction::TokenMintV1 as u8]; - mint_debris.params.encode(&mut data)?; + mint_debris.params.encode_async(&mut data).await?; let mint_call = ContractCall { contract_id: *MONEY_CONTRACT_ID, data }; + // Create the auth call let builder = AuthTokenMintCallBuilder { coin_attrs, token_attrs, mint_keypair: mint_authority, - auth_mint_zkbin, - auth_mint_pk, + auth_mint_zkbin: auth_mint_zkbin.clone(), + auth_mint_pk: auth_mint_pk.clone(), }; let auth_debris = builder.build()?; let mut data = vec![MoneyFunction::AuthTokenMintV1 as u8]; - auth_debris.params.encode(&mut data)?; + auth_debris.params.encode_async(&mut data).await?; let auth_call = ContractCall { contract_id: *MONEY_CONTRACT_ID, data }; - let mut tx = Transaction { - calls: vec![ - DarkLeaf { data: mint_call, parent_index: Some(1), children_indexes: vec![] }, - DarkLeaf { data: auth_call, parent_index: None, children_indexes: vec![0] }, - ], - proofs: vec![mint_debris.proofs, auth_debris.proofs], - signatures: vec![], - }; + // Create the TransactionBuilder containing above calls + let mut tx_builder = TransactionBuilder::new( + ContractCallLeaf { call: auth_call, proofs: auth_debris.proofs }, + vec![DarkTree::new( + ContractCallLeaf { call: mint_call, proofs: mint_debris.proofs }, + vec![], + None, + None, + )], + )?; + + // If we have tx fees enabled, make an offering + let mut fee_params = None; + let mut fee_signature_secrets = None; + if self.verify_fees { + let mut tx = tx_builder.build()?; + let mint_sigs = tx.create_sigs(&mut OsRng, &[])?; + let auth_sigs = tx.create_sigs(&mut OsRng, &[mint_authority.secret])?; + tx.signatures = vec![mint_sigs, auth_sigs]; + + let (fee_call, fee_proofs, fee_secrets, _spent_fee_coins, fee_call_params) = + self.append_fee_call(holder, tx, block_height, &[]).await?; + + // Append the fee call to the transaction + tx_builder.append(ContractCallLeaf { call: fee_call, proofs: fee_proofs }, vec![])?; + fee_signature_secrets = Some(fee_secrets); + fee_params = Some(fee_call_params); + } + + // Now build the actual transaction and sign it with necessary keys. + let mut tx = tx_builder.build()?; let mint_sigs = tx.create_sigs(&mut OsRng, &[])?; let auth_sigs = tx.create_sigs(&mut OsRng, &[mint_authority.secret])?; tx.signatures = vec![mint_sigs, auth_sigs]; - tx_action_benchmark.creation_times.push(timer.elapsed()); + if let Some(fee_signature_secrets) = fee_signature_secrets { + let sigs = tx.create_sigs(&mut OsRng, &fee_signature_secrets)?; + tx.signatures.push(sigs); + } - // Calculate transaction sizes - let encoded: Vec = serialize(&tx); - let size = std::mem::size_of_val(&*encoded); - tx_action_benchmark.sizes.push(size); - let base58 = bs58::encode(&encoded).into_string(); - let size = std::mem::size_of_val(&*base58); - tx_action_benchmark.broadcasted_sizes.push(size); - - Ok((tx, mint_debris.params, auth_debris.params)) + Ok((tx, mint_debris.params, auth_debris.params, fee_params)) } + /// Execute the transaction created by `token_mint()` for a given [`Holder`]. + /// + /// Returns any found [`OwnCoin`]s. + #[allow(clippy::too_many_arguments)] pub async fn execute_token_mint_tx( &mut self, holder: &Holder, - tx: &Transaction, - params: &MoneyTokenMintParamsV1, + tx: Transaction, + mint_params: &MoneyTokenMintParamsV1, + auth_params: &MoneyAuthTokenMintParamsV1, + fee_params: &Option, block_height: u64, - ) -> Result<()> { + append: bool, + ) -> Result> { let wallet = self.holders.get_mut(holder).unwrap(); - let tx_action_benchmark = - self.tx_action_benchmarks.get_mut(&TxAction::MoneyTokenMint).unwrap(); - let timer = Instant::now(); - wallet.validator.add_transactions(&[tx.clone()], block_height, true).await?; - wallet.money_merkle_tree.append(MerkleNode::from(params.coin.inner())); + // Execute the transaction + wallet.validator.add_transactions(&[tx], block_height, true, self.verify_fees).await?; - tx_action_benchmark.verify_times.push(timer.elapsed()); + // Iterate over all inputs to mark any spent coins + if let Some(ref fee_params) = fee_params { + if append { + if let Some(spent_coin) = wallet + .unspent_money_coins + .iter() + .find(|x| x.nullifier() == fee_params.input.nullifier) + .cloned() + { + debug!("Found spent OwnCoin({}) for {:?}", spent_coin.coin, holder); + wallet + .unspent_money_coins + .retain(|x| x.nullifier() != fee_params.input.nullifier); + wallet.spent_money_coins.push(spent_coin.clone()); + } + } + } - Ok(()) + let mut found_owncoins = vec![]; + + if append { + wallet.money_merkle_tree.append(MerkleNode::from(mint_params.coin.inner())); + + // Attempt to decrypt the encrypted note of the minted token + if let Ok(note) = auth_params.enc_note.decrypt::(&wallet.keypair.secret) { + let owncoin = OwnCoin { + coin: mint_params.coin, + note: note.clone(), + secret: wallet.keypair.secret, + leaf_position: wallet.money_merkle_tree.mark().unwrap(), + }; + + debug!("Found new OwnCoin({}) for {:?}", owncoin.coin, holder); + wallet.unspent_money_coins.push(owncoin.clone()); + found_owncoins.push(owncoin); + }; + + if let Some(ref fee_params) = fee_params { + wallet.money_merkle_tree.append(MerkleNode::from(fee_params.output.coin.inner())); + + // Attempt to decrypt the encrypted note in the fee output + if let Ok(note) = + fee_params.output.note.decrypt::(&wallet.keypair.secret) + { + let owncoin = OwnCoin { + coin: fee_params.output.coin, + note: note.clone(), + secret: wallet.keypair.secret, + leaf_position: wallet.money_merkle_tree.mark().unwrap(), + }; + + debug!("Found new OwnCoin({}) for {:?}", owncoin.coin, holder); + wallet.unspent_money_coins.push(owncoin.clone()); + found_owncoins.push(owncoin); + } + } + } + + Ok(found_owncoins) } - pub fn token_freeze( + /// Freeze the supply of a minted token + pub async fn token_freeze( &mut self, holder: &Holder, - ) -> Result<(Transaction, MoneyTokenFreezeParamsV1)> { + block_height: u64, + ) -> Result<(Transaction, MoneyTokenFreezeParamsV1, Option)> { let wallet = self.holders.get(holder).unwrap(); - let mint_keypair = wallet.token_mint_authority; - let token_blind = wallet.token_blind; + let mint_authority = wallet.token_mint_authority; let (frz_pk, frz_zkbin) = self.proving_keys.get(&MONEY_CONTRACT_ZKAS_TOKEN_FRZ_NS_V1.to_string()).unwrap(); - let tx_action_benchmark = - self.tx_action_benchmarks.get_mut(&TxAction::MoneyTokenFreeze).unwrap(); - - let timer = Instant::now(); - let auth_func_id = FuncRef { contract_id: *MONEY_CONTRACT_ID, func_code: MoneyFunction::AuthTokenMintV1 as u8, } .to_func_id(); + let (mint_auth_x, mint_auth_y) = mint_authority.public.xy(); + let token_blind = pallas::Base::random(&mut OsRng); + let token_attrs = TokenAttributes { auth_parent: auth_func_id, - user_data: poseidon_hash([mint_keypair.public.x(), mint_keypair.public.y()]), + user_data: poseidon_hash([mint_auth_x, mint_auth_y]), blind: token_blind, }; + // Create the freeze call let builder = TokenFreezeCallBuilder { - mint_keypair, + mint_keypair: mint_authority, token_attrs, freeze_zkbin: frz_zkbin.clone(), freeze_pk: frz_pk.clone(), }; - - let debris = builder.build()?; - + let freeze_debris = builder.build()?; let mut data = vec![MoneyFunction::TokenFreezeV1 as u8]; - debris.params.encode(&mut data)?; - let call = ContractCall { contract_id: *MONEY_CONTRACT_ID, data }; - let mut tx_builder = - TransactionBuilder::new(ContractCallLeaf { call, proofs: debris.proofs }, vec![])?; + freeze_debris.params.encode_async(&mut data).await?; + let freeze_call = ContractCall { contract_id: *MONEY_CONTRACT_ID, data }; + + // Create the TransactionBuilder containing the above call + let mut tx_builder = TransactionBuilder::new( + ContractCallLeaf { call: freeze_call, proofs: freeze_debris.proofs }, + vec![], + )?; + + // If we have tx fees enabled, make an offering + let mut fee_params = None; + let mut fee_signature_secrets = None; + if self.verify_fees { + let mut tx = tx_builder.build()?; + let freeze_sigs = tx.create_sigs(&mut OsRng, &[mint_authority.secret])?; + tx.signatures = vec![freeze_sigs]; + + let (fee_call, fee_proofs, fee_secrets, _spent_fee_coins, fee_call_params) = + self.append_fee_call(holder, tx, block_height, &[]).await?; + + // Append the fee call to the transaction + tx_builder.append(ContractCallLeaf { call: fee_call, proofs: fee_proofs }, vec![])?; + fee_signature_secrets = Some(fee_secrets); + fee_params = Some(fee_call_params); + } + + // Now build the actual transaction and sign it with necessary keys. let mut tx = tx_builder.build()?; - let sigs = tx.create_sigs(&mut OsRng, &[mint_keypair.secret])?; - tx.signatures = vec![sigs]; - tx_action_benchmark.creation_times.push(timer.elapsed()); + let freeze_sigs = tx.create_sigs(&mut OsRng, &[mint_authority.secret])?; + tx.signatures = vec![freeze_sigs]; + if let Some(fee_signature_secrets) = fee_signature_secrets { + let sigs = tx.create_sigs(&mut OsRng, &fee_signature_secrets)?; + tx.signatures.push(sigs); + } - // Calculate transaction sizes - let encoded: Vec = serialize(&tx); - let size = std::mem::size_of_val(&*encoded); - tx_action_benchmark.sizes.push(size); - let base58 = bs58::encode(&encoded).into_string(); - let size = std::mem::size_of_val(&*base58); - tx_action_benchmark.broadcasted_sizes.push(size); - - Ok((tx, debris.params)) + Ok((tx, freeze_debris.params, fee_params)) } + /// Execute the transaction created by `token_freeze()` for a given [`Holder`]. + /// + /// Returns any found [`OwnCoin`]s. pub async fn execute_token_freeze_tx( &mut self, holder: &Holder, - tx: &Transaction, - _params: &MoneyTokenFreezeParamsV1, + tx: Transaction, + _freeze_params: &MoneyTokenFreezeParamsV1, + fee_params: &Option, block_height: u64, - ) -> Result<()> { + append: bool, + ) -> Result> { let wallet = self.holders.get_mut(holder).unwrap(); - let tx_action_benchmark = - self.tx_action_benchmarks.get_mut(&TxAction::MoneyTokenFreeze).unwrap(); - let timer = Instant::now(); - wallet.validator.add_transactions(&[tx.clone()], block_height, true).await?; - tx_action_benchmark.verify_times.push(timer.elapsed()); + // Execute the transaction + wallet.validator.add_transactions(&[tx], block_height, true, self.verify_fees).await?; - Ok(()) + let mut found_owncoins = vec![]; + if let Some(ref fee_params) = fee_params { + if append { + if let Some(spent_coin) = wallet + .unspent_money_coins + .iter() + .find(|x| x.nullifier() == fee_params.input.nullifier) + .cloned() + { + debug!("Found spent OwnCoin({}) for {:?}", spent_coin.coin, holder); + wallet + .unspent_money_coins + .retain(|x| x.nullifier() != fee_params.input.nullifier); + wallet.spent_money_coins.push(spent_coin.clone()); + } + + wallet.money_merkle_tree.append(MerkleNode::from(fee_params.output.coin.inner())); + + // Attempt to decrypt the encrypted note + if let Ok(note) = + fee_params.output.note.decrypt::(&wallet.keypair.secret) + { + let owncoin = OwnCoin { + coin: fee_params.output.coin, + note: note.clone(), + secret: wallet.keypair.secret, + leaf_position: wallet.money_merkle_tree.mark().unwrap(), + }; + + debug!("Found new OwnCoin({}) for {:?}", owncoin.coin, holder); + wallet.unspent_money_coins.push(owncoin.clone()); + found_owncoins.push(owncoin); + } + } + } + + Ok(found_owncoins) } } diff --git a/src/contract/test-harness/src/money_transfer.rs b/src/contract/test-harness/src/money_transfer.rs index 4df5f864f..58d8f6d1f 100644 --- a/src/contract/test-harness/src/money_transfer.rs +++ b/src/contract/test-harness/src/money_transfer.rs @@ -16,8 +16,6 @@ * along with this program. If not, see . */ -use std::time::Instant; - use darkfi::{ tx::{ContractCallLeaf, Transaction, TransactionBuilder}, Result, @@ -28,23 +26,29 @@ use darkfi_money_contract::{ MoneyFunction, MONEY_CONTRACT_ZKAS_BURN_NS_V1, MONEY_CONTRACT_ZKAS_MINT_NS_V1, }; use darkfi_sdk::{ - crypto::{MerkleNode, MONEY_CONTRACT_ID}, - ContractCall, + client::{transfer_v1::make_transfer_call, MoneyNote, OwnCoin}, + crypto::{contract_id::MONEY_CONTRACT_ID, MerkleNode, TokenId, MONEY_CONTRACT_ID}, + model::{Input, MoneyFeeParamsV1, MoneyTransferParamsV1, Output}, + ContractCall, MoneyFunction, MONEY_CONTRACT_ZKAS_BURN_NS_V1, MONEY_CONTRACT_ZKAS_MINT_NS_V1, }; -use darkfi_serial::{serialize, Encodable}; +use darkfi_serial::AsyncEncodable; +use log::debug; use rand::rngs::OsRng; -use super::{Holder, TestHarness, TxAction}; +use super::{Holder, TestHarness}; impl TestHarness { - pub fn transfer( + /// Create a `Money::Transfer` transaction. + pub async fn transfer( &mut self, amount: u64, holder: &Holder, recipient: &Holder, owncoins: &[OwnCoin], token_id: TokenId, - ) -> Result<(Transaction, MoneyTransferParamsV1, Vec)> { + block_height: u64, + ) -> Result<(Transaction, (MoneyTransferParamsV1, Option), Vec)> + { let wallet = self.holders.get(holder).unwrap(); let rcpt = self.holders.get(recipient).unwrap().keypair.public; @@ -54,12 +58,8 @@ impl TestHarness { let (burn_pk, burn_zkbin) = self.proving_keys.get(&MONEY_CONTRACT_ZKAS_BURN_NS_V1.to_string()).unwrap(); - let tx_action_benchmark = - self.tx_action_benchmarks.get_mut(&TxAction::MoneyTransfer).unwrap(); - - let timer = Instant::now(); - - let (params, secrets, spent_coins) = make_transfer_call( + // Create the transfer call + let (params, secrets, mut spent_coins) = make_transfer_call( wallet.keypair, rcpt, amount, @@ -72,91 +72,120 @@ impl TestHarness { burn_pk.clone(), )?; + // Encode the call let mut data = vec![MoneyFunction::TransferV1 as u8]; - params.encode(&mut data)?; + params.encode_async(&mut data).await?; let call = ContractCall { contract_id: *MONEY_CONTRACT_ID, data }; + + // Create the TransactionBuilder containing the `Transfer` call let mut tx_builder = TransactionBuilder::new(ContractCallLeaf { call, proofs: secrets.proofs }, vec![])?; + + // If we have tx fees enabled, we first have to execute the fee-less tx to gather its + // used gas, and then we feed it into the fee-creating function. + // We also tell it about any spent coins so we don't accidentally reuse them in the + // fee call. + // TODO: We have to build a proper coin selection algorithm so that we can utilize + // the Money::Transfer to merge any coins which would give us a coin with enough + // value for paying the transaction fee. + let mut fee_params = None; + let mut fee_signature_secrets = None; + if self.verify_fees { + let mut tx = tx_builder.build()?; + let sigs = tx.create_sigs(&mut OsRng, &secrets.signature_secrets)?; + tx.signatures = vec![sigs]; + + let (fee_call, fee_proofs, fee_secrets, spent_fee_coins, fee_call_params) = + self.append_fee_call(holder, tx, block_height, &spent_coins).await?; + + // Append the fee call to the transaction + tx_builder.append(ContractCallLeaf { call: fee_call, proofs: fee_proofs }, vec![])?; + fee_signature_secrets = Some(fee_secrets); + spent_coins.extend_from_slice(&spent_fee_coins); + fee_params = Some(fee_call_params); + } + + // Now build the actual transaction and sign it with all necessary keys. let mut tx = tx_builder.build()?; let sigs = tx.create_sigs(&mut OsRng, &secrets.signature_secrets)?; tx.signatures = vec![sigs]; - tx_action_benchmark.creation_times.push(timer.elapsed()); + if let Some(fee_signature_secrets) = fee_signature_secrets { + let sigs = tx.create_sigs(&mut OsRng, &fee_signature_secrets)?; + tx.signatures.push(sigs); + } - // Calculate transaction sizes - let encoded: Vec = serialize(&tx); - let size = std::mem::size_of_val(&*encoded); - tx_action_benchmark.sizes.push(size); - let base58 = bs58::encode(&encoded).into_string(); - let size = std::mem::size_of_val(&*base58); - tx_action_benchmark.broadcasted_sizes.push(size); - - Ok((tx, params, spent_coins)) + Ok((tx, (params, fee_params), spent_coins)) } + /// Execute a `Money::Transfer` transaction for a given [`Holder`]. + /// + /// Returns any found [`OwnCoin`]s. pub async fn execute_transfer_tx( &mut self, holder: &Holder, - tx: &Transaction, - params: &MoneyTransferParamsV1, + tx: Transaction, + call_params: &MoneyTransferParamsV1, + fee_params: &Option, block_height: u64, append: bool, - ) -> Result<()> { + ) -> Result> { let wallet = self.holders.get_mut(holder).unwrap(); - let tx_action_benchmark = - self.tx_action_benchmarks.get_mut(&TxAction::MoneyTransfer).unwrap(); - let timer = Instant::now(); - wallet.validator.add_transactions(&[tx.clone()], block_height, true).await?; - if append { - for output in ¶ms.outputs { - wallet.money_merkle_tree.append(MerkleNode::from(output.coin.inner())); - } + // Execute the transaction + wallet.validator.add_transactions(&[tx], block_height, true, self.verify_fees).await?; + + // Iterate over all inputs to mark any spent coins + let mut inputs: Vec = call_params.inputs.to_vec(); + if let Some(ref fee_params) = fee_params { + inputs.push(fee_params.input.clone()); } - tx_action_benchmark.verify_times.push(timer.elapsed()); - Ok(()) - } - - pub async fn execute_multiple_transfer_txs( - &mut self, - holder: &Holder, - txs: &[Transaction], - txs_params: &Vec, - block_height: u64, - append: bool, - ) -> Result<()> { - let wallet = self.holders.get_mut(holder).unwrap(); - let tx_action_benchmark = - self.tx_action_benchmarks.get_mut(&TxAction::MoneyTransfer).unwrap(); - let timer = Instant::now(); - - wallet.validator.add_transactions(txs, block_height, true).await?; if append { - for params in txs_params { - for output in ¶ms.outputs { - wallet.money_merkle_tree.append(MerkleNode::from(output.coin.inner())); + for input in inputs.iter() { + if let Some(spent_coin) = wallet + .unspent_money_coins + .iter() + .find(|x| x.nullifier() == input.nullifier) + .cloned() + { + debug!("Found spent OwnCoin({}) for {:?}", spent_coin.coin, holder); + wallet.unspent_money_coins.retain(|x| x.nullifier() != input.nullifier); + wallet.spent_money_coins.push(spent_coin.clone()); } } } - tx_action_benchmark.verify_times.push(timer.elapsed()); - Ok(()) - } + // Iterate over all outputs to find any new OwnCoins + let mut found_owncoins = vec![]; + let mut outputs: Vec = call_params.outputs.to_vec(); + if let Some(ref fee_params) = fee_params { + outputs.push(fee_params.output.clone()); + } - pub async fn verify_transfer_tx( - &mut self, - holder: &Holder, - tx: &Transaction, - block_height: u64, - ) -> Result<()> { - let wallet = self.holders.get(holder).unwrap(); - let tx_action_benchmark = - self.tx_action_benchmarks.get_mut(&TxAction::MoneyTransfer).unwrap(); - let timer = Instant::now(); + for output in outputs.iter() { + if !append { + continue + } - wallet.validator.add_transactions(&[tx.clone()], block_height, false).await?; - tx_action_benchmark.verify_times.push(timer.elapsed()); + wallet.money_merkle_tree.append(MerkleNode::from(output.coin.inner())); - Ok(()) + // Attempt to decrypt the output note to see if this is a coin for the holder. + let Ok(note) = output.note.decrypt::(&wallet.keypair.secret) else { + continue + }; + + let owncoin = OwnCoin { + coin: output.coin, + note: note.clone(), + secret: wallet.keypair.secret, + leaf_position: wallet.money_merkle_tree.mark().unwrap(), + }; + + debug!("Found new OwnCoin({}) for {:?}", owncoin.coin, holder); + wallet.unspent_money_coins.push(owncoin.clone()); + found_owncoins.push(owncoin); + } + + Ok(found_owncoins) } } diff --git a/src/contract/test-harness/src/vks.rs b/src/contract/test-harness/src/vks.rs index 7a5a1e4ed..ea31a9502 100644 --- a/src/contract/test-harness/src/vks.rs +++ b/src/contract/test-harness/src/vks.rs @@ -38,19 +38,21 @@ use darkfi_dao_contract::{ }; use darkfi_deployooor_contract::DEPLOY_CONTRACT_ZKAS_DERIVE_NS_V1; use darkfi_money_contract::{ - MONEY_CONTRACT_ZKAS_AUTH_TOKEN_MINT_NS_V1, MONEY_CONTRACT_ZKAS_BURN_NS_V1, - MONEY_CONTRACT_ZKAS_FEE_NS_V1, MONEY_CONTRACT_ZKAS_MINT_NS_V1, + MONEY_CONTRACT_ZKAS_BURN_NS_V1, MONEY_CONTRACT_ZKAS_FEE_NS_V1, MONEY_CONTRACT_ZKAS_MINT_NS_V1, MONEY_CONTRACT_ZKAS_TOKEN_FRZ_NS_V1, MONEY_CONTRACT_ZKAS_TOKEN_MINT_NS_V1, }; use darkfi_sdk::crypto::{contract_id::DEPLOYOOOR_CONTRACT_ID, DAO_CONTRACT_ID, MONEY_CONTRACT_ID}; use darkfi_serial::{deserialize, serialize}; + use log::debug; -/// Update this if any circuits are changed -const VKS_HASH: &str = "605a72d885e6194ac346a328482504ca37f0c990c2d636ad1b548a8bfb05542b"; -const PKS_HASH: &str = "277228a59ed3cc1df8a9d9e61b3230b4417512d649b4aca1fb3e5f02514a2e96"; +/// Update these if any circuits are changed. +/// Delete the existing cachefiles, and enable debug logging, you will see the new hashes. +const VKS_HASH: &str = "249d56e6e49ea45a0b42521d85f703bc7bc4428090d6fed11aa6f179a1006a97"; +const PKS_HASH: &str = "85f3e2f69df9d4e11967611be6611dd7e0726b87fbcd3985f535f556be5982fb"; -fn pks_path(typ: &str) -> Result { +/// Build a `PathBuf` to a cachefile +fn cache_path(typ: &str) -> Result { let output = Command::new("git").arg("rev-parse").arg("--show-toplevel").output()?.stdout; let mut path = PathBuf::from(String::from_utf8(output[..output.len() - 1].to_vec())?); path.push("src"); @@ -62,36 +64,19 @@ fn pks_path(typ: &str) -> Result { /// (Bincode, Namespace, VK) pub type Vks = Vec<(Vec, String, Vec)>; +/// (Bincode, Namespace, VK) pub type Pks = Vec<(Vec, String, Vec)>; -pub fn read_or_gen_vks_and_pks() -> Result<(Pks, Vks)> { - let vks_path = pks_path("vks.bin")?; - let pks_path = pks_path("pks.bin")?; +/// Generate or read cached PKs and VKs +pub fn get_cached_pks_and_vks() -> Result<(Pks, Vks)> { + let pks_path = cache_path("pks.bin")?; + let vks_path = cache_path("vks.bin")?; - let mut vks = None; let mut pks = None; - - if vks_path.exists() { - debug!("Found vks.bin"); - let mut f = File::open(vks_path.clone())?; - let mut data = vec![]; - f.read_to_end(&mut data)?; - - let known_hash = blake3::Hash::from_hex(VKS_HASH)?; - let found_hash = blake3::hash(&data); - - debug!("Known VKS hash: {}", known_hash); - debug!("Found VKS hash: {}", found_hash); - - if known_hash == found_hash { - vks = Some(deserialize(&data)?) - } - - drop(f); - } + let mut vks = None; if pks_path.exists() { - debug!("Found pks.bin"); + debug!("Found {:?}", pks_path); let mut f = File::open(pks_path.clone())?; let mut data = vec![]; f.read_to_end(&mut data)?; @@ -109,10 +94,31 @@ pub fn read_or_gen_vks_and_pks() -> Result<(Pks, Vks)> { drop(f); } + if vks_path.exists() { + debug!("Found {:?}", vks_path); + let mut f = File::open(vks_path.clone())?; + let mut data = vec![]; + f.read_to_end(&mut data)?; + + let known_hash = blake3::Hash::from_hex(VKS_HASH)?; + let found_hash = blake3::hash(&data); + + debug!("Known VKS hash: {}", known_hash); + debug!("Found VKS hash: {}", found_hash); + + if known_hash == found_hash { + vks = Some(deserialize(&data)?) + } + + drop(f); + } + + // Cache is correct, return if let (Some(pks), Some(vks)) = (pks, vks) { return Ok((pks, vks)) } + // Otherwise, build them let bins = vec![ // Money &include_bytes!("../../money/proof/fee_v1.zk.bin")[..], @@ -120,7 +126,6 @@ pub fn read_or_gen_vks_and_pks() -> Result<(Pks, Vks)> { &include_bytes!("../../money/proof/burn_v1.zk.bin")[..], &include_bytes!("../../money/proof/token_mint_v1.zk.bin")[..], &include_bytes!("../../money/proof/token_freeze_v1.zk.bin")[..], - &include_bytes!("../../money/proof/auth_token_mint_v1.zk.bin")[..], // DAO &include_bytes!("../../dao/proof/mint.zk.bin")[..], &include_bytes!("../../dao/proof/propose-input.zk.bin")[..], @@ -134,76 +139,70 @@ pub fn read_or_gen_vks_and_pks() -> Result<(Pks, Vks)> { &include_bytes!("../../deployooor/proof/derive_contract_id.zk.bin")[..], ]; - let mut vks = vec![]; let mut pks = vec![]; + let mut vks = vec![]; for bincode in bins.iter() { let zkbin = ZkBinary::decode(bincode)?; - debug!("Building VK for {}", zkbin.namespace); + debug!("Building PK for {}", zkbin.namespace); let witnesses = empty_witnesses(&zkbin)?; let circuit = ZkCircuit::new(witnesses, &zkbin); - let vk = VerifyingKey::build(zkbin.k, &circuit); - let mut vk_buf = vec![]; - vk.write(&mut vk_buf)?; - vks.push((bincode.to_vec(), zkbin.namespace.clone(), vk_buf)); let pk = ProvingKey::build(zkbin.k, &circuit); let mut pk_buf = vec![]; pk.write(&mut pk_buf)?; - pks.push((bincode.to_vec(), zkbin.namespace, pk_buf)); + pks.push((bincode.to_vec(), zkbin.namespace.clone(), pk_buf)); + + debug!("Building VK for {}", zkbin.namespace); + let vk = VerifyingKey::build(zkbin.k, &circuit); + let mut vk_buf = vec![]; + vk.write(&mut vk_buf)?; + vks.push((bincode.to_vec(), zkbin.namespace.clone(), vk_buf)); } - debug!("Writing to {:?}", vks_path); - let mut f = File::create(vks_path)?; - let ser = serialize(&vks); - let hash = blake3::hash(&ser); - debug!("vks.bin {}", hash); - f.write_all(&ser)?; - - debug!("Writing to {:?}", pks_path); - let mut f = File::create(pks_path)?; + debug!("Writing PKs to {:?}", pks_path); + let mut f = File::create(&pks_path)?; let ser = serialize(&pks); let hash = blake3::hash(&ser); - debug!("pks.bin {}", hash); + debug!("{:?} {}", pks_path, hash); + f.write_all(&ser)?; + + debug!("Writing VKs to {:?}", vks_path); + let mut f = File::create(&vks_path)?; + let ser = serialize(&vks); + let hash = blake3::hash(&ser); + debug!("{:?} {}", vks_path, hash); f.write_all(&ser)?; Ok((pks, vks)) } +/// Inject cached VKs into a given blockchain database reference pub fn inject(sled_db: &sled::Db, vks: &Vks) -> Result<()> { - // Inject vks into the db - let money_zkas_tree_ptr = MONEY_CONTRACT_ID.hash_state_id(SMART_CONTRACT_ZKAS_DB_NAME); - let money_zkas_tree = sled_db.open_tree(money_zkas_tree_ptr)?; + // Derive the database names for the specific contracts + let money_db_name = MONEY_CONTRACT_ID.hash_state_id(SMART_CONTRACT_ZKAS_DB_NAME); + let dao_db_name = DAO_CONTRACT_ID.hash_state_id(SMART_CONTRACT_ZKAS_DB_NAME); + let deployooor_db_name = DEPLOYOOOR_CONTRACT_ID.hash_state_id(SMART_CONTRACT_ZKAS_DB_NAME); - let dao_zkas_tree_ptr = DAO_CONTRACT_ID.hash_state_id(SMART_CONTRACT_ZKAS_DB_NAME); - let dao_zkas_tree = sled_db.open_tree(dao_zkas_tree_ptr)?; - - let deployooor_zkas_tree_ptr = - DEPLOYOOOR_CONTRACT_ID.hash_state_id(SMART_CONTRACT_ZKAS_DB_NAME); - let deployooor_zkas_tree = sled_db.open_tree(deployooor_zkas_tree_ptr)?; + // Create the db trees + let money_tree = sled_db.open_tree(money_db_name)?; + let dao_tree = sled_db.open_tree(dao_db_name)?; + let deployooor_tree = sled_db.open_tree(deployooor_db_name)?; for (bincode, namespace, vk) in vks.iter() { match namespace.as_str() { - // Money circuits + // Money contract circuits MONEY_CONTRACT_ZKAS_FEE_NS_V1 | MONEY_CONTRACT_ZKAS_MINT_NS_V1 | MONEY_CONTRACT_ZKAS_BURN_NS_V1 | MONEY_CONTRACT_ZKAS_TOKEN_MINT_NS_V1 | - MONEY_CONTRACT_ZKAS_TOKEN_FRZ_NS_V1 | - MONEY_CONTRACT_ZKAS_AUTH_TOKEN_MINT_NS_V1 => { + MONEY_CONTRACT_ZKAS_TOKEN_FRZ_NS_V1 => { let key = serialize(&namespace.as_str()); let value = serialize(&(bincode.clone(), vk.clone())); - money_zkas_tree.insert(key, value)?; + money_tree.insert(key, value)?; } - // Deployooor circuits - DEPLOY_CONTRACT_ZKAS_DERIVE_NS_V1 => { - let key = serialize(&namespace.as_str()); - let value = serialize(&(bincode.clone(), vk.clone())); - deployooor_zkas_tree.insert(key, value)?; - } - - // DAO circuits + // DAO contract circuits DAO_CONTRACT_ZKAS_DAO_MINT_NS | DAO_CONTRACT_ZKAS_DAO_VOTE_INPUT_NS | DAO_CONTRACT_ZKAS_DAO_VOTE_MAIN_NS | @@ -214,7 +213,14 @@ pub fn inject(sled_db: &sled::Db, vks: &Vks) -> Result<()> { DAO_CONTRACT_ZKAS_DAO_AUTH_MONEY_TRANSFER_ENC_COIN_NS => { let key = serialize(&namespace.as_str()); let value = serialize(&(bincode.clone(), vk.clone())); - dao_zkas_tree.insert(key, value)?; + dao_tree.insert(key, value)?; + } + + // Deployooor contract circuits + DEPLOY_CONTRACT_ZKAS_DERIVE_NS_V1 => { + let key = serialize(&namespace.as_str()); + let value = serialize(&(bincode.clone(), vk.clone())); + deployooor_tree.insert(key, value)?; } x => panic!("Found unhandled zkas namespace {}", x), diff --git a/src/validator/mod.rs b/src/validator/mod.rs index 06057c9b1..3eff3b62d 100644 --- a/src/validator/mod.rs +++ b/src/validator/mod.rs @@ -424,41 +424,47 @@ impl Validator { /// In case any of the transactions fail, they will be returned to the caller. /// The function takes a boolean called `write` which tells it to actually write /// the state transitions to the database. + /// + /// Returns the total gas used for the given transactions. pub async fn add_transactions( &self, txs: &[Transaction], verifying_block_height: u64, write: bool, - ) -> Result<()> { + verify_fees: bool, + ) -> Result { debug!(target: "validator::add_transactions", "Instantiating BlockchainOverlay"); let overlay = BlockchainOverlay::new(&self.blockchain)?; // Verify all transactions and get erroneous ones - let e = verify_transactions( + let verify_result = verify_transactions( &overlay, verifying_block_height, txs, &mut MerkleTree::new(1), - self.verify_fees, + verify_fees, ) .await; + let lock = overlay.lock().unwrap(); let mut overlay = lock.overlay.lock().unwrap(); - if let Err(e) = e { + if let Err(e) = verify_result { overlay.purge_new_trees()?; return Err(e) } + let gas_used = verify_result.unwrap(); + if !write { debug!(target: "validator::add_transactions", "Skipping apply of state updates because write=false"); overlay.purge_new_trees()?; - return Ok(()) + return Ok(gas_used) } debug!(target: "validator::add_transactions", "Applying overlay changes"); overlay.apply()?; - Ok(()) + Ok(gas_used) } /// Validate a producer `Transaction` and apply it if valid.