mirror of
https://github.com/darkrenaissance/darkfi.git
synced 2026-01-10 07:08:05 -05:00
contract: WIP implementation of generic test harness for native contracts.
This commit is contained in:
20
Cargo.lock
generated
20
Cargo.lock
generated
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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
1
src/contract/test-harness/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
vks.bin
|
||||
22
src/contract/test-harness/Cargo.toml
Normal file
22
src/contract/test-harness/Cargo.toml
Normal 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"
|
||||
68
src/contract/test-harness/src/benchmarks.rs
Normal file
68
src/contract/test-harness/src/benchmarks.rs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
288
src/contract/test-harness/src/lib.rs
Normal file
288
src/contract/test-harness/src/lib.rs
Normal 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)
|
||||
}
|
||||
}
|
||||
112
src/contract/test-harness/src/money_airdrop.rs
Normal file
112
src/contract/test-harness/src/money_airdrop.rs
Normal 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(())
|
||||
}
|
||||
}
|
||||
163
src/contract/test-harness/src/money_token.rs
Normal file
163
src/contract/test-harness/src/money_token.rs
Normal 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(())
|
||||
}
|
||||
}
|
||||
195
src/contract/test-harness/src/vks.rs
Normal file
195
src/contract/test-harness/src/vks.rs
Normal 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(())
|
||||
}
|
||||
Reference in New Issue
Block a user