contract: WIP implementation of generic test harness for native contracts.

This commit is contained in:
parazyd
2023-06-14 02:32:56 +02:00
parent b36861fa1d
commit a2ddbcd4f8
10 changed files with 871 additions and 0 deletions

20
Cargo.lock generated
View File

@@ -1355,6 +1355,25 @@ dependencies = [
"thiserror",
]
[[package]]
name = "darkfi-contract-test-harness"
version = "0.4.1"
dependencies = [
"blake3",
"bs58",
"darkfi",
"darkfi-consensus-contract",
"darkfi-dao-contract",
"darkfi-deployooor-contract",
"darkfi-money-contract",
"darkfi-sdk",
"darkfi-serial",
"log",
"rand",
"simplelog",
"sled",
]
[[package]]
name = "darkfi-dao-contract"
version = "0.4.1"
@@ -1413,6 +1432,7 @@ dependencies = [
"bs58",
"chacha20poly1305",
"darkfi",
"darkfi-contract-test-harness",
"darkfi-sdk",
"darkfi-serial",
"getrandom",

View File

@@ -47,6 +47,7 @@ members = [
"src/serial/derive",
"src/serial/derive-internal",
"src/contract/test-harness",
"src/contract/money",
"src/contract/dao",
"src/contract/consensus",

View File

@@ -163,6 +163,7 @@ impl ContractStateStore {
}
// We open the tree and clear it. This is unfortunately not atomic.
// TODO: FIXME: Can we make it atomic?
let tree = db.open_tree(ptr)?;
tree.clear()?;

1
src/contract/test-harness/.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
vks.bin

View File

@@ -0,0 +1,22 @@
[package]
name = "darkfi-contract-test-harness"
version = "0.4.1"
authors = ["Dyne.org foundation <foundation@dyne.org>"]
license = "AGPL-3.0-only"
edition = "2021"
[dependencies]
darkfi = {path = "../../../", features = ["zk", "tx", "blockchain"]}
darkfi-sdk = {path = "../../../src/sdk"}
darkfi-serial = {path = "../../../src/serial", features = ["derive", "crypto"]}
darkfi-dao-contract = {path = "../dao", features = ["client", "no-entrypoint"]}
darkfi-money-contract = {path = "../money", features = ["client", "no-entrypoint"]}
darkfi-consensus-contract = {path = "../consensus", features = ["client", "no-entrypoint"]}
darkfi-deployooor-contract = {path = "../deployooor", features = ["no-entrypoint"]}
blake3 = "1.4.0"
bs58 = "0.5.0"
log = "0.4.19"
rand = "0.8.5"
simplelog = "0.12.1"
sled = "0.34.7"

View File

@@ -0,0 +1,68 @@
/* This file is part of DarkFi (https://dark.fi)
*
* Copyright (C) 2020-2023 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 <https://www.gnu.org/licenses/>.
*/
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<usize>,
/// Vector holding each transaction broadcasted size in Bytes
pub broadcasted_sizes: Vec<usize>,
/// Vector holding each transaction creation time
pub creation_times: Vec<Duration>,
/// Vector holding each transaction verify time
pub verify_times: Vec<Duration>,
}
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::<usize>();
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::<usize>();
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::<Duration>();
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::<Duration>();
let avg = avg / self.verify_times.len() as u32;
info!("Average {:?} verification time: {:?}", action, avg);
}
}
}

View File

