contract/test-harness: Deduplicate Nullifier and Coin handling code

This commit is contained in:
x
2026-02-10 13:55:01 +00:00
parent 6ff035e8f7
commit c17f2a650d
13 changed files with 767 additions and 744 deletions

View File

@@ -21,17 +21,11 @@ use darkfi::{
Result,
};
use darkfi_deployooor_contract::{client::deploy_v1::DeployCallBuilder, DeployFunction};
use darkfi_money_contract::{
client::{MoneyNote, OwnCoin},
model::MoneyFeeParamsV1,
};
use darkfi_money_contract::{client::OwnCoin, model::MoneyFeeParamsV1};
use darkfi_sdk::{
crypto::{contract_id::DEPLOYOOOR_CONTRACT_ID, MerkleNode},
deploy::DeployParamsV1,
ContractCall,
crypto::contract_id::DEPLOYOOOR_CONTRACT_ID, deploy::DeployParamsV1, ContractCall,
};
use darkfi_serial::AsyncEncodable;
use tracing::debug;
use darkfi_serial::Encodable;
use super::{Holder, TestHarness};
@@ -45,7 +39,7 @@ impl TestHarness {
wasm_bincode: Vec<u8>,
block_height: u32,
) -> Result<(Transaction, DeployParamsV1, Option<MoneyFeeParamsV1>)> {
let wallet = self.holders.get(holder).unwrap();
let wallet = self.wallet(holder);
let deploy_keypair = wallet.contract_deploy_authority;
// Build the contract call
@@ -54,7 +48,7 @@ impl TestHarness {
// Encode the call
let mut data = vec![DeployFunction::DeployV1 as u8];
debris.params.encode_async(&mut data).await?;
debris.params.encode(&mut data)?;
let call = ContractCall { contract_id: *DEPLOYOOOR_CONTRACT_ID, data };
let mut tx_builder =
TransactionBuilder::new(ContractCallLeaf { call, proofs: vec![] }, vec![])?;
@@ -102,50 +96,12 @@ impl TestHarness {
) -> Result<Vec<OwnCoin>> {
let wallet = self.holders.get_mut(holder).unwrap();
// Execute the transaction
wallet.add_transaction("deploy::deploy", tx, block_height).await?;
if !append {
return Ok(vec![])
return Ok(vec![]);
}
if let Some(ref fee_params) = fee_params {
let nullifier = fee_params.input.nullifier.inner();
wallet
.money_null_smt
.insert_batch(vec![(nullifier, nullifier)])
.expect("smt.insert_batch()");
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::<MoneyNote>(&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![])
Ok(wallet.process_fee(fee_params, holder))
}
}

View File

@@ -23,17 +23,9 @@ use darkfi::{
use darkfi_deployooor_contract::{
client::lock_v1::LockCallBuilder, model::LockParamsV1, DeployFunction,
};
use darkfi_money_contract::{
client::{MoneyNote, OwnCoin},
model::MoneyFeeParamsV1,
};
use darkfi_sdk::{
crypto::{contract_id::DEPLOYOOOR_CONTRACT_ID, MerkleNode},
//deploy::LockParamsV1,
ContractCall,
};
use darkfi_serial::AsyncEncodable;
use tracing::debug;
use darkfi_money_contract::{client::OwnCoin, model::MoneyFeeParamsV1};
use darkfi_sdk::{crypto::contract_id::DEPLOYOOOR_CONTRACT_ID, ContractCall};
use darkfi_serial::Encodable;
use super::{Holder, TestHarness};
@@ -46,7 +38,7 @@ impl TestHarness {
holder: &Holder,
block_height: u32,
) -> Result<(Transaction, LockParamsV1, Option<MoneyFeeParamsV1>)> {
let wallet = self.holders.get(holder).unwrap();
let wallet = self.wallet(holder);
let deploy_keypair = wallet.contract_deploy_authority;
// Build the contract call
@@ -55,7 +47,7 @@ impl TestHarness {
// Encode the call
let mut data = vec![DeployFunction::LockV1 as u8];
debris.params.encode_async(&mut data).await?;
debris.params.encode(&mut data)?;
let call = ContractCall { contract_id: *DEPLOYOOOR_CONTRACT_ID, data };
let mut tx_builder =
TransactionBuilder::new(ContractCallLeaf { call, proofs: vec![] }, vec![])?;
@@ -101,52 +93,14 @@ impl TestHarness {
block_height: u32,
append: bool,
) -> Result<Vec<OwnCoin>> {
let wallet = self.holders.get_mut(holder).unwrap();
let wallet = self.wallet_mut(holder);
// Execute the transaction
wallet.add_transaction("deploy::lock", tx, block_height).await?;
if !append {
return Ok(vec![])
return Ok(vec![]);
}
if let Some(ref fee_params) = fee_params {
let nullifier = fee_params.input.nullifier.inner();
wallet
.money_null_smt
.insert_batch(vec![(nullifier, nullifier)])
.expect("smt.insert_batch()");
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::<MoneyNote>(&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![])
Ok(wallet.process_fee(fee_params, holder))
}
}

View File

@@ -29,21 +29,20 @@ use darkfi_dao_contract::{
DAO_CONTRACT_ZKAS_EXEC_NS,
};
use darkfi_money_contract::{
client::{transfer_v1 as xfer, MoneyNote, OwnCoin},
client::{transfer_v1 as xfer, OwnCoin},
model::{CoinAttributes, MoneyFeeParamsV1, MoneyTransferParamsV1},
MoneyFunction, MONEY_CONTRACT_ZKAS_BURN_NS_V1, MONEY_CONTRACT_ZKAS_MINT_NS_V1,
};
use darkfi_sdk::{
crypto::{
contract_id::{DAO_CONTRACT_ID, MONEY_CONTRACT_ID},
pedersen_commitment_u64, Blind, FuncRef, MerkleNode, ScalarBlind, SecretKey,
pedersen_commitment_u64, Blind, FuncRef, ScalarBlind, SecretKey,
},
dark_tree::DarkTree,
ContractCall,
};
use darkfi_serial::AsyncEncodable;
use darkfi_serial::Encodable;
use rand::rngs::OsRng;
use tracing::debug;
use super::{Holder, TestHarness};
@@ -64,7 +63,7 @@ impl TestHarness {
all_vote_blind: ScalarBlind,
block_height: u32,
) -> Result<(Transaction, MoneyTransferParamsV1, Option<MoneyFeeParamsV1>)> {
let dao_wallet = self.holders.get(&Holder::Dao).unwrap();
let dao_wallet = self.wallet(&Holder::Dao);
let (mint_pk, mint_zkbin) = self.proving_keys.get(MONEY_CONTRACT_ZKAS_MINT_NS_V1).unwrap();
let (burn_pk, burn_zkbin) = self.proving_keys.get(MONEY_CONTRACT_ZKAS_BURN_NS_V1).unwrap();
@@ -136,7 +135,7 @@ impl TestHarness {
let (xfer_params, xfer_secrets) = xfer_builder.build()?;
let mut data = vec![MoneyFunction::TransferV1 as u8];
xfer_params.encode_async(&mut data).await?;
xfer_params.encode(&mut data)?;
let xfer_call = ContractCall { contract_id: *MONEY_CONTRACT_ID, data };
// We need to extract stuff from the inputs and outputs that we'll also
@@ -172,7 +171,7 @@ impl TestHarness {
dao_exec_pk,
)?;
let mut data = vec![DaoFunction::Exec as u8];
exec_params.encode_async(&mut data).await?;
exec_params.encode(&mut data)?;
let exec_call = ContractCall { contract_id: *DAO_CONTRACT_ID, data };
// Auth module
@@ -190,7 +189,7 @@ impl TestHarness {
dao_auth_xfer_enc_coin_pk,
)?;
let mut data = vec![DaoFunction::AuthMoneyTransfer as u8];
auth_xfer_params.encode_async(&mut data).await?;
auth_xfer_params.encode(&mut data)?;
let auth_xfer_call = ContractCall { contract_id: *DAO_CONTRACT_ID, data };
// We need to construct this tree, where exec is the parent:
@@ -267,7 +266,7 @@ impl TestHarness {
all_vote_blind: ScalarBlind,
block_height: u32,
) -> Result<(Transaction, Option<MoneyFeeParamsV1>)> {
let wallet = self.holders.get_mut(holder).unwrap();
let wallet = self.wallet(holder);
let (dao_exec_pk, dao_exec_zkbin) = match dao_early_exec_secret_key {
Some(_) => self.proving_keys.get(DAO_CONTRACT_ZKAS_EARLY_EXEC_NS).unwrap(),
@@ -297,7 +296,7 @@ impl TestHarness {
// Encode the call
let mut data = vec![DaoFunction::Exec as u8];
exec_params.encode_async(&mut data).await?;
exec_params.encode(&mut data)?;
let exec_call = ContractCall { contract_id: *DAO_CONTRACT_ID, data };
// Create the TransactionBuilder containing the `DAO::Exec` call
@@ -348,61 +347,27 @@ impl TestHarness {
block_height: u32,
append: bool,
) -> Result<Vec<OwnCoin>> {
let wallet = self.holders.get_mut(holder).unwrap();
let wallet = self.wallet_mut(holder);
// Execute the transaction
wallet.add_transaction("dao::exec", tx, block_height).await?;
if !append {
return Ok(vec![])
return Ok(vec![]);
}
let (mut inputs, mut outputs) = match xfer_params {
Some(params) => (params.inputs.to_vec(), params.outputs.to_vec()),
None => (vec![], vec![]),
};
if let Some(ref fee_params) = fee_params {
inputs.push(fee_params.input.clone());
outputs.push(fee_params.output.clone());
// Combine transfer and fee inputs/outputs, then process uniformly
let mut inputs = vec![];
let mut outputs = vec![];
if let Some(params) = xfer_params {
inputs.extend_from_slice(&params.inputs);
outputs.extend_from_slice(&params.outputs);
}
if let Some(ref fp) = fee_params {
inputs.push(fp.input.clone());
outputs.push(fp.output.clone());
}
let nullifiers = inputs.iter().map(|i| i.nullifier.inner()).map(|l| (l, l)).collect();
wallet.money_null_smt.insert_batch(nullifiers).expect("smt.insert_batch()");
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::<MoneyNote>(&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)
wallet.process_inputs(&inputs, holder);
Ok(wallet.process_outputs(&outputs, holder))
}
}

View File

@@ -25,16 +25,12 @@ use darkfi_dao_contract::{
model::{Dao, DaoMintParams},
DaoFunction, DAO_CONTRACT_ZKAS_MINT_NS,
};
use darkfi_money_contract::{
client::{MoneyNote, OwnCoin},
model::MoneyFeeParamsV1,
};
use darkfi_money_contract::{client::OwnCoin, model::MoneyFeeParamsV1};
use darkfi_sdk::{
crypto::{contract_id::DAO_CONTRACT_ID, MerkleNode, SecretKey},
ContractCall,
};
use darkfi_serial::AsyncEncodable;
use tracing::debug;
use darkfi_serial::Encodable;
use super::{Holder, TestHarness};
@@ -74,7 +70,7 @@ impl TestHarness {
// Encode the call
let mut data = vec![DaoFunction::Mint as u8];
params.encode_async(&mut data).await?;
params.encode(&mut data)?;
let call = ContractCall { contract_id: *DAO_CONTRACT_ID, data };
let mut tx_builder = TransactionBuilder::new(ContractCallLeaf { call, proofs }, vec![])?;
@@ -119,56 +115,19 @@ impl TestHarness {
block_height: u32,
append: bool,
) -> Result<Vec<OwnCoin>> {
let wallet = self.holders.get_mut(holder).unwrap();
let wallet = self.wallet_mut(holder);
// Execute the transaction
wallet.add_transaction("dao::mint", tx, block_height).await?;
if !append {
return Ok(vec![])
return Ok(vec![]);
}
// Track the DAO bulla in the DAO Merkle tree
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);
if let Some(ref fee_params) = fee_params {
let nullifier = fee_params.input.nullifier.inner();
wallet
.money_null_smt
.insert_batch(vec![(nullifier, nullifier)])
.expect("smt.insert_batch()");
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::<MoneyNote>(&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![])
Ok(wallet.process_fee(fee_params, holder))
}
}

View File

@@ -27,7 +27,7 @@ use darkfi_dao_contract::{
DaoFunction, DAO_CONTRACT_ZKAS_PROPOSE_INPUT_NS, DAO_CONTRACT_ZKAS_PROPOSE_MAIN_NS,
};
use darkfi_money_contract::{
client::{MoneyNote, OwnCoin},
client::OwnCoin,
model::{CoinAttributes, MoneyFeeParamsV1},
MoneyFunction,
};
@@ -39,9 +39,8 @@ use darkfi_sdk::{
pasta::pallas,
ContractCall,
};
use darkfi_serial::AsyncEncodable;
use darkfi_serial::Encodable;
use rand::rngs::OsRng;
use tracing::debug;
use super::{Holder, TestHarness};
@@ -58,7 +57,7 @@ impl TestHarness {
block_height: u32,
duration_blockwindows: u64,
) -> Result<(Transaction, DaoProposeParams, Option<MoneyFeeParamsV1>, DaoProposal)> {
let wallet = self.holders.get(proposer).unwrap();
let wallet = self.wallet(proposer);
let (dao_propose_burn_pk, dao_propose_burn_zkbin) =
self.proving_keys.get(DAO_CONTRACT_ZKAS_PROPOSE_INPUT_NS).unwrap();
@@ -103,7 +102,7 @@ impl TestHarness {
proposal_coins.push(coin_params.to_coin());
}
let mut proposal_data = vec![];
proposal_coins.encode_async(&mut proposal_data).await?;
proposal_coins.encode(&mut proposal_data)?;
// Create Auth calls
let auth_calls = vec![
@@ -155,7 +154,7 @@ impl TestHarness {
// Encode the call
let mut data = vec![DaoFunction::Propose as u8];
params.encode_async(&mut data).await?;
params.encode(&mut data)?;
let call = ContractCall { contract_id: *DAO_CONTRACT_ID, data };
let mut tx_builder = TransactionBuilder::new(ContractCallLeaf { call, proofs }, vec![])?;
@@ -198,7 +197,7 @@ impl TestHarness {
block_height: u32,
duration_blockwindows: u64,
) -> Result<(Transaction, DaoProposeParams, Option<MoneyFeeParamsV1>, DaoProposal)> {
let wallet = self.holders.get(proposer).unwrap();
let wallet = self.wallet(proposer);
let (dao_propose_burn_pk, dao_propose_burn_zkbin) =
self.proving_keys.get(DAO_CONTRACT_ZKAS_PROPOSE_INPUT_NS).unwrap();
@@ -213,20 +212,6 @@ impl TestHarness {
.unwrap()
.clone();
// Useful code snippet to dump a sled contract DB
/*{
let blockchain = &wallet.validator.read().await.blockchain;
let contracts = &blockchain.contracts;
let tree = contracts
.lookup(&blockchain.sled_db, &MONEY_CONTRACT_ID, "nullifier_roots")
.unwrap();
for kv in tree.iter() {
let (key, value) = kv.unwrap();
debug!("STATE {:?}", key);
debug!(" => {:?}", value);
}
}*/
let input = DaoProposeStakeInput {
secret: wallet.keypair.secret,
note: propose_owncoin.note.clone(),
@@ -273,7 +258,7 @@ impl TestHarness {
// Encode the call
let mut data = vec![DaoFunction::Propose as u8];
params.encode_async(&mut data).await?;
params.encode(&mut data)?;
let call = ContractCall { contract_id: *DAO_CONTRACT_ID, data };
let mut tx_builder = TransactionBuilder::new(ContractCallLeaf { call, proofs }, vec![])?;
@@ -318,59 +303,22 @@ impl TestHarness {
block_height: u32,
append: bool,
) -> Result<Vec<OwnCoin>> {
let wallet = self.holders.get_mut(holder).unwrap();
let wallet = self.wallet_mut(holder);
// Execute the transaction
wallet.add_transaction("dao::propose", tx, block_height).await?;
wallet.money_null_smt_snapshot = Some(wallet.money_null_smt.clone());
if !append {
return Ok(vec![])
return Ok(vec![]);
}
// Track proposal in the proposals tree
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));
if let Some(ref fee_params) = fee_params {
let nullifier = fee_params.input.nullifier.inner();
wallet
.money_null_smt
.insert_batch(vec![(nullifier, nullifier)])
.expect("smt.insert_batch()");
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::<MoneyNote>(&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![])
Ok(wallet.process_fee(fee_params, holder))
}
}

View File

@@ -2,7 +2,7 @@
*
* Copyright (C) 2020-2026 Dyne.org foundation
*
* This program is free software: you can redistributemoney it and/or modify
* 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.
@@ -26,16 +26,9 @@ use darkfi_dao_contract::{
model::{Dao, DaoProposal, DaoVoteParams},
DaoFunction, DAO_CONTRACT_ZKAS_VOTE_INPUT_NS, DAO_CONTRACT_ZKAS_VOTE_MAIN_NS,
};
use darkfi_money_contract::{
client::{MoneyNote, OwnCoin},
model::MoneyFeeParamsV1,
};
use darkfi_sdk::{
crypto::{contract_id::DAO_CONTRACT_ID, MerkleNode},
ContractCall,
};
use darkfi_serial::AsyncEncodable;
use tracing::debug;
use darkfi_money_contract::{client::OwnCoin, model::MoneyFeeParamsV1};
use darkfi_sdk::{crypto::contract_id::DAO_CONTRACT_ID, ContractCall};
use darkfi_serial::Encodable;
use super::{Holder, TestHarness};
@@ -49,7 +42,7 @@ impl TestHarness {
proposal: &DaoProposal,
block_height: u32,
) -> Result<(Transaction, DaoVoteParams, Option<MoneyFeeParamsV1>)> {
let wallet = self.holders.get(voter).unwrap();
let wallet = self.wallet(voter);
let (dao_vote_burn_pk, dao_vote_burn_zkbin) =
self.proving_keys.get(DAO_CONTRACT_ZKAS_VOTE_INPUT_NS).unwrap();
@@ -94,7 +87,7 @@ impl TestHarness {
// Encode the call
let mut data = vec![DaoFunction::Vote as u8];
params.encode_async(&mut data).await?;
params.encode(&mut data)?;
let call = ContractCall { contract_id: *DAO_CONTRACT_ID, data };
let mut tx_builder = TransactionBuilder::new(ContractCallLeaf { call, proofs }, vec![])?;
@@ -138,52 +131,14 @@ impl TestHarness {
block_height: u32,
append: bool,
) -> Result<Vec<OwnCoin>> {
let wallet = self.holders.get_mut(holder).unwrap();
let wallet = self.wallet_mut(holder);
// Execute the transaction
wallet.add_transaction("dao::vote", tx, block_height).await?;
if !append {
return Ok(vec![])
return Ok(vec![]);
}
if let Some(ref fee_params) = fee_params {
let nullifier = fee_params.input.nullifier.inner();
wallet
.money_null_smt
.insert_batch(vec![(nullifier, nullifier)])
.expect("smt.insert_batch()");
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::<MoneyNote>(&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![])
Ok(wallet.process_fee(fee_params, holder))
}
}

View File

@@ -18,8 +18,10 @@
use std::{
collections::HashMap,
fs::OpenOptions,
io::{Cursor, Write},
slice,
time::Instant,
};
use darkfi::{
@@ -36,64 +38,68 @@ use darkfi::{
zkas::ZkBinary,
Result,
};
use darkfi_dao_contract::model::{DaoBulla, DaoProposalBulla};
use darkfi_money_contract::client::OwnCoin;
use darkfi_dao_contract::model::{Dao, DaoBulla, DaoProposal, DaoProposalBulla, DaoVoteParams};
use darkfi_money_contract::{
client::{MoneyNote, OwnCoin},
model::{
CoinAttributes, Input, MoneyFeeParamsV1, MoneyGenesisMintParamsV1, Nullifier, Output,
TokenAttributes, TokenId,
},
MoneyFunction,
};
use darkfi_sdk::{
bridgetree,
crypto::{
contract_id::MONEY_CONTRACT_ID,
poseidon_hash,
smt::{MemoryStorageFp, PoseidonFp, SmtMemoryFp, EMPTY_NODES_FP},
Keypair, MerkleNode, MerkleTree,
BaseBlind, FuncRef, Keypair, MerkleNode, MerkleTree, ScalarBlind, SecretKey,
},
pasta::pallas,
};
use darkfi_serial::{serialize, Encodable};
use num_bigint::BigUint;
use rand::rngs::OsRng;
use sled_overlay::sled;
use tracing::warn;
use tracing::{debug, warn};
/// Utility module for caching ZK proof PKs and VKs
pub mod vks;
/// `Money::PoWReward` functionality
mod money_pow_reward;
/// `Money::Fee` functionality
mod money_fee;
/// `Money::GenesisMint` functionality
mod money_genesis_mint;
/// `Money::OtcSwap` functionality
mod money_otc_swap;
/// `Money::PoWReward` functionality
mod money_pow_reward;
/// `Money::TokenMint` functionality
mod money_token;
/// `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;
/// `Deployooor::Lock` functionality
mod contract_lock;
/// `Dao::Exec` functionality
mod dao_exec;
/// `Dao::Mint` functionality
mod dao_mint;
/// `Dao::Propose` functionality
mod dao_propose;
/// `Dao::Vote` functionality
mod dao_vote;
/// `Dao::Exec` functionality
mod dao_exec;
/// PoW target
const POW_TARGET: u32 = 120;
/// Initialize the logging mechanism
pub fn init_logger() {
// We check this error so we can execute same file tests in parallel,
// otherwise second one fails to init logger here.
// We check this error so we can execute same-file tests in parallel.
// Otherwise subsequent calls fail to init the logger here.
if setup_test_logger(
&["sled"],
false,
@@ -104,7 +110,7 @@ pub fn init_logger() {
)
.is_err()
{
warn!(target: "test_harness", "Logger already initialized");
warn!(target: "test-harness", "Logger already initialized");
}
}
@@ -130,13 +136,16 @@ pub struct Wallet {
pub validator: ValidatorPtr,
/// Holder's instance of the Merkle tree for the `Money` contract
pub money_merkle_tree: MerkleTree,
/// Holder's instance of the SMT tree for the `Money` contract
/// Holder's instance of the nullifiers SMT tree for the `Money` contract
pub money_null_smt: SmtMemoryFp,
/// Holder's instance of the SMT tree for the `Money` contract (snapshotted for DAO::propose())
/// Holder's instance of the nullifiers SMT tree for the `Money` contract
/// snapshotted for `DAO::Propose`
pub money_null_smt_snapshot: Option<SmtMemoryFp>,
/// Holder's instance of the Merkle tree for the `DAO` contract (holding DAO bullas)
/// 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)
/// 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<OwnCoin>,
@@ -166,8 +175,8 @@ impl Wallet {
// Inject the cached VKs into the database
let overlay = BlockchainOverlay::new(&Blockchain::new(&sled_db)?)?;
vks::inject(&overlay, vks)?;
let pow_target = 120;
deploy_native_contracts(&overlay, pow_target).await?;
deploy_native_contracts(&overlay, POW_TARGET).await?;
let diff = overlay.lock().unwrap().overlay.lock().unwrap().diff(&[])?;
overlay.lock().unwrap().contracts.update_state_monotree(&diff)?;
overlay.lock().unwrap().overlay.lock().unwrap().apply()?;
@@ -175,15 +184,15 @@ impl Wallet {
// Create the `Validator` instance
let validator_config = ValidatorConfig {
confirmation_threshold: 3,
pow_target,
pow_target: POW_TARGET,
pow_fixed_difficulty: Some(BigUint::from(1_u8)),
genesis_block,
verify_fees,
};
let validator = Validator::new(&sled_db, &validator_config).await?;
// The Merkle tree for the `Money` contract is initialized with a "null"
// leaf at position 0.
// The Merkle tree for the Money contract is initialized with a
// "null" leaf at position 0.
let mut money_merkle_tree = MerkleTree::new(1);
money_merkle_tree.append(MerkleNode::from(pallas::Base::ZERO));
money_merkle_tree.mark().unwrap();
@@ -217,7 +226,7 @@ impl Wallet {
block_height: u32,
) -> Result<()> {
if self.bench_wasm {
let _ = benchmark_wasm_calls(callname, &self.validator, &tx, block_height).await;
let _ = benchmark_wasm_calls(callname, self.validator.clone(), &tx, block_height).await;
}
let validator = self.validator.read().await;
@@ -232,21 +241,85 @@ impl Wallet {
.await?;
// Write the data
{
let blockchain = &validator.blockchain;
let txs = &blockchain.transactions;
txs.insert(slice::from_ref(&tx)).expect("insert tx");
txs.insert_location(&[tx.hash()], block_height).expect("insert loc");
}
let blockchain = &validator.blockchain;
let txs = &blockchain.transactions;
txs.insert(slice::from_ref(&tx)).expect("insert tx");
txs.insert_location(&[tx.hash()], block_height).expect("insert loc");
Ok(())
}
/// Mark a single nullifier as spent in the SMT and move any matching
/// OwnCoin from `unspent_money_coins` to `spent_money_coins`
pub fn mark_spent_nullifier(&mut self, nullifier: Nullifier, holder: &Holder) {
let n = nullifier.inner();
self.money_null_smt.insert_batch(vec![(n, n)]).expect("smt.insert_batch()");
if let Some(spent_coin) =
self.unspent_money_coins.iter().find(|x| x.nullifier() == nullifier).cloned()
{
debug!("Found spent OwnCoin({}) for {:?}", spent_coin.coin, holder);
self.unspent_money_coins.retain(|x| x.nullifier() != nullifier);
self.spent_money_coins.push(spent_coin);
}
}
/// Process a set of [`Input`]s.
/// Insert nullifiers into the SMT and move any matching OwnCoins from
/// unspent to spent.
pub fn process_inputs(&mut self, inputs: &[Input], holder: &Holder) {
for input in inputs {
self.mark_spent_nullifier(input.nullifier, holder);
}
}
/// Process a set of [`Output`]s.
/// Append each coin to the Merkle tree and attempt to decrypt the note.
/// Returns any new OwnCoins found.
pub fn process_outputs(&mut self, outputs: &[Output], holder: &Holder) -> Vec<OwnCoin> {
let mut found = vec![];
for output in outputs {
self.money_merkle_tree.append(MerkleNode::from(output.coin.inner()));
let Ok(note) = output.note.decrypt::<MoneyNote>(&self.keypair.secret) else { continue };
let owncoin = OwnCoin {
coin: output.coin,
note: note.clone(),
secret: self.keypair.secret,
leaf_position: self.money_merkle_tree.mark().unwrap(),
};
debug!("Found new OwnCoin({}) for {:?}", owncoin.coin, holder);
self.unspent_money_coins.push(owncoin.clone());
found.push(owncoin);
}
found
}
/// Process the fee component of a transaction (if present).
/// Handles the fee input nullifier and fee change output.
/// Returns any new OwnCoins found.
pub fn process_fee(
&mut self,
fee_params: &Option<MoneyFeeParamsV1>,
holder: &Holder,
) -> Vec<OwnCoin> {
let Some(ref fp) = fee_params else { return vec![] };
self.mark_spent_nullifier(fp.input.nullifier, holder);
self.process_outputs(slice::from_ref(&fp.output), holder)
}
}
/// Native contract test harness instance
pub struct TestHarness {
/// Initialized [`Holder`]s for this instance
pub holders: HashMap<Holder, Wallet>,
/// Ordered list of all holder keys (for broadcast operations)
pub holder_keys: Vec<Holder>,
/// Cached [`ProvingKey`]s for native contract ZK proving
pub proving_keys: HashMap<String, (ProvingKey, ZkBinary)>,
/// The genesis block for this harness
@@ -283,13 +356,15 @@ impl TestHarness {
let sled_db = sled::Config::new().temporary(true).open()?;
let overlay = BlockchainOverlay::new(&Blockchain::new(&sled_db)?)?;
vks::inject(&overlay, &vks)?;
deploy_native_contracts(&overlay, 90).await?;
deploy_native_contracts(&overlay, POW_TARGET).await?;
let diff = overlay.lock().unwrap().overlay.lock().unwrap().diff(&[])?;
genesis_block.header.state_root =
overlay.lock().unwrap().contracts.update_state_monotree(&diff)?;
// Create `Wallet` instances
let mut holders_map = HashMap::new();
let mut holder_keys = Vec::with_capacity(holders.len());
for holder in holders {
let keypair = Keypair::random(&mut rng);
let token_mint_authority = Keypair::random(&mut rng);
@@ -306,35 +381,517 @@ impl TestHarness {
.await?;
holders_map.insert(*holder, wallet);
holder_keys.push(*holder);
}
Ok(Self { holders: holders_map, proving_keys, genesis_block, verify_fees })
Ok(Self { holders: holders_map, holder_keys, proving_keys, genesis_block, verify_fees })
}
/// Assert that all holders' trees are the same
/// Get a reference to a Holder's Wallet
pub fn wallet(&self, holder: &Holder) -> &Wallet {
self.holders.get(holder).unwrap()
}
/// Get a mutable reference to a Holder's Wallet
pub fn wallet_mut(&mut self, holder: &Holder) -> &mut Wallet {
self.holders.get_mut(holder).unwrap()
}
/// Get a Holder's unspent OwnCoins
pub fn coins(&self, holder: &Holder) -> &[OwnCoin] {
&self.wallet(holder).unspent_money_coins
}
/// Get a Holder's unspent OwnCoins filtered by token ID
pub fn coins_by_token(&self, holder: &Holder, token_id: TokenId) -> Vec<OwnCoin> {
self.coins(holder).iter().filter(|c| c.note.token_id == token_id).cloned().collect()
}
/// Get the total balance of a Holder for a given token
pub fn balance(&self, holder: &Holder, token_id: TokenId) -> u64 {
self.coins(holder)
.iter()
.filter(|c| c.note.token_id == token_id)
.map(|c| c.note.value)
.sum()
}
/// Assert that all holders' Merkle trees are consistent
pub fn assert_trees(&self, holders: &[Holder]) {
assert!(holders.len() > 1);
// Gather wallets
assert!(!holders.is_empty());
let mut wallets = vec![];
for holder in holders {
wallets.push(self.holders.get(holder).unwrap());
}
// Compare trees
let wallet = wallets[0];
let money_root = wallet.money_merkle_tree.root(0).unwrap();
let money_root = wallets[0].money_merkle_tree.root(0).unwrap();
for wallet in &wallets[1..] {
assert!(money_root == wallet.money_merkle_tree.root(0).unwrap());
assert_eq!(money_root, wallet.money_merkle_tree.root(0).unwrap());
}
}
/// Assert all registered holders' Merkle trees are consistent
pub fn assert_all_trees(&self) {
if !self.holder_keys.is_empty() {
self.assert_trees(&self.holder_keys);
}
}
/// Mint a token for `recipient` and execute the tx on all registered
/// holders.
/// Returns the minted `TokenId`
pub async fn token_mint_to_all(
&mut self,
amount: u64,
holder: &Holder,
recipient: &Holder,
block_height: u32,
) -> Result<TokenId> {
let token_blind = BaseBlind::random(&mut OsRng);
let (tx, mint_params, auth_params, fee_params) = self
.token_mint(amount, holder, recipient, token_blind, None, None, block_height)
.await?;
// Derive the Token ID
let token_id = self.derive_token_id(recipient, token_blind);
let holders = self.holder_keys.clone();
for h in &holders {
self.execute_token_mint_tx(
h,
tx.clone(),
&mint_params,
&auth_params,
&fee_params,
block_height,
true,
)
.await?;
}
self.assert_all_trees();
Ok(token_id)
}
/// Mint a token with a specific `token_blind` and execute on all
/// registered holders. Returns the [`TokenId`].
///
/// Use this instead of `token_mint_to_all` when you need the same
/// token blind across multiple mints (e.g. DAO governance tokens).
pub async fn token_mint_with_blind_to_all(
&mut self,
amount: u64,
holder: &Holder,
recipient: &Holder,
token_blind: BaseBlind,
block_height: u32,
) -> Result<TokenId> {
let (tx, mint_params, auth_params, fee_params) = self
.token_mint(amount, holder, recipient, token_blind, None, None, block_height)
.await?;
let token_id = self.derive_token_id(recipient, token_blind);
let holders = self.holder_keys.clone();
for h in &holders {
self.execute_token_mint_tx(
h,
tx.clone(),
&mint_params,
&auth_params,
&fee_params,
block_height,
true,
)
.await?;
}
self.assert_all_trees();
Ok(token_id)
}
/// Transfer `amount` of `token_id` from `sender` to `recipient` and
/// execute the tx on all registered holders.
pub async fn transfer_to_all(
&mut self,
amount: u64,
sender: &Holder,
recipient: &Holder,
token_id: TokenId,
block_height: u32,
) -> Result<()> {
let owncoins = self.coins_by_token(sender, token_id);
let (tx, (params, fee_params), _spent) = self
.transfer(amount, sender, recipient, &owncoins, token_id, block_height, false)
.await?;
let holders = self.holder_keys.clone();
for h in &holders {
self.execute_transfer_tx(h, tx.clone(), &params, &fee_params, block_height, true)
.await?;
}
self.assert_all_trees();
Ok(())
}
/// Build a genesis mint for `holder` and execute on all registered holders.
/// Returns the found [`OwnCoin`]s.
pub async fn genesis_mint_to_all(
&mut self,
holder: &Holder,
amounts: &[u64],
block_height: u32,
) -> Result<Vec<OwnCoin>> {
let (tx, params) = self.genesis_mint(holder, amounts, None, None).await?;
self.genesis_mint_to_all_with(tx, &params, block_height).await
}
/// Execute a pre-built genesis mint transaction on all registered holders.
/// Useful when you need to test the transaction before broadcasting
/// (e.g. malicious block height checks).
pub async fn genesis_mint_to_all_with(
&mut self,
tx: Transaction,
params: &MoneyGenesisMintParamsV1,
block_height: u32,
) -> Result<Vec<OwnCoin>> {
let holders = self.holder_keys.clone();
let mut found = vec![];
for h in &holders {
found.extend(
self.execute_genesis_mint_tx(h, tx.clone(), params, block_height, true).await?,
);
}
self.assert_all_trees();
Ok(found)
}
/// Freeze a token authority for `holder` and execute on all registered
/// holders.
pub async fn token_freeze_to_all(&mut self, holder: &Holder, block_height: u32) -> Result<()> {
let (tx, freeze_params, fee_params) = self.token_freeze(holder, block_height).await?;
let holders = self.holder_keys.clone();
for h in &holders {
self.execute_token_freeze_tx(
h,
tx.clone(),
&freeze_params,
&fee_params,
block_height,
true,
)
.await?;
}
self.assert_all_trees();
Ok(())
}
/// Generate a new block mined by `miner` and broadcast to all registered
/// holders. Convenience wrapper around `generate_block` that uses
/// `holder_keys` instead of requiring the caller to pass holders.
pub async fn generate_block_all(&mut self, miner: &Holder) -> Result<Vec<OwnCoin>> {
let holders = self.holder_keys.clone();
self.generate_block(miner, &holders).await
}
/// Consolidate all coins of `token_id` owned by `holder` into a single
/// coin by transferring to self, then execute on all registered holders.
pub async fn consolidate_to_all(
&mut self,
holder: &Holder,
token_id: TokenId,
block_height: u32,
) -> Result<()> {
let owncoins = self.coins_by_token(holder, token_id);
if owncoins.len() <= 1 {
// Nothing to consolidate
return Ok(())
}
let total: u64 = owncoins.iter().map(|c| c.note.value).sum();
let (tx, (params, fee_params), _spent) =
self.transfer(total, holder, holder, &owncoins, token_id, block_height, false).await?;
let holders = self.holder_keys.clone();
for h in &holders {
self.execute_transfer_tx(h, tx.clone(), &params, &fee_params, block_height, true)
.await?;
}
self.assert_all_trees();
Ok(())
}
/// Perform an OTC swap between two holders and execute on all registered
/// holders.
pub async fn otc_swap_to_all(
&mut self,
holder0: &Holder,
coin0: &OwnCoin,
holder1: &Holder,
coin1: &OwnCoin,
block_height: u32,
) -> Result<()> {
let (tx, params, fee_params) =
self.otc_swap(holder0, coin0, holder1, coin1, block_height).await?;
let holders = self.holder_keys.clone();
for h in &holders {
self.execute_otc_swap_tx(h, tx.clone(), &params, &fee_params, block_height, true)
.await?;
}
self.assert_all_trees();
Ok(())
}
/// Build a `Dao::Mint` transaction and execute on all registered holders.
#[allow(clippy::too_many_arguments)]
pub async fn dao_mint_to_all(
&mut self,
holder: &Holder,
dao: &Dao,
dao_notes_secret_key: &SecretKey,
dao_proposer_secret_key: &SecretKey,
dao_proposals_secret_key: &SecretKey,
dao_votes_secret_key: &SecretKey,
dao_exec_secret_key: &SecretKey,
dao_early_exec_secret_key: &SecretKey,
block_height: u32,
) -> Result<()> {
let (tx, params, fee_params) = self
.dao_mint(
holder,
dao,
dao_notes_secret_key,
dao_proposer_secret_key,
dao_proposals_secret_key,
dao_votes_secret_key,
dao_exec_secret_key,
dao_early_exec_secret_key,
block_height,
)
.await?;
let holders = self.holder_keys.clone();
for h in &holders {
self.execute_dao_mint_tx(h, tx.clone(), &params, &fee_params, block_height, true)
.await?;
}
self.assert_all_trees();
Ok(())
}
/// Build a `Dao::Propose` (transfer) transaction and execute on all
/// registered holders. Returns the [`DaoProposal`] for subsequent
/// voting/execution.
#[allow(clippy::too_many_arguments)]
pub async fn dao_propose_transfer_to_all(
&mut self,
proposer: &Holder,
proposal_coinattrs: &[CoinAttributes],
user_data: pallas::Base,
dao: &Dao,
dao_proposer_secret_key: &SecretKey,
block_height: u32,
duration_blockwindows: u64,
) -> Result<DaoProposal> {
let (tx, params, fee_params, proposal_info) = self
.dao_propose_transfer(
proposer,
proposal_coinattrs,
user_data,
dao,
dao_proposer_secret_key,
block_height,
duration_blockwindows,
)
.await?;
let holders = self.holder_keys.clone();
for h in &holders {
self.execute_dao_propose_tx(h, tx.clone(), &params, &fee_params, block_height, true)
.await?;
}
self.assert_all_trees();
Ok(proposal_info)
}
/// Build a `Dao::Propose` (generic) transaction and execute on all
/// registered holders. Returns the [`DaoProposal`].
pub async fn dao_propose_generic_to_all(
&mut self,
proposer: &Holder,
user_data: pallas::Base,
dao: &Dao,
dao_proposer_secret_key: &SecretKey,
block_height: u32,
duration_blockwindows: u64,
) -> Result<DaoProposal> {
let (tx, params, fee_params, proposal_info) = self
.dao_propose_generic(
proposer,
user_data,
dao,
dao_proposer_secret_key,
block_height,
duration_blockwindows,
)
.await?;
let holders = self.holder_keys.clone();
for h in &holders {
self.execute_dao_propose_tx(h, tx.clone(), &params, &fee_params, block_height, true)
.await?;
}
self.assert_all_trees();
Ok(proposal_info)
}
/// Build and broadcast a single `Dao::Vote` transaction on all registered
/// holders. Returns the [`DaoVoteParams`] (needed for vote counting).
pub async fn dao_vote_to_all(
&mut self,
voter: &Holder,
vote_option: bool,
dao: &Dao,
proposal: &DaoProposal,
block_height: u32,
) -> Result<DaoVoteParams> {
let (tx, vote_params, fee_params) =
self.dao_vote(voter, vote_option, dao, proposal, block_height).await?;
let holders = self.holder_keys.clone();
for h in &holders {
self.execute_dao_vote_tx(h, tx.clone(), &fee_params, block_height, true).await?;
}
self.assert_all_trees();
Ok(vote_params)
}
/// Build and broadcast a `Dao::Exec` (transfer) transaction on all
/// registered holders.
#[allow(clippy::too_many_arguments)]
pub async fn dao_exec_transfer_to_all(
&mut self,
executor: &Holder,
dao: &Dao,
dao_exec_secret_key: &SecretKey,
dao_early_exec_secret_key: &Option<SecretKey>,
proposal_info: &DaoProposal,
proposal_coinattrs: Vec<CoinAttributes>,
total_yes_vote_value: u64,
total_all_vote_value: u64,
total_yes_vote_blind: ScalarBlind,
total_all_vote_blind: ScalarBlind,
block_height: u32,
) -> Result<()> {
let (tx, xfer_params, fee_params) = self
.dao_exec_transfer(
executor,
dao,
dao_exec_secret_key,
dao_early_exec_secret_key,
proposal_info,
proposal_coinattrs,
total_yes_vote_value,
total_all_vote_value,
total_yes_vote_blind,
total_all_vote_blind,
block_height,
)
.await?;
let holders = self.holder_keys.clone();
for h in &holders {
self.execute_dao_exec_tx(
h,
tx.clone(),
Some(&xfer_params),
&fee_params,
block_height,
true,
)
.await?;
}
self.assert_all_trees();
Ok(())
}
/// Build and broadcast a `Dao::Exec` (generic) transaction on all
/// registered holders.
#[allow(clippy::too_many_arguments)]
pub async fn dao_exec_generic_to_all(
&mut self,
executor: &Holder,
dao: &Dao,
dao_exec_secret_key: &SecretKey,
dao_early_exec_secret_key: &Option<SecretKey>,
proposal_info: &DaoProposal,
total_yes_vote_value: u64,
total_all_vote_value: u64,
total_yes_vote_blind: ScalarBlind,
total_all_vote_blind: ScalarBlind,
block_height: u32,
) -> Result<()> {
let (tx, fee_params) = self
.dao_exec_generic(
executor,
dao,
dao_exec_secret_key,
dao_early_exec_secret_key,
proposal_info,
total_yes_vote_value,
total_all_vote_value,
total_yes_vote_blind,
total_all_vote_blind,
block_height,
)
.await?;
let holders = self.holder_keys.clone();
for h in &holders {
self.execute_dao_exec_tx(h, tx.clone(), None, &fee_params, block_height, true).await?;
}
self.assert_all_trees();
Ok(())
}
/// Derive the [`TokenId`] that a given holder's `token_mint_authority`
/// would produce with a given blind.
pub fn derive_token_id(&self, holder: &Holder, token_blind: BaseBlind) -> TokenId {
let wallet = self.wallet(holder);
let mint_authority = wallet.token_mint_authority;
let auth_func_id = FuncRef {
contract_id: *MONEY_CONTRACT_ID,
func_code: MoneyFunction::AuthTokenMintV1 as u8,
}
.to_func_id();
let token_attrs = TokenAttributes {
auth_parent: auth_func_id,
user_data: poseidon_hash([mint_authority.public.x(), mint_authority.public.y()]),
blind: token_blind,
};
token_attrs.to_token_id()
}
}
async fn benchmark_wasm_calls(
callname: &str,
validator: &ValidatorPtr,
validator: ValidatorPtr,
tx: &Transaction,
block_height: u32,
) -> Result<()> {
let mut file = std::fs::OpenOptions::new().create(true).append(true).open("bench.csv")?;
let mut file = OpenOptions::new().create(true).append(true).open("bench.csv")?;
let validator = validator.read().await;
for (idx, call) in tx.calls.iter().enumerate() {
@@ -356,29 +913,29 @@ async fn benchmark_wasm_calls(
tx.calls.encode(&mut payload)?;
let mut times = [0; 3];
let now = std::time::Instant::now();
let now = Instant::now();
let _metadata = runtime.metadata(&payload)?;
times[0] = now.elapsed().as_micros();
let now = std::time::Instant::now();
let now = Instant::now();
let mut update = vec![call.data.data[0]];
update.append(&mut runtime.exec(&payload)?);
times[1] = now.elapsed().as_micros();
let now = std::time::Instant::now();
let now = Instant::now();
runtime.apply(&update)?;
times[2] = now.elapsed().as_micros();
writeln!(
file,
"{}, {}, {}, {}, {}, {}, {}",
"{},{},{},{},{},{},{}",
callname,
tx.hash(),
idx,
times[0],
times[1],
times[2],
serialize(tx).len()
serialize(tx).len(),
)?;
}

View File

@@ -36,14 +36,14 @@ use darkfi_money_contract::{
use darkfi_sdk::{
crypto::{
contract_id::MONEY_CONTRACT_ID, note::AeadEncryptedNote, BaseBlind, Blind, FuncId,
MerkleNode, ScalarBlind, SecretKey,
ScalarBlind, SecretKey,
},
pasta::pallas,
ContractCall,
};
use darkfi_serial::AsyncEncodable;
use darkfi_serial::Encodable;
use rand::rngs::OsRng;
use tracing::{debug, info};
use tracing::info;
use super::{Holder, TestHarness};
@@ -55,7 +55,7 @@ impl TestHarness {
&mut self,
holder: &Holder,
) -> Result<(Transaction, MoneyFeeParamsV1)> {
let wallet = self.holders.get(holder).unwrap();
let wallet = self.wallet(holder);
// Compute fee call required fee
let required_fee = compute_fee(&FEE_CALL_GAS);
@@ -92,7 +92,7 @@ impl TestHarness {
// Generate an ephemeral signing key
let signature_secret = SecretKey::random(&mut OsRng);
info!("Creting FeeV1 ZK proof");
info!("Creating FeeV1 ZK proof");
let (fee_pk, fee_zkbin) = self.proving_keys.get(MONEY_CONTRACT_ZKAS_FEE_NS_V1).unwrap();
let (proof, public_inputs) = create_fee_proof(
@@ -143,8 +143,8 @@ impl TestHarness {
};
let mut data = vec![MoneyFunction::FeeV1 as u8];
required_fee.encode_async(&mut data).await?;
params.encode_async(&mut data).await?;
required_fee.encode(&mut data)?;
params.encode(&mut data)?;
let call = ContractCall { contract_id: *MONEY_CONTRACT_ID, data };
let mut tx_builder =
TransactionBuilder::new(ContractCallLeaf { call, proofs: vec![proof] }, vec![])?;
@@ -155,7 +155,7 @@ impl TestHarness {
Ok((tx, params))
}
/// Execute the transaction created by `create_empty_fee_call()` for a given [`Holder`]
/// 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(
@@ -165,44 +165,9 @@ impl TestHarness {
params: &MoneyFeeParamsV1,
block_height: u32,
) -> Result<Vec<OwnCoin>> {
let wallet = self.holders.get_mut(holder).unwrap();
let nullifier = params.input.nullifier.inner();
wallet
.money_null_smt
.insert_batch(vec![(nullifier, nullifier)])
.expect("smt.insert_batch()");
let wallet = self.wallet_mut(holder);
wallet.add_transaction("money::fee", tx, block_height).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::<MoneyNote>(&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])
Ok(wallet.process_fee(&Some(params.clone()), holder))
}
/// Create and append a `Money::Fee` call to a given [`Transaction`] for
@@ -218,9 +183,9 @@ impl TestHarness {
block_height: u32,
spent_coins: &[OwnCoin],
) -> Result<(ContractCall, Vec<Proof>, Vec<SecretKey>, Vec<OwnCoin>, 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();
// First we verify the fee-less transaction to see how much gas it
// uses for execution and verification.
let wallet = self.wallet(holder);
let validator = wallet.validator.read().await;
let gas_used = validator
.add_test_transactions(
@@ -236,8 +201,8 @@ impl TestHarness {
// Compute the required fee
let required_fee = compute_fee(&(gas_used + FEE_CALL_GAS));
// Knowing the total gas, we can now find an OwnCoin of enough value
// so that we can create a valid Money::Fee call.
// 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
@@ -325,8 +290,8 @@ impl TestHarness {
// Encode the contract call
let mut data = vec![MoneyFunction::FeeV1 as u8];
required_fee.encode_async(&mut data).await?;
params.encode_async(&mut data).await?;
required_fee.encode(&mut data)?;
params.encode(&mut data)?;
let call = ContractCall { contract_id: *MONEY_CONTRACT_ID, data };
Ok((call, vec![proof], vec![signature_secret], vec![coin.clone()], params))

View File

@@ -21,17 +21,16 @@ use darkfi::{
Result,
};
use darkfi_money_contract::{
client::{genesis_mint_v1::GenesisMintCallBuilder, MoneyNote, OwnCoin},
client::{genesis_mint_v1::GenesisMintCallBuilder, OwnCoin},
model::MoneyGenesisMintParamsV1,
MoneyFunction, MONEY_CONTRACT_ZKAS_MINT_NS_V1,
};
use darkfi_sdk::{
crypto::{contract_id::MONEY_CONTRACT_ID, FuncId, MerkleNode},
crypto::{contract_id::MONEY_CONTRACT_ID, FuncId},
pasta::pallas,
ContractCall,
};
use darkfi_serial::AsyncEncodable;
use tracing::debug;
use darkfi_serial::Encodable;
use super::{Holder, TestHarness};
@@ -46,7 +45,7 @@ impl TestHarness {
spend_hook: Option<FuncId>,
user_data: Option<pallas::Base>,
) -> Result<(Transaction, MoneyGenesisMintParamsV1)> {
let wallet = self.holders.get(holder).unwrap();
let wallet = self.wallet(holder);
let (mint_pk, mint_zkbin) = self.proving_keys.get(MONEY_CONTRACT_ZKAS_MINT_NS_V1).unwrap();
@@ -65,7 +64,7 @@ impl TestHarness {
// Encode and build the transaction
let mut data = vec![MoneyFunction::GenesisMintV1 as u8];
debris.params.encode_async(&mut data).await?;
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![])?;
@@ -89,35 +88,12 @@ impl TestHarness {
) -> Result<Vec<OwnCoin>> {
let wallet = self.holders.get_mut(holder).unwrap();
// Execute the transaction
wallet.add_transaction("money::genesis_mint", tx, block_height).await?;
if !append {
return Ok(vec![])
return Ok(vec![]);
}
// Iterate over call outputs to find any new OwnCoins
let mut found_owncoins = vec![];
for output in &params.outputs {
wallet.money_merkle_tree.append(MerkleNode::from(output.coin.inner()));
// Attempt to decrypt the output note to see if this is a coin for the holder.
let Ok(note) = output.note.decrypt::<MoneyNote>(&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)
Ok(wallet.process_outputs(&params.outputs, holder))
}
}

View File

@@ -24,18 +24,17 @@ use darkfi::{
Result,
};
use darkfi_money_contract::{
client::{swap_v1::SwapCallBuilder, MoneyNote, OwnCoin},
client::{swap_v1::SwapCallBuilder, OwnCoin},
model::{MoneyFeeParamsV1, MoneyTransferParamsV1},
MoneyFunction, MONEY_CONTRACT_ZKAS_BURN_NS_V1, MONEY_CONTRACT_ZKAS_MINT_NS_V1,
};
use darkfi_sdk::{
crypto::{contract_id::MONEY_CONTRACT_ID, BaseBlind, Blind, FuncId, MerkleNode},
crypto::{contract_id::MONEY_CONTRACT_ID, BaseBlind, Blind, FuncId},
pasta::pallas,
ContractCall,
};
use darkfi_serial::AsyncEncodable;
use darkfi_serial::Encodable;
use rand::rngs::OsRng;
use tracing::debug;
use super::{Holder, TestHarness};
@@ -51,8 +50,8 @@ impl TestHarness {
owncoin1: &OwnCoin,
block_height: u32,
) -> Result<(Transaction, MoneyTransferParamsV1, Option<MoneyFeeParamsV1>)> {
let wallet0 = self.holders.get(holder0).unwrap();
let wallet1 = self.holders.get(holder1).unwrap();
let wallet0 = self.wallet(holder0);
let wallet1 = self.wallet(holder1);
let (mint_pk, mint_zkbin) = self.proving_keys.get(MONEY_CONTRACT_ZKAS_MINT_NS_V1).unwrap();
let (burn_pk, burn_zkbin) = self.proving_keys.get(MONEY_CONTRACT_ZKAS_BURN_NS_V1).unwrap();
@@ -131,7 +130,7 @@ impl TestHarness {
// Encode the contract call
let mut data = vec![MoneyFunction::OtcSwapV1 as u8];
swap_full_params.encode_async(&mut data).await?;
swap_full_params.encode(&mut data)?;
let call = ContractCall { contract_id: *MONEY_CONTRACT_ID, data };
let mut tx_builder =
TransactionBuilder::new(ContractCallLeaf { call, proofs: swap_full_proofs }, vec![])?;
@@ -185,60 +184,23 @@ impl TestHarness {
block_height: u32,
append: bool,
) -> Result<Vec<OwnCoin>> {
let wallet = self.holders.get_mut(holder).unwrap();
let wallet = self.wallet_mut(holder);
// Execute the transaction
wallet.add_transaction("money::otc_swap", tx, block_height).await?;
let mut found_owncoins = vec![];
if !append {
return Ok(found_owncoins)
return Ok(vec![]);
}
// Combine swap inputs/outputs with fee inputs/outputs, then process
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());
if let Some(ref fp) = fee_params {
inputs.push(fp.input.clone());
outputs.push(fp.output.clone());
}
let nullifiers = inputs.iter().map(|i| i.nullifier.inner()).map(|l| (l, l)).collect();
wallet.money_null_smt.insert_batch(nullifiers).expect("smt.insert_batch()");
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());
}
}
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::<MoneyNote>(&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)
wallet.process_inputs(&inputs, holder);
Ok(wallet.process_outputs(&outputs, holder))
}
}

View File

@@ -16,6 +16,8 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
use std::slice;
use darkfi::{
blockchain::{BlockInfo, BlockchainOverlay, Header},
tx::{ContractCallLeaf, Transaction, TransactionBuilder},
@@ -23,15 +25,15 @@ use darkfi::{
Result,
};
use darkfi_money_contract::{
client::{pow_reward_v1::PoWRewardCallBuilder, MoneyNote, OwnCoin},
client::{pow_reward_v1::PoWRewardCallBuilder, OwnCoin},
model::MoneyPoWRewardParamsV1,
MoneyFunction, MONEY_CONTRACT_ZKAS_MINT_NS_V1,
};
use darkfi_sdk::{
crypto::{contract_id::MONEY_CONTRACT_ID, MerkleNode, MerkleTree},
crypto::{contract_id::MONEY_CONTRACT_ID, MerkleTree},
ContractCall,
};
use darkfi_serial::AsyncEncodable;
use darkfi_serial::Encodable;
use tracing::info;
use super::{Holder, TestHarness};
@@ -39,7 +41,8 @@ use super::{Holder, TestHarness};
impl TestHarness {
/// Create a `Money::PoWReward` transaction for a given [`Holder`].
///
/// Optionally takes a specific reward recipient and a nonstandard reward value.
/// Optionally takes a specific reward recipient and a nonstandard
/// reward value.
/// Returns the created [`Transaction`] and [`MoneyPoWRewardParamsV1`].
async fn pow_reward(
&mut self,
@@ -48,7 +51,7 @@ impl TestHarness {
reward: Option<u64>,
fees: Option<u64>,
) -> Result<(Transaction, MoneyPoWRewardParamsV1)> {
let wallet = self.holders.get(holder).unwrap();
let wallet = self.wallet(holder);
let (mint_pk, mint_zkbin) = self.proving_keys.get(MONEY_CONTRACT_ZKAS_MINT_NS_V1).unwrap();
@@ -56,11 +59,7 @@ impl TestHarness {
let last_block = wallet.validator.read().await.blockchain.last_block()?;
// If there's a set reward recipient, use it, otherwise reward the holder
let recipient = if let Some(holder) = recipient {
Some(self.holders.get(holder).unwrap().keypair.public)
} else {
None
};
let recipient = recipient.map(|holder| self.wallet(holder).keypair.public);
// If there's fees paid, use them, otherwise set to zero
let fees = fees.unwrap_or_default();
@@ -84,7 +83,7 @@ impl TestHarness {
// Encode the transaction
let mut data = vec![MoneyFunction::PoWRewardV1 as u8];
debris.params.encode_async(&mut data).await?;
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![])?;
@@ -95,7 +94,7 @@ impl TestHarness {
Ok((tx, debris.params))
}
/// Generate and add an empty block to the given [`Holder`]s blockchains.
/// Generate and add an empty block to the given [`Holder`]'s blockchains.
/// The `miner` holder will produce the block and receive the reward.
///
/// Returns any found [`OwnCoin`]s.
@@ -109,7 +108,7 @@ impl TestHarness {
let (tx, params) = self.pow_reward(miner, None, None, None).await?;
// Fetch the last block in the blockchain
let wallet = self.holders.get(miner).unwrap();
let wallet = self.wallet(miner);
let validator = wallet.validator.read().await;
let previous = validator.blockchain.last_block()?;
@@ -141,33 +140,19 @@ impl TestHarness {
)
.await?;
drop(validator);
let diff = overlay.lock().unwrap().overlay.lock().unwrap().diff(&[])?;
block.header.state_root = overlay.lock().unwrap().contracts.update_state_monotree(&diff)?;
// Attach signature
block.sign(&wallet.keypair.secret);
// For all holders, append the block
// For all holders, append the block and process the reward output
let mut found_owncoins = vec![];
for holder in holders {
let wallet = self.holders.get_mut(holder).unwrap();
let wallet = self.wallet_mut(holder);
wallet.validator.write().await.add_test_blocks(&[block.clone()]).await?;
wallet.money_merkle_tree.append(MerkleNode::from(params.output.coin.inner()));
// Attempt to decrypt the note to see if this is a coin for the holder
let Ok(note) = params.output.note.decrypt::<MoneyNote>(&wallet.keypair.secret) else {
continue
};
let owncoin = OwnCoin {
coin: params.output.coin,
note: note.clone(),
secret: wallet.keypair.secret,
leaf_position: wallet.money_merkle_tree.mark().unwrap(),
};
wallet.unspent_money_coins.push(owncoin.clone());
found_owncoins.push(owncoin);
found_owncoins.extend(wallet.process_outputs(slice::from_ref(&params.output), holder));
}
Ok(found_owncoins)

View File

@@ -39,7 +39,7 @@ use darkfi_sdk::{
pasta::pallas,
ContractCall,
};
use darkfi_serial::AsyncEncodable;
use darkfi_serial::Encodable;
use rand::rngs::OsRng;
use tracing::debug;
@@ -63,9 +63,9 @@ impl TestHarness {
MoneyAuthTokenMintParamsV1,
Option<MoneyFeeParamsV1>,
)> {
let wallet = self.holders.get(holder).unwrap();
let wallet = self.wallet(holder);
let mint_authority = wallet.token_mint_authority;
let rcpt = self.holders.get(recipient).unwrap().keypair.public;
let rcpt = self.wallet(recipient).keypair.public;
let (token_mint_pk, token_mint_zkbin) =
self.proving_keys.get(MONEY_CONTRACT_ZKAS_TOKEN_MINT_NS_V1).unwrap();
@@ -109,7 +109,7 @@ impl TestHarness {
};
let auth_debris = builder.build()?;
let mut data = vec![MoneyFunction::AuthTokenMintV1 as u8];
auth_debris.params.encode_async(&mut data).await?;
auth_debris.params.encode(&mut data)?;
let auth_call = ContractCall { contract_id: *MONEY_CONTRACT_ID, data };
// Create the minting call
@@ -121,7 +121,7 @@ impl TestHarness {
};
let mint_debris = builder.build()?;
let mut data = vec![MoneyFunction::TokenMintV1 as u8];
mint_debris.params.encode_async(&mut data).await?;
mint_debris.params.encode(&mut data)?;
let mint_call = ContractCall { contract_id: *MONEY_CONTRACT_ID, data };
// Create the TransactionBuilder containing above calls
@@ -180,35 +180,17 @@ impl TestHarness {
block_height: u32,
append: bool,
) -> Result<Vec<OwnCoin>> {
let wallet = self.holders.get_mut(holder).unwrap();
let wallet = self.wallet_mut(holder);
// Execute the transaction
wallet.add_transaction("money::token_mint", tx, block_height).await?;
// 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());
}
}
}
let mut found_owncoins = vec![];
if append {
wallet.money_merkle_tree.append(MerkleNode::from(mint_params.coin.inner()));
// Process the fee input (mark spent)
found_owncoins.extend(wallet.process_fee(fee_params, holder));
// Attempt to decrypt the encrypted note of the minted token
// Process the minted coin output
wallet.money_merkle_tree.append(MerkleNode::from(mint_params.coin.inner()));
if let Ok(note) = auth_params.enc_note.decrypt::<MoneyNote>(&wallet.keypair.secret) {
let owncoin = OwnCoin {
coin: mint_params.coin,
@@ -216,30 +198,9 @@ impl TestHarness {
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::<MoneyNote>(&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);
}
}
}
@@ -252,7 +213,7 @@ impl TestHarness {
holder: &Holder,
block_height: u32,
) -> Result<(Transaction, MoneyAuthTokenFreezeParamsV1, Option<MoneyFeeParamsV1>)> {
let wallet = self.holders.get(holder).unwrap();
let wallet = self.wallet(holder);
let mint_authority = wallet.token_mint_authority;
let (auth_mint_pk, auth_mint_zkbin) =
@@ -282,7 +243,7 @@ impl TestHarness {
};
let freeze_debris = builder.build()?;
let mut data = vec![MoneyFunction::AuthTokenFreezeV1 as u8];
freeze_debris.params.encode_async(&mut data).await?;
freeze_debris.params.encode(&mut data)?;
let freeze_call = ContractCall { contract_id: *MONEY_CONTRACT_ID, data };
// Create the TransactionBuilder containing the above call
@@ -332,53 +293,14 @@ impl TestHarness {
block_height: u32,
append: bool,
) -> Result<Vec<OwnCoin>> {
let wallet = self.holders.get_mut(holder).unwrap();
let wallet = self.wallet_mut(holder);
// Execute the transaction
wallet.add_transaction("money::token_freeze", tx, block_height).await?;
let mut found_owncoins = vec![];
if let Some(ref fee_params) = fee_params {
if append {
let nullifier = fee_params.input.nullifier.inner();
wallet
.money_null_smt
.insert_batch(vec![(nullifier, nullifier)])
.expect("smt.insert_batch()");
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::<MoneyNote>(&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);
}
}
if !append {
return Ok(vec![]);
}
Ok(found_owncoins)
Ok(wallet.process_fee(fee_params, holder))
}
}

View File

@@ -21,16 +21,12 @@ use darkfi::{
Result,
};
use darkfi_money_contract::{
client::{transfer_v1::make_transfer_call, MoneyNote, OwnCoin},
client::{transfer_v1::make_transfer_call, OwnCoin},
model::{MoneyFeeParamsV1, MoneyTransferParamsV1, TokenId},
MoneyFunction, MONEY_CONTRACT_ZKAS_BURN_NS_V1, MONEY_CONTRACT_ZKAS_MINT_NS_V1,
};
use darkfi_sdk::{
crypto::{contract_id::MONEY_CONTRACT_ID, MerkleNode},
ContractCall,
};
use darkfi_serial::AsyncEncodable;
use tracing::debug;
use darkfi_sdk::{crypto::contract_id::MONEY_CONTRACT_ID, ContractCall};
use darkfi_serial::Encodable;
use super::{Holder, TestHarness};
@@ -48,8 +44,8 @@ impl TestHarness {
half_split: bool,
) -> Result<(Transaction, (MoneyTransferParamsV1, Option<MoneyFeeParamsV1>), Vec<OwnCoin>)>
{
let wallet = self.holders.get(holder).unwrap();
let rcpt = self.holders.get(recipient).unwrap().keypair.public;
let wallet = self.wallet(holder);
let rcpt = self.wallet(recipient).keypair.public;
let (mint_pk, mint_zkbin) = self.proving_keys.get(MONEY_CONTRACT_ZKAS_MINT_NS_V1).unwrap();
let (burn_pk, burn_zkbin) = self.proving_keys.get(MONEY_CONTRACT_ZKAS_BURN_NS_V1).unwrap();
@@ -73,20 +69,21 @@ impl TestHarness {
// Encode the call
let mut data = vec![MoneyFunction::TransferV1 as u8];
params.encode_async(&mut data).await?;
params.encode(&mut data)?;
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.
// If we have tx fees enabled, we first have to execute the fee-less
// transaction 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 {
@@ -118,7 +115,7 @@ impl TestHarness {
/// Execute a `Money::Transfer` transaction for a given [`Holder`].
///
/// Returns any found [`OwnCoin`]s.
/// Returns any found [`OwnCoin`]s
pub async fn execute_transfer_tx(
&mut self,
holder: &Holder,
@@ -128,95 +125,17 @@ impl TestHarness {
block_height: u32,
append: bool,
) -> Result<Vec<OwnCoin>> {
let wallet = self.holders.get_mut(holder).unwrap();
let wallet = self.wallet_mut(holder);
// Execute the transaction
wallet.add_transaction("money::transfer", tx, block_height).await?;
// Iterate over call inputs to mark any spent coins
let nullifiers =
call_params.inputs.iter().map(|i| i.nullifier.inner()).map(|l| (l, l)).collect();
wallet.money_null_smt.insert_batch(nullifiers).expect("smt.insert_batch()");
// Always insert nullifiers into SMT (needed for state consistency)
wallet.process_inputs(&call_params.inputs, holder);
let mut found_owncoins = vec![];
if append {
for input in &call_params.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());
}
}
// Iterate over call outputs to find any new OwnCoins
for output in &call_params.outputs {
wallet.money_merkle_tree.append(MerkleNode::from(output.coin.inner()));
// Attempt to decrypt the output note to see if this is a coin for the holder.
let Ok(note) = output.note.decrypt::<MoneyNote>(&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);
}
}
// Handle fee call
if let Some(ref fee_params) = fee_params {
// Process call input to mark any spent coins
let nullifier = fee_params.input.nullifier.inner();
wallet
.money_null_smt
.insert_batch(vec![(nullifier, nullifier)])
.expect("smt.insert_batch()");
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());
}
// Process call output to find any new OwnCoins
wallet.money_merkle_tree.append(MerkleNode::from(fee_params.output.coin.inner()));
// Attempt to decrypt the output note to see if this is a coin for the holder.
if let Ok(note) =
fee_params.output.note.decrypt::<MoneyNote>(&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);
};
}
found_owncoins.extend(wallet.process_outputs(&call_params.outputs, holder));
found_owncoins.extend(wallet.process_fee(fee_params, holder));
}
Ok(found_owncoins)