darkfi_contract_test_harness/
lib.rsuse std::{
collections::HashMap,
io::{Cursor, Write},
};
use darkfi::{
blockchain::{BlockInfo, BlockchainOverlay},
runtime::vm_runtime::Runtime,
tx::Transaction,
util::{pcg::Pcg32, time::Timestamp},
validator::{Validator, ValidatorConfig, ValidatorPtr},
zk::{empty_witnesses, halo2::Field, ProvingKey, ZkCircuit},
zkas::ZkBinary,
Result,
};
use darkfi_dao_contract::model::{DaoBulla, DaoProposalBulla};
use darkfi_money_contract::client::OwnCoin;
use darkfi_sdk::{
bridgetree,
crypto::{
smt::{MemoryStorageFp, PoseidonFp, SmtMemoryFp, EMPTY_NODES_FP},
Keypair, MerkleNode, MerkleTree,
},
pasta::pallas,
};
use darkfi_serial::Encodable;
use log::debug;
use num_bigint::BigUint;
use sled_overlay::sled;
pub mod vks;
mod money_pow_reward;
mod money_fee;
mod money_genesis_mint;
mod money_transfer;
mod money_token;
mod money_otc_swap;
mod contract_deploy;
mod dao_mint;
mod dao_propose;
mod dao_vote;
mod dao_exec;
pub fn init_logger() {
let mut cfg = simplelog::ConfigBuilder::new();
cfg.add_filter_ignore("sled".to_string());
if simplelog::TermLogger::init(
simplelog::LevelFilter::Info,
cfg.build(),
simplelog::TerminalMode::Mixed,
simplelog::ColorChoice::Auto,
)
.is_err()
{
debug!(target: "test_harness", "Logger initialized");
}
}
#[derive(Clone, Copy, Eq, PartialEq, Hash, Debug)]
pub enum Holder {
Alice,
Bob,
Charlie,
Dao,
Rachel,
}
pub struct Wallet {
pub keypair: Keypair,
pub token_mint_authority: Keypair,
pub contract_deploy_authority: Keypair,
pub validator: ValidatorPtr,
pub money_merkle_tree: MerkleTree,
pub money_null_smt: SmtMemoryFp,
pub money_null_smt_snapshot: Option<SmtMemoryFp>,
pub dao_merkle_tree: MerkleTree,
pub dao_proposals_tree: MerkleTree,
pub unspent_money_coins: Vec<OwnCoin>,
pub spent_money_coins: Vec<OwnCoin>,
pub dao_leafs: HashMap<DaoBulla, bridgetree::Position>,
pub dao_prop_leafs: HashMap<DaoProposalBulla, (bridgetree::Position, MerkleTree)>,
pub bench_wasm: bool,
}
impl Wallet {
pub async fn new(
keypair: Keypair,
token_mint_authority: Keypair,
contract_deploy_authority: Keypair,
genesis_block: BlockInfo,
vks: &vks::Vks,
verify_fees: bool,
) -> Result<Self> {
let sled_db = sled::Config::new().temporary(true).open()?;
vks::inject(&sled_db, vks)?;
let validator_config = ValidatorConfig {
confirmation_threshold: 3,
pow_target: 90,
pow_fixed_difficulty: Some(BigUint::from(1_u8)),
genesis_block,
verify_fees,
};
let validator = Validator::new(&sled_db, &validator_config).await?;
let mut money_merkle_tree = MerkleTree::new(1);
money_merkle_tree.append(MerkleNode::from(pallas::Base::ZERO));
money_merkle_tree.mark().unwrap();
let hasher = PoseidonFp::new();
let store = MemoryStorageFp::new();
let money_null_smt = SmtMemoryFp::new(store, hasher, &EMPTY_NODES_FP);
Ok(Self {
keypair,
token_mint_authority,
contract_deploy_authority,
validator,
money_merkle_tree,
money_null_smt,
money_null_smt_snapshot: None,
dao_merkle_tree: MerkleTree::new(1),
dao_proposals_tree: MerkleTree::new(1),
unspent_money_coins: vec![],
spent_money_coins: vec![],
dao_leafs: HashMap::new(),
dao_prop_leafs: HashMap::new(),
bench_wasm: false,
})
}
pub async fn add_transaction(
&mut self,
callname: &str,
tx: Transaction,
block_height: u32,
) -> Result<()> {
if self.bench_wasm {
benchmark_wasm_calls(callname, &self.validator, &tx, block_height).await;
}
self.validator
.add_test_transactions(
&[tx.clone()],
block_height,
self.validator.consensus.module.read().await.target,
true,
self.validator.verify_fees,
)
.await?;
{
let blockchain = &self.validator.blockchain;
let txs = &blockchain.transactions;
txs.insert(&[tx.clone()]).expect("insert tx");
txs.insert_location(&[tx.hash()], block_height).expect("insert loc");
}
Ok(())
}
}
pub struct TestHarness {
pub holders: HashMap<Holder, Wallet>,
pub proving_keys: HashMap<String, (ProvingKey, ZkBinary)>,
pub genesis_block: BlockInfo,
pub verify_fees: bool,
}
impl TestHarness {
pub async fn new(holders: &[Holder], verify_fees: bool) -> Result<Self> {
let mut genesis_block = BlockInfo::default();
genesis_block.header.timestamp = Timestamp::from_u64(1689772567);
let producer_tx = genesis_block.txs.pop().unwrap();
genesis_block.append_txs(vec![producer_tx]);
let mut rng = Pcg32::new(42);
let (pks, vks) = vks::get_cached_pks_and_vks()?;
let mut proving_keys = HashMap::new();
for (bincode, namespace, pk) in pks {
let mut reader = Cursor::new(pk);
let zkbin = ZkBinary::decode(&bincode)?;
let circuit = ZkCircuit::new(empty_witnesses(&zkbin)?, &zkbin);
let proving_key = ProvingKey::read(&mut reader, circuit)?;
proving_keys.insert(namespace, (proving_key, zkbin));
}
let mut holders_map = HashMap::new();
for holder in holders {
let keypair = Keypair::random(&mut rng);
let token_mint_authority = Keypair::random(&mut rng);
let contract_deploy_authority = Keypair::random(&mut rng);
let wallet = Wallet::new(
keypair,
token_mint_authority,
contract_deploy_authority,
genesis_block.clone(),
&vks,
verify_fees,
)
.await?;
holders_map.insert(*holder, wallet);
}
Ok(Self { holders: holders_map, proving_keys, genesis_block, verify_fees })
}
pub fn assert_trees(&self, holders: &[Holder]) {
assert!(holders.len() > 1);
let mut wallets = vec![];
for holder in holders {
wallets.push(self.holders.get(holder).unwrap());
}
let wallet = wallets[0];
let money_root = wallet.money_merkle_tree.root(0).unwrap();
for wallet in &wallets[1..] {
assert!(money_root == wallet.money_merkle_tree.root(0).unwrap());
}
}
}
async fn benchmark_wasm_calls(
callname: &str,
validator: &Validator,
tx: &Transaction,
block_height: u32,
) {
let mut file = std::fs::OpenOptions::new().create(true).append(true).open("bench.csv").unwrap();
for (idx, call) in tx.calls.iter().enumerate() {
let overlay = BlockchainOverlay::new(&validator.blockchain).expect("blockchain overlay");
let wasm = overlay.lock().unwrap().contracts.get(call.data.contract_id).unwrap();
let mut runtime = Runtime::new(
&wasm,
overlay.clone(),
call.data.contract_id,
block_height,
validator.consensus.module.read().await.target,
tx.hash(),
idx as u8,
)
.expect("runtime");
let mut payload = vec![];
tx.calls.encode(&mut payload).unwrap();
let mut times = [0; 3];
let now = std::time::Instant::now();
let _metadata = runtime.metadata(&payload).expect("metadata");
times[0] = now.elapsed().as_micros();
let now = std::time::Instant::now();
let update = runtime.exec(&payload).expect("exec");
times[1] = now.elapsed().as_micros();
let now = std::time::Instant::now();
runtime.apply(&update).expect("update");
times[2] = now.elapsed().as_micros();
writeln!(
file,
"{}, {}, {}, {}, {}, {}",
callname,
tx.hash(),
idx,
times[0],
times[1],
times[2]
)
.unwrap();
}
}