@@ -0,0 +1,288 @@
/* This file is part of DarkFi (https://dark.fi)
*
* Copyright (C) 2020-2023 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 <https://www.gnu.org/licenses/>.
*/
use std::collections::HashMap;
use darkfi::{
consensus::{
ValidatorState, ValidatorStatePtr, TESTNET_BOOTSTRAP_TIMESTAMP, TESTNET_GENESIS_HASH_BYTES,
TESTNET_GENESIS_TIMESTAMP, TESTNET_INITIAL_DISTRIBUTION,
},
runtime::vm_runtime::SMART_CONTRACT_ZKAS_DB_NAME,
wallet::{WalletDb, WalletPtr},
zk::{empty_witnesses, ProvingKey, ZkCircuit},
zkas::ZkBinary,
Result,
};
use darkfi_dao_contract::{
DAO_CONTRACT_ZKAS_DAO_EXEC_NS, DAO_CONTRACT_ZKAS_DAO_MINT_NS,
DAO_CONTRACT_ZKAS_DAO_PROPOSE_BURN_NS, DAO_CONTRACT_ZKAS_DAO_PROPOSE_MAIN_NS,
DAO_CONTRACT_ZKAS_DAO_VOTE_BURN_NS, DAO_CONTRACT_ZKAS_DAO_VOTE_MAIN_NS,
};
use darkfi_money_contract::{
client::{MoneyNote, OwnCoin},
model::Output,
CONSENSUS_CONTRACT_ZKAS_BURN_NS_V1, CONSENSUS_CONTRACT_ZKAS_MINT_NS_V1,
CONSENSUS_CONTRACT_ZKAS_PROPOSAL_NS_V1, MONEY_CONTRACT_ZKAS_BURN_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::{
poseidon_hash, Keypair, MerkleTree, Nullifier, PublicKey, SecretKey, CONSENSUS_CONTRACT_ID,
DAO_CONTRACT_ID, MONEY_CONTRACT_ID,
};
use darkfi_serial::{deserialize, serialize};
use log::info;
use rand::rngs::OsRng;
mod benchmarks;
use benchmarks::TxActionBenchmarks;
mod vks;
mod money_airdrop;
mod money_token;
pub fn init_logger() {
let mut cfg = simplelog::ConfigBuilder::new();
cfg.add_filter_ignore("sled".to_string());
if let Err(_) = simplelog::TermLogger::init(
simplelog::LevelFilter::Info,
//simplelog::LevelFilter::Debug,
//simplelog::LevelFilter::Trace,
cfg.build(),
simplelog::TerminalMode::Mixed,
simplelog::ColorChoice::Auto,
) {
return
}
}
/// Enum representing configured wallet holders
#[derive(Debug, Eq, Hash, PartialEq)]
pub enum Holder {
Faucet,
Alice,
Bob,
Charlie,
Rachel,
}
/// Enum representing transaction actions
#[derive(Debug, Eq, Hash, PartialEq)]
pub enum TxAction {
MoneyAirdrop,
MoneyTokenMint,
MoneyTokenFreeze,
MoneyGenesisMint,
MoneyTransfer,
MoneyOtcSwap,
}
pub struct Wallet {
pub keypair: Keypair,
pub token_mint_authority: Keypair,
pub state: ValidatorStatePtr,
pub money_merkle_tree: MerkleTree,
pub consensus_staked_merkle_tree: MerkleTree,
pub consensus_unstaked_merkle_tree: MerkleTree,
pub wallet: WalletPtr,
pub unspent_money_coins: Vec<OwnCoin>,
pub spent_money_coins: Vec<OwnCoin>,
}
impl Wallet {
pub async fn new(keypair: Keypair, faucet_pubkeys: &[PublicKey]) -> Result<Self> {
let wallet = WalletDb::new("sqlite::memory:", "foo").await?;
let sled_db = sled::Config::new().temporary(true).open()?;
// Use pregenerated vks
vks::inject(&sled_db)?;
let state = ValidatorState::new(
&sled_db,
*TESTNET_BOOTSTRAP_TIMESTAMP,
*TESTNET_GENESIS_TIMESTAMP,
*TESTNET_GENESIS_HASH_BYTES,
*TESTNET_INITIAL_DISTRIBUTION,
wallet.clone(),
faucet_pubkeys.to_vec(),
false,
false,
)
.await?;
// Create necessary Merkle trees for tracking
let money_merkle_tree = MerkleTree::new(100);
let consensus_staked_merkle_tree = MerkleTree::new(100);
let consensus_unstaked_merkle_tree = MerkleTree::new(100);
let unspent_money_coins = vec![];
let spent_money_coins = vec![];
let token_mint_authority = Keypair::random(&mut OsRng);
Ok(Self {
keypair,
token_mint_authority,
state,
money_merkle_tree,
consensus_staked_merkle_tree,
consensus_unstaked_merkle_tree,
wallet,
unspent_money_coins,
spent_money_coins,
})
}
}
pub struct TestHarness {
pub holders: HashMap<Holder, Wallet>,
pub proving_keys: HashMap<&'static str, (ProvingKey, ZkBinary)>,
pub tx_action_benchmarks: HashMap<TxAction, TxActionBenchmarks>,
}
impl TestHarness {
pub async fn new(contracts: &[String]) -> Result<Self> {
let mut holders = HashMap::new();
let faucet_kp = Keypair::random(&mut OsRng);
let faucet_pubkeys = vec![faucet_kp.public];
let faucet = Wallet::new(faucet_kp, &faucet_pubkeys).await?;
holders.insert(Holder::Faucet, faucet);
let alice_kp = Keypair::random(&mut OsRng);
let alice = Wallet::new(alice_kp, &faucet_pubkeys).await?;
// Alice is inserted at end of function
let bob_kp = Keypair::random(&mut OsRng);
let bob = Wallet::new(bob_kp, &faucet_pubkeys).await?;
holders.insert(Holder::Bob, bob);
let charlie_kp = Keypair::random(&mut OsRng);
let charlie = Wallet::new(charlie_kp, &faucet_pubkeys).await?;
holders.insert(Holder::Charlie, charlie);
let rachel_kp = Keypair::random(&mut OsRng);
let rachel = Wallet::new(rachel_kp, &faucet_pubkeys).await?;
holders.insert(Holder::Rachel, rachel);
// Get the zkas circuits and build proving keys
let mut proving_keys = HashMap::new();
let alice_sled = alice.state.read().await.blockchain.sled_db.clone();
macro_rules! mkpk {
($db:expr, $ns:expr) => {
info!("Building ProvingKey for {}", $ns);
let zkas_bytes = $db.get(&serialize(&$ns))?.unwrap();
let (zkbin, _): (Vec<u8>, Vec<u8>) = deserialize(&zkas_bytes)?;
let zkbin = ZkBinary::decode(&zkbin)?;
let witnesses = empty_witnesses(&zkbin);
let circuit = ZkCircuit::new(witnesses, zkbin.clone());
let pk = ProvingKey::build(13, &circuit);
proving_keys.insert($ns, (pk, zkbin));
};
}
if contracts.contains(&"money".to_string()) {
let db_handle = alice.state.read().await.blockchain.contracts.lookup(
&alice_sled,
&MONEY_CONTRACT_ID,
SMART_CONTRACT_ZKAS_DB_NAME,
)?;
mkpk!(db_handle, MONEY_CONTRACT_ZKAS_MINT_NS_V1);
mkpk!(db_handle, MONEY_CONTRACT_ZKAS_BURN_NS_V1);
mkpk!(db_handle, MONEY_CONTRACT_ZKAS_TOKEN_MINT_NS_V1);
mkpk!(db_handle, MONEY_CONTRACT_ZKAS_TOKEN_FRZ_NS_V1);
}
if contracts.contains(&"consensus".to_string()) {
let db_handle = alice.state.read().await.blockchain.contracts.lookup(
&alice_sled,
&CONSENSUS_CONTRACT_ID,
SMART_CONTRACT_ZKAS_DB_NAME,
)?;
mkpk!(db_handle, CONSENSUS_CONTRACT_ZKAS_MINT_NS_V1);
mkpk!(db_handle, CONSENSUS_CONTRACT_ZKAS_BURN_NS_V1);
mkpk!(db_handle, CONSENSUS_CONTRACT_ZKAS_PROPOSAL_NS_V1);
}
if contracts.contains(&"dao".to_string()) {
let db_handle = alice.state.read().await.blockchain.contracts.lookup(
&alice_sled,
&DAO_CONTRACT_ID,
SMART_CONTRACT_ZKAS_DB_NAME,
)?;
mkpk!(db_handle, DAO_CONTRACT_ZKAS_DAO_EXEC_NS);
mkpk!(db_handle, DAO_CONTRACT_ZKAS_DAO_MINT_NS);
mkpk!(db_handle, DAO_CONTRACT_ZKAS_DAO_VOTE_BURN_NS);
mkpk!(db_handle, DAO_CONTRACT_ZKAS_DAO_VOTE_MAIN_NS);
mkpk!(db_handle, DAO_CONTRACT_ZKAS_DAO_PROPOSE_BURN_NS);
mkpk!(db_handle, DAO_CONTRACT_ZKAS_DAO_PROPOSE_MAIN_NS);
}
// Build benchmarks map
let mut tx_action_benchmarks = HashMap::new();
tx_action_benchmarks.insert(TxAction::MoneyAirdrop, TxActionBenchmarks::new());
tx_action_benchmarks.insert(TxAction::MoneyTokenMint, TxActionBenchmarks::new());
tx_action_benchmarks.insert(TxAction::MoneyTokenFreeze, TxActionBenchmarks::new());
tx_action_benchmarks.insert(TxAction::MoneyGenesisMint, TxActionBenchmarks::new());
tx_action_benchmarks.insert(TxAction::MoneyOtcSwap, TxActionBenchmarks::new());
tx_action_benchmarks.insert(TxAction::MoneyTransfer, TxActionBenchmarks::new());
// Alice jumps down the rabbit hole
holders.insert(Holder::Alice, alice);
Ok(Self { holders, proving_keys, tx_action_benchmarks })
}
pub fn statistics(&self) {
info!("==================== Statistics ====================");
for (action, tx_action_benchmark) in &self.tx_action_benchmarks {
tx_action_benchmark.statistics(action);
}
info!("====================================================");
}
pub fn gather_owncoin(
&mut self,
holder: Holder,
output: Output,
secret_key: Option<SecretKey>,
) -> Result<OwnCoin> {
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 = 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(), note.serial])),
leaf_position,
};
wallet.unspent_money_coins.push(oc.clone());
Ok(oc)
}
}

View File

@@ -0,0 +1,112 @@
/* This file is part of DarkFi (https://dark.fi)
*
* Copyright (C) 2020-2023 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 <https://www.gnu.org/licenses/>.
*/
use std::time::Instant;
use darkfi::{tx::Transaction, zk::halo2::Field, Result};
use darkfi_money_contract::{
client::transfer_v1::TransferCallBuilder, model::MoneyTransferParamsV1, MoneyFunction,
MONEY_CONTRACT_ZKAS_BURN_NS_V1, MONEY_CONTRACT_ZKAS_MINT_NS_V1,
};
use darkfi_sdk::{
crypto::{MerkleNode, DARK_TOKEN_ID, MONEY_CONTRACT_ID},
pasta::pallas,
ContractCall,
};
use darkfi_serial::{serialize, Encodable};
use rand::rngs::OsRng;
use super::{Holder, TestHarness, TxAction};
impl TestHarness {
pub fn airdrop_native(
&mut self,
value: u64,
holder: Holder,
) -> Result<(Transaction, MoneyTransferParamsV1)> {
let recipient = self.holders.get_mut(&holder).unwrap().keypair.public;
let faucet = self.holders.get_mut(&Holder::Faucet).unwrap();
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();
let tx_action_benchmark =
self.tx_action_benchmarks.get_mut(&TxAction::MoneyAirdrop).unwrap();
let timer = Instant::now();
let builder = TransferCallBuilder {
keypair: faucet.keypair,
recipient,
value,
token_id: *DARK_TOKEN_ID,
rcpt_spend_hook: pallas::Base::ZERO,
rcpt_user_data: pallas::Base::ZERO,
rcpt_user_data_blind: pallas::Base::random(&mut OsRng),
change_spend_hook: pallas::Base::ZERO,
change_user_data: pallas::Base::ZERO,
change_user_data_blind: pallas::Base::random(&mut OsRng),
coins: vec![],
tree: faucet.money_merkle_tree.clone(),
mint_zkbin: mint_zkbin.clone(),
mint_pk: mint_pk.clone(),
burn_zkbin: burn_zkbin.clone(),
burn_pk: burn_pk.clone(),
clear_input: true,
};
let debris = builder.build()?;
let mut data = vec![MoneyFunction::TransferV1 as u8];
debris.params.encode(&mut data)?;
let calls = vec![ContractCall { contract_id: *MONEY_CONTRACT_ID, data }];
let proofs = vec![debris.proofs];
let mut tx = Transaction { calls, proofs, signatures: vec![] };
let sigs = tx.create_sigs(&mut OsRng, &debris.signature_secrets)?;
tx.signatures = vec![sigs];
tx_action_benchmark.creation_times.push(timer.elapsed());
// Calculate transaction sizes
let encoded: Vec<u8> = 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))
}
pub async fn execute_airdrop_native_tx(
&mut self,
holder: Holder,
tx: &Transaction,
params: &MoneyTransferParamsV1,
slot: u64,
) -> Result<()> {
let wallet = self.holders.get_mut(&holder).unwrap();
let tx_action_benchmark =
self.tx_action_benchmarks.get_mut(&TxAction::MoneyAirdrop).unwrap();
let timer = Instant::now();
let erroneous_txs =
wallet.state.read().await.verify_transactions(&[tx.clone()], slot, true).await?;
assert!(erroneous_txs.is_empty());
wallet.money_merkle_tree.append(MerkleNode::from(params.outputs[0].coin.inner()));
tx_action_benchmark.verify_times.push(timer.elapsed());
Ok(())
}
}

View File

@@ -0,0 +1,163 @@
/* This file is part of DarkFi (https://dark.fi)
*
* Copyright (C) 2020-2023 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 <https://www.gnu.org/licenses/>.
*/
use std::time::Instant;
use darkfi::{tx::Transaction, zk::halo2::Field, Result};
use darkfi_money_contract::{
client::{token_freeze_v1::TokenFreezeCallBuilder, token_mint_v1::TokenMintCallBuilder},
model::{MoneyTokenFreezeParamsV1, MoneyTokenMintParamsV1},
MoneyFunction, MONEY_CONTRACT_ZKAS_TOKEN_FRZ_NS_V1, MONEY_CONTRACT_ZKAS_TOKEN_MINT_NS_V1,
};
use darkfi_sdk::{
crypto::{MerkleNode, MONEY_CONTRACT_ID},
pasta::pallas,
ContractCall,
};
use darkfi_serial::{serialize, Encodable};
use rand::rngs::OsRng;
use super::{Holder, TestHarness, TxAction};
impl TestHarness {
pub fn token_mint(
&mut self,
amount: u64,
holder: Holder,
recipient: Holder,
) -> Result<(Transaction, MoneyTokenMintParamsV1)> {
let rcpt = self.holders.get_mut(&recipient).unwrap().keypair.public;
let mint_authority = self.holders.get_mut(&holder).unwrap().token_mint_authority;
let (mint_pk, mint_zkbin) =
self.proving_keys.get(&MONEY_CONTRACT_ZKAS_TOKEN_MINT_NS_V1).unwrap();
let tx_action_benchmark =
self.tx_action_benchmarks.get_mut(&TxAction::MoneyTokenMint).unwrap();
let timer = Instant::now();
let builder = TokenMintCallBuilder {
mint_authority,
recipient: rcpt,
amount,
spend_hook: pallas::Base::ZERO,
user_data: pallas::Base::ZERO,
token_mint_zkbin: mint_zkbin.clone(),
token_mint_pk: mint_pk.clone(),
};
let debris = builder.build()?;
let mut data = vec![MoneyFunction::TokenMintV1 as u8];
debris.params.encode(&mut data)?;
let calls = vec![ContractCall { contract_id: *MONEY_CONTRACT_ID, data }];
let proofs = vec![debris.proofs];
let mut tx = Transaction { calls, proofs, signatures: vec![] };
let sigs = tx.create_sigs(&mut OsRng, &[mint_authority.secret])?;
tx.signatures = vec![sigs];
tx_action_benchmark.creation_times.push(timer.elapsed());
// Calculate transaction sizes
let encoded: Vec<u8> = 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))
}
pub async fn execute_token_mint_tx(
&mut self,
holder: Holder,
tx: &Transaction,
params: &MoneyTokenMintParamsV1,
slot: u64,
) -> 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();
let erroneous_txs =
wallet.state.read().await.verify_transactions(&[tx.clone()], slot, true).await?;
assert!(erroneous_txs.is_empty());
wallet.money_merkle_tree.append(MerkleNode::from(params.output.coin.inner()));
tx_action_benchmark.verify_times.push(timer.elapsed());
Ok(())
}
pub fn token_freeze(
&mut self,
holder: Holder,
) -> Result<(Transaction, MoneyTokenFreezeParamsV1)> {
let mint_authority = self.holders.get_mut(&holder).unwrap().token_mint_authority;
let (frz_pk, frz_zkbin) =
self.proving_keys.get(&MONEY_CONTRACT_ZKAS_TOKEN_FRZ_NS_V1).unwrap();
let tx_action_benchmark =
self.tx_action_benchmarks.get_mut(&TxAction::MoneyTokenFreeze).unwrap();
let timer = Instant::now();
let builder = TokenFreezeCallBuilder {
mint_authority,
token_freeze_zkbin: frz_zkbin.clone(),
token_freeze_pk: frz_pk.clone(),
};
let debris = builder.build()?;
let mut data = vec![MoneyFunction::TokenFreezeV1 as u8];
debris.params.encode(&mut data)?;
let calls = vec![ContractCall { contract_id: *MONEY_CONTRACT_ID, data }];
let proofs = vec![debris.proofs];
let mut tx = Transaction { calls, proofs, signatures: vec![] };
let sigs = tx.create_sigs(&mut OsRng, &[mint_authority.secret])?;
tx.signatures = vec![sigs];
tx_action_benchmark.creation_times.push(timer.elapsed());
// Calculate transaction sizes
let encoded: Vec<u8> = 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))
}
pub async fn execute_token_freeze_tx(
&mut self,
holder: Holder,
tx: &Transaction,
_params: &MoneyTokenFreezeParamsV1,
slot: u64,
) -> 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();
let erroneous_txs =
wallet.state.read().await.verify_transactions(&[tx.clone()], slot, true).await?;
assert!(erroneous_txs.is_empty());
tx_action_benchmark.verify_times.push(timer.elapsed());
Ok(())
}
}

View File

@@ -0,0 +1,195 @@
/* This file is part of DarkFi (https://dark.fi)
*
* Copyright (C) 2020-2023 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 <https://www.gnu.org/licenses/>.
*/
use std::{
fs::File,
io::{Read, Write},
path::PathBuf,
process::Command,
};
use darkfi::{
runtime::vm_runtime::SMART_CONTRACT_ZKAS_DB_NAME,
zk::{empty_witnesses, VerifyingKey, ZkCircuit},
zkas::ZkBinary,
Result,
};
use darkfi_dao_contract::{
DAO_CONTRACT_ZKAS_DAO_EXEC_NS, DAO_CONTRACT_ZKAS_DAO_MINT_NS,
DAO_CONTRACT_ZKAS_DAO_PROPOSE_BURN_NS, DAO_CONTRACT_ZKAS_DAO_PROPOSE_MAIN_NS,
DAO_CONTRACT_ZKAS_DAO_VOTE_BURN_NS, DAO_CONTRACT_ZKAS_DAO_VOTE_MAIN_NS,
};
use darkfi_deployooor_contract::DEPLOY_CONTRACT_ZKAS_DERIVE_NS_V1;
use darkfi_money_contract::{
CONSENSUS_CONTRACT_ZKAS_BURN_NS_V1, CONSENSUS_CONTRACT_ZKAS_MINT_NS_V1,
CONSENSUS_CONTRACT_ZKAS_PROPOSAL_NS_V1, MONEY_CONTRACT_ZKAS_BURN_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, CONSENSUS_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 = "0a745d4055440b52a9e669264a8cbf51a726d1ca290a4a3c6dc4cad3e711a1f3";
fn vks_path() -> Result<PathBuf> {
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");
path.push("contract");
path.push("test-harness");
path.push("vks.bin");
Ok(path)
}
/// (Bincode, Namespace, VK)
pub type Vks = Vec<(Vec<u8>, String, Vec<u8>)>;
fn read_or_gen_vks() -> Result<Vks> {
let vks_path = vks_path()?;
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 hash: {}", known_hash);
debug!("Found hash: {}", found_hash);
if known_hash == found_hash {
return Ok(deserialize(&data)?)
}
drop(f);
}
let bins = vec![
// Money
&include_bytes!("../../money/proof/mint_v1.zk.bin")[..],
&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")[..],
// DAO
&include_bytes!("../../dao/proof/dao-mint.zk.bin")[..],
&include_bytes!("../../dao/proof/dao-exec.zk.bin")[..],
&include_bytes!("../../dao/proof/dao-propose-burn.zk.bin")[..],
&include_bytes!("../../dao/proof/dao-propose-main.zk.bin")[..],
&include_bytes!("../../dao/proof/dao-vote-burn.zk.bin")[..],
&include_bytes!("../../dao/proof/dao-vote-main.zk.bin")[..],
// Consensus
&include_bytes!("../../consensus/proof/consensus_burn_v1.zk.bin")[..],
&include_bytes!("../../consensus/proof/consensus_mint_v1.zk.bin")[..],
&include_bytes!("../../consensus/proof/consensus_proposal_v1.zk.bin")[..],
// Deployooor
&include_bytes!("../../deployooor/proof/derive_contract_id.zk.bin")[..],
];
let mut vks = vec![];
for bincode in bins.iter() {
let zkbin = ZkBinary::decode(&bincode)?;
debug!("Building VK for {}", zkbin.namespace);
let witnesses = empty_witnesses(&zkbin);
let circuit = ZkCircuit::new(witnesses, zkbin.clone());
let vk = VerifyingKey::build(13, &circuit);
let mut vk_buf = vec![];
vk.write(&mut vk_buf)?;
vks.push((bincode.to_vec(), zkbin.namespace, 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)?;
Ok(vks)
}
pub(crate) fn inject(sled_db: &sled::Db) -> Result<()> {
// Use pregenerated vks
let vks = read_or_gen_vks()?;
// Inject them 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)?;
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 consensus_zkas_tree_ptr = CONSENSUS_CONTRACT_ID.hash_state_id(SMART_CONTRACT_ZKAS_DB_NAME);
let consensus_zkas_tree = sled_db.open_tree(consensus_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)?;
for (bincode, namespace, vk) in vks.iter() {
match namespace.as_str() {
// Money circuits
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 => {
let key = serialize(&namespace.as_str());
let value = serialize(&(bincode.clone(), vk.clone()));
money_zkas_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_ZKAS_DAO_MINT_NS |
DAO_CONTRACT_ZKAS_DAO_EXEC_NS |
DAO_CONTRACT_ZKAS_DAO_VOTE_BURN_NS |
DAO_CONTRACT_ZKAS_DAO_VOTE_MAIN_NS |
DAO_CONTRACT_ZKAS_DAO_PROPOSE_BURN_NS |
DAO_CONTRACT_ZKAS_DAO_PROPOSE_MAIN_NS => {
let key = serialize(&namespace.as_str());
let value = serialize(&(bincode.clone(), vk.clone()));
dao_zkas_tree.insert(key, value)?;
}
// Consensus circuits
CONSENSUS_CONTRACT_ZKAS_MINT_NS_V1 |
CONSENSUS_CONTRACT_ZKAS_BURN_NS_V1 |
CONSENSUS_CONTRACT_ZKAS_PROPOSAL_NS_V1 => {
let key = serialize(&namespace.as_str());
let value = serialize(&(bincode.clone(), vk.clone()));
consensus_zkas_tree.insert(key, value)?;
}
x => panic!("Found unhandled zkas namespace {}", x),
}
}
Ok(())
}