Jelajahi Sumber

contract/consensus: migrated tests to new contract/test-harness

aggstam 3 tahun lalu
induk
melakukan
410ab42b54

+ 1 - 0
Cargo.lock

@@ -1343,6 +1343,7 @@ dependencies = [
  "bs58",
  "chacha20poly1305",
  "darkfi",
+ "darkfi-contract-test-harness",
  "darkfi-money-contract",
  "darkfi-sdk",
  "darkfi-serial",

+ 1 - 0
src/contract/consensus/Cargo.toml

@@ -29,6 +29,7 @@ async-std = {version = "1.12.0", features = ["attributes"]}
 bs58 = "0.5.0"
 darkfi = {path = "../../../", features = ["tx", "blockchain"]}
 darkfi-money-contract = { path = "../money", features = ["client", "no-entrypoint"] }
+darkfi-contract-test-harness = {path = "../test-harness"}
 simplelog = "0.12.1"
 sled = "0.34.7"
 

+ 17 - 18
src/contract/consensus/tests/genesis_stake_unstake.rs

@@ -28,14 +28,15 @@ use darkfi::Result;
 use log::info;
 
 use darkfi_consensus_contract::model::{calculate_grace_period, EPOCH_LENGTH, REWARD};
-
-mod harness;
-use harness::{init_logger, ConsensusTestHarness, Holder};
+use darkfi_contract_test_harness::{init_logger, Holder, TestHarness};
 
 #[async_std::test]
 async fn consensus_contract_genesis_stake_unstake() -> Result<()> {
     init_logger();
 
+    // Holders this test will use
+    const HOLDERS: [Holder; 2] = [Holder::Faucet, Holder::Alice];
+
     // Some numbers we want to assert
     const ALICE_INITIAL: u64 = 1000;
 
@@ -43,7 +44,7 @@ async fn consensus_contract_genesis_stake_unstake() -> Result<()> {
     let mut current_slot = 0;
 
     // Initialize harness
-    let mut th = ConsensusTestHarness::new().await?;
+    let mut th = TestHarness::new(&["money".to_string(), "consensus".to_string()]).await?;
 
     // Now Alice can craate a genesis stake transaction to mint
     // some staked coins
@@ -85,7 +86,7 @@ async fn consensus_contract_genesis_stake_unstake() -> Result<()> {
     info!(target: "consensus", "[Faucet] ================================");
     th.execute_genesis_stake_tx(
         Holder::Faucet,
-        genesis_stake_tx.clone(),
+        &genesis_stake_tx,
         &genesis_stake_params,
         current_slot,
     )
@@ -96,13 +97,13 @@ async fn consensus_contract_genesis_stake_unstake() -> Result<()> {
     info!(target: "consensus", "[Alice] ================================");
     th.execute_genesis_stake_tx(
         Holder::Alice,
-        genesis_stake_tx,
+        &genesis_stake_tx,
         &genesis_stake_params,
         current_slot,
     )
     .await?;
 
-    th.assert_trees();
+    th.assert_trees(&HOLDERS);
 
     // Gather new staked owncoin
     let alice_staked_oc =
@@ -128,15 +129,14 @@ async fn consensus_contract_genesis_stake_unstake() -> Result<()> {
     info!(target: "consensus", "[Faucet] ===========================");
     info!(target: "consensus", "[Faucet] Executing Alice proposal tx");
     info!(target: "consensus", "[Faucet] ===========================");
-    th.execute_proposal_tx(Holder::Faucet, proposal_tx.clone(), &proposal_params, current_slot)
-        .await?;
+    th.execute_proposal_tx(Holder::Faucet, &proposal_tx, &proposal_params, current_slot).await?;
 
     info!(target: "consensus", "[Alice] ===========================");
     info!(target: "consensus", "[Alice] Executing Alice proposal tx");
     info!(target: "consensus", "[Alice] ===========================");
-    th.execute_proposal_tx(Holder::Alice, proposal_tx, &proposal_params, current_slot).await?;
+    th.execute_proposal_tx(Holder::Alice, &proposal_tx, &proposal_params, current_slot).await?;
 
-    th.assert_trees();
+    th.assert_trees(&HOLDERS);
 
     // Gather new staked owncoin which includes the reward
     let alice_rewarded_staked_oc = th.gather_consensus_staked_owncoin(
@@ -168,7 +168,7 @@ async fn consensus_contract_genesis_stake_unstake() -> Result<()> {
     info!(target: "consensus", "[Faucet] ==================================");
     th.execute_unstake_request_tx(
         Holder::Faucet,
-        unstake_request_tx.clone(),
+        &unstake_request_tx,
         &unstake_request_params,
         current_slot,
     )
@@ -179,13 +179,13 @@ async fn consensus_contract_genesis_stake_unstake() -> Result<()> {
     info!(target: "consensus", "[Alice] ==================================");
     th.execute_unstake_request_tx(
         Holder::Alice,
-        unstake_request_tx,
+        &unstake_request_tx,
         &unstake_request_params,
         current_slot,
     )
     .await?;
 
-    th.assert_trees();
+    th.assert_trees(&HOLDERS);
 
     // Gather new unstake request owncoin
     let alice_unstake_request_oc = th.gather_consensus_unstaked_owncoin(
@@ -210,15 +210,14 @@ async fn consensus_contract_genesis_stake_unstake() -> Result<()> {
     info!(target: "consensus", "[Faucet] ==========================");
     info!(target: "consensus", "[Faucet] Executing Alice unstake tx");
     info!(target: "consensus", "[Faucet] ==========================");
-    th.execute_unstake_tx(Holder::Faucet, unstake_tx.clone(), &unstake_params, current_slot)
-        .await?;
+    th.execute_unstake_tx(Holder::Faucet, &unstake_tx, &unstake_params, current_slot).await?;
 
     info!(target: "consensus", "[Alice] ==========================");
     info!(target: "consensus", "[Alice] Executing Alice unstake tx");
     info!(target: "consensus", "[Alice] ==========================");
-    th.execute_unstake_tx(Holder::Alice, unstake_tx, &unstake_params, current_slot).await?;
+    th.execute_unstake_tx(Holder::Alice, &unstake_tx, &unstake_params, current_slot).await?;
 
-    th.assert_trees();
+    th.assert_trees(&HOLDERS);
 
     // Gather new unstaked owncoin
     let alice_unstaked_oc = th.gather_owncoin(Holder::Alice, unstake_params.output, None)?;

+ 0 - 954
src/contract/consensus/tests/harness.rs

@@ -1,954 +0,0 @@
-/* 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,
-    time::{Duration, Instant},
-};
-
-use darkfi::{
-    consensus::{
-        SlotCheckpoint, ValidatorState, ValidatorStatePtr, TESTNET_BOOTSTRAP_TIMESTAMP,
-        TESTNET_GENESIS_HASH_BYTES, TESTNET_GENESIS_TIMESTAMP, TESTNET_INITIAL_DISTRIBUTION,
-    },
-    runtime::vm_runtime::SMART_CONTRACT_ZKAS_DB_NAME,
-    tx::Transaction,
-    wallet::{WalletDb, WalletPtr},
-    zk::{empty_witnesses, halo2::Field, ProvingKey, ZkCircuit},
-    zkas::ZkBinary,
-    Result,
-};
-use darkfi_sdk::{
-    crypto::{
-        poseidon_hash, Keypair, MerkleNode, MerkleTree, Nullifier, PublicKey, SecretKey,
-        CONSENSUS_CONTRACT_ID, DARK_TOKEN_ID, MONEY_CONTRACT_ID,
-    },
-    pasta::pallas,
-    ContractCall,
-};
-use darkfi_serial::{deserialize, serialize, Encodable};
-use log::{info, warn};
-use rand::rngs::OsRng;
-
-use darkfi_consensus_contract::{
-    client::{
-        genesis_stake_v1::ConsensusGenesisStakeCallBuilder,
-        proposal_v1::ConsensusProposalCallBuilder, stake_v1::ConsensusStakeCallBuilder,
-        unstake_request_v1::ConsensusUnstakeRequestCallBuilder,
-        unstake_v1::ConsensusUnstakeCallBuilder,
-    },
-    model::{ConsensusGenesisStakeParamsV1, ConsensusProposalParamsV1},
-    ConsensusFunction,
-};
-use darkfi_money_contract::{
-    client::{
-        stake_v1::MoneyStakeCallBuilder, transfer_v1::TransferCallBuilder,
-        unstake_v1::MoneyUnstakeCallBuilder, ConsensusNote, ConsensusOwnCoin, MoneyNote, OwnCoin,
-    },
-    model::{
-        Coin, ConsensusOutput, ConsensusStakeParamsV1, ConsensusUnstakeReqParamsV1,
-        MoneyTransferParamsV1, MoneyUnstakeParamsV1, Output,
-    },
-    MoneyFunction, 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,
-};
-
-pub fn init_logger() {
-    let mut cfg = simplelog::ConfigBuilder::new();
-    cfg.add_filter_ignore("sled".to_string());
-    cfg.add_filter_ignore("blockchain::contractstore".to_string());
-    // We check this error so we can execute same file tests in parallel,
-    // otherwise second one fails to init logger here.
-    if let Err(_) = simplelog::TermLogger::init(
-        //simplelog::LevelFilter::Info,
-        simplelog::LevelFilter::Debug,
-        //simplelog::LevelFilter::Trace,
-        cfg.build(),
-        simplelog::TerminalMode::Mixed,
-        simplelog::ColorChoice::Auto,
-    ) {
-        warn!(target: "money_harness", "Logger already initialized");
-    }
-}
-
-/// Enum representing configured wallet holders
-#[derive(Debug, Eq, Hash, PartialEq)]
-pub enum Holder {
-    Faucet,
-    Alice,
-}
-
-/// Enum representing transaction actions
-#[derive(Debug, Eq, Hash, PartialEq)]
-pub enum TxAction {
-    Airdrop,
-    GenesisStake,
-    Stake,
-    Proposal,
-    UnstakeRequest,
-    Unstake,
-}
-
-/// 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!(target: "consensus", "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!(target: "consensus", "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!(target: "consensus", "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!(target: "consensus", "Average {:?} verification time: {:?}", action, avg);
-        }
-    }
-}
-
-pub struct Wallet {
-    pub keypair: 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 coins: Vec<OwnCoin>,
-    pub spent_coins: Vec<OwnCoin>,
-}
-
-impl Wallet {
-    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()?;
-
-        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?;
-
-        let money_merkle_tree = MerkleTree::new(100);
-        let consensus_staked_merkle_tree = MerkleTree::new(100);
-        let consensus_unstaked_merkle_tree = MerkleTree::new(100);
-
-        let coins = vec![];
-        let spent_coins = vec![];
-
-        Ok(Self {
-            keypair,
-            state,
-            money_merkle_tree,
-            consensus_staked_merkle_tree,
-            consensus_unstaked_merkle_tree,
-            wallet,
-            coins,
-            spent_coins,
-        })
-    }
-}
-
-pub struct ConsensusTestHarness {
-    pub holders: HashMap<Holder, Wallet>,
-    pub proving_keys: HashMap<&'static str, (ProvingKey, ZkBinary)>,
-    pub tx_action_benchmarks: HashMap<TxAction, TxActionBenchmarks>,
-}
-
-impl ConsensusTestHarness {
-    pub async fn new() -> 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?;
-
-        // 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();
-        let mut db_handle = alice.state.read().await.blockchain.contracts.lookup(
-            &alice_sled,
-            &MONEY_CONTRACT_ID,
-            SMART_CONTRACT_ZKAS_DB_NAME,
-        )?;
-
-        macro_rules! mkpk {
-            ($ns:expr) => {
-                let zkas_bytes = db_handle.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));
-            };
-        }
-
-        mkpk!(MONEY_CONTRACT_ZKAS_MINT_NS_V1);
-        mkpk!(MONEY_CONTRACT_ZKAS_BURN_NS_V1);
-
-        db_handle = alice.state.read().await.blockchain.contracts.lookup(
-            &alice_sled,
-            &CONSENSUS_CONTRACT_ID,
-            SMART_CONTRACT_ZKAS_DB_NAME,
-        )?;
-        mkpk!(CONSENSUS_CONTRACT_ZKAS_MINT_NS_V1);
-        mkpk!(CONSENSUS_CONTRACT_ZKAS_BURN_NS_V1);
-        mkpk!(CONSENSUS_CONTRACT_ZKAS_PROPOSAL_NS_V1);
-
-        holders.insert(Holder::Alice, alice);
-
-        // Build benchmarks map
-        let mut tx_action_benchmarks = HashMap::new();
-        tx_action_benchmarks.insert(TxAction::Airdrop, TxActionBenchmarks::new());
-        tx_action_benchmarks.insert(TxAction::GenesisStake, TxActionBenchmarks::new());
-        tx_action_benchmarks.insert(TxAction::Stake, TxActionBenchmarks::new());
-        tx_action_benchmarks.insert(TxAction::Proposal, TxActionBenchmarks::new());
-        tx_action_benchmarks.insert(TxAction::UnstakeRequest, TxActionBenchmarks::new());
-        tx_action_benchmarks.insert(TxAction::Unstake, TxActionBenchmarks::new());
-
-        Ok(Self { holders, proving_keys, tx_action_benchmarks })
-    }
-
-    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::Airdrop).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::Airdrop).unwrap();
-        let timer = Instant::now();
-
-        let erroneous_txs =
-            wallet.state.read().await.verify_transactions(&[tx], 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(())
-    }
-
-    pub fn genesis_stake(
-        &mut self,
-        holder: Holder,
-        amount: u64,
-    ) -> Result<(Transaction, ConsensusGenesisStakeParamsV1)> {
-        let wallet = self.holders.get_mut(&holder).unwrap();
-        let (mint_pk, mint_zkbin) =
-            self.proving_keys.get(&CONSENSUS_CONTRACT_ZKAS_MINT_NS_V1).unwrap();
-        let tx_action_benchmark =
-            self.tx_action_benchmarks.get_mut(&TxAction::GenesisStake).unwrap();
-        let timer = Instant::now();
-
-        // Building Consensus::GenesisStake params
-        let genesis_stake_call_debris = ConsensusGenesisStakeCallBuilder {
-            keypair: wallet.keypair,
-            recipient: wallet.keypair.public,
-            amount,
-            mint_zkbin: mint_zkbin.clone(),
-            mint_pk: mint_pk.clone(),
-        }
-        .build()?;
-        let (genesis_stake_params, genesis_stake_proofs) =
-            (genesis_stake_call_debris.params, genesis_stake_call_debris.proofs);
-
-        // Building genesis stake tx
-        let mut data = vec![ConsensusFunction::GenesisStakeV1 as u8];
-        genesis_stake_params.encode(&mut data)?;
-        let contract_call = ContractCall { contract_id: *CONSENSUS_CONTRACT_ID, data };
-        let calls = vec![contract_call];
-        let proofs = vec![genesis_stake_proofs];
-        let mut genesis_stake_tx = Transaction { calls, proofs, signatures: vec![] };
-        let sigs = genesis_stake_tx.create_sigs(&mut OsRng, &[wallet.keypair.secret])?;
-        genesis_stake_tx.signatures = vec![sigs];
-        tx_action_benchmark.creation_times.push(timer.elapsed());
-
-        // Calculate transaction sizes
-        let encoded: Vec<u8> = serialize(&genesis_stake_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((genesis_stake_tx, genesis_stake_params))
-    }
-
-    pub async fn execute_genesis_stake_tx(
-        &mut self,
-        holder: Holder,
-        tx: Transaction,
-        params: &ConsensusGenesisStakeParamsV1,
-        slot: u64,
-    ) -> Result<()> {
-        let wallet = self.holders.get_mut(&holder).unwrap();
-        let tx_action_benchmark =
-            self.tx_action_benchmarks.get_mut(&TxAction::GenesisStake).unwrap();
-        let timer = Instant::now();
-
-        let erroneous_txs =
-            wallet.state.read().await.verify_transactions(&[tx], slot, true).await?;
-        assert!(erroneous_txs.is_empty());
-        wallet.consensus_staked_merkle_tree.append(MerkleNode::from(params.output.coin.inner()));
-        tx_action_benchmark.verify_times.push(timer.elapsed());
-
-        Ok(())
-    }
-
-    pub async fn execute_erroneous_genesis_stake_txs(
-        &mut self,
-        holder: Holder,
-        txs: Vec<Transaction>,
-        slot: u64,
-        erroneous: usize,
-    ) -> Result<()> {
-        let wallet = self.holders.get_mut(&holder).unwrap();
-        let tx_action_benchmark =
-            self.tx_action_benchmarks.get_mut(&TxAction::GenesisStake).unwrap();
-        let timer = Instant::now();
-
-        let erroneous_txs =
-            wallet.state.read().await.verify_transactions(&txs, slot, false).await?;
-        assert_eq!(erroneous_txs.len(), erroneous);
-        tx_action_benchmark.verify_times.push(timer.elapsed());
-
-        Ok(())
-    }
-
-    pub async fn stake(
-        &mut self,
-        holder: Holder,
-        slot: u64,
-        owncoin: OwnCoin,
-    ) -> Result<(Transaction, ConsensusStakeParamsV1, SecretKey)> {
-        let wallet = self.holders.get_mut(&holder).unwrap();
-        let (mint_pk, mint_zkbin) =
-            self.proving_keys.get(&CONSENSUS_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::Stake).unwrap();
-        let epoch = wallet.state.read().await.consensus.time_keeper.slot_epoch(slot);
-        let timer = Instant::now();
-
-        // Building Money::Stake params
-        let money_stake_call_debris = MoneyStakeCallBuilder {
-            coin: owncoin.clone(),
-            tree: wallet.money_merkle_tree.clone(),
-            burn_zkbin: burn_zkbin.clone(),
-            burn_pk: burn_pk.clone(),
-        }
-        .build()?;
-
-        let (
-            money_stake_params,
-            money_stake_proofs,
-            money_stake_secret_key,
-            money_stake_value_blind,
-        ) = (
-            money_stake_call_debris.params,
-            money_stake_call_debris.proofs,
-            money_stake_call_debris.signature_secret,
-            money_stake_call_debris.value_blind,
-        );
-
-        // Building Consensus::Stake params
-        let consensus_stake_call_debris = ConsensusStakeCallBuilder {
-            coin: owncoin,
-            epoch,
-            value_blind: money_stake_value_blind,
-            money_input: money_stake_params.input.clone(),
-            mint_zkbin: mint_zkbin.clone(),
-            mint_pk: mint_pk.clone(),
-        }
-        .build()?;
-
-        let (consensus_stake_params, consensus_stake_proofs, consensus_stake_secret_key) = (
-            consensus_stake_call_debris.params,
-            consensus_stake_call_debris.proofs,
-            consensus_stake_call_debris.signature_secret,
-        );
-
-        // Building stake tx
-        let mut data = vec![MoneyFunction::StakeV1 as u8];
-        money_stake_params.encode(&mut data)?;
-        let money_call = ContractCall { contract_id: *MONEY_CONTRACT_ID, data };
-
-        let mut data = vec![ConsensusFunction::StakeV1 as u8];
-        consensus_stake_params.encode(&mut data)?;
-        let consensus_call = ContractCall { contract_id: *CONSENSUS_CONTRACT_ID, data };
-
-        let calls = vec![money_call, consensus_call];
-        let proofs = vec![money_stake_proofs, consensus_stake_proofs];
-        let mut stake_tx = Transaction { calls, proofs, signatures: vec![] };
-        let money_sigs = stake_tx.create_sigs(&mut OsRng, &[money_stake_secret_key])?;
-        let consensus_sigs = stake_tx.create_sigs(&mut OsRng, &[consensus_stake_secret_key])?;
-        stake_tx.signatures = vec![money_sigs, consensus_sigs];
-        tx_action_benchmark.creation_times.push(timer.elapsed());
-
-        // Calculate transaction sizes
-        let encoded: Vec<u8> = serialize(&stake_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((stake_tx, consensus_stake_params, consensus_stake_secret_key))
-    }
-
-    pub async fn execute_stake_tx(
-        &mut self,
-        holder: Holder,
-        tx: Transaction,
-        params: &ConsensusStakeParamsV1,
-        slot: u64,
-    ) -> Result<()> {
-        let wallet = self.holders.get_mut(&holder).unwrap();
-        let tx_action_benchmark = self.tx_action_benchmarks.get_mut(&TxAction::Stake).unwrap();
-        let timer = Instant::now();
-
-        let erroneous_txs =
-            wallet.state.read().await.verify_transactions(&[tx], slot, true).await?;
-        assert!(erroneous_txs.is_empty());
-        wallet.consensus_staked_merkle_tree.append(MerkleNode::from(params.output.coin.inner()));
-        tx_action_benchmark.verify_times.push(timer.elapsed());
-
-        Ok(())
-    }
-
-    pub async fn proposal(
-        &mut self,
-        holder: Holder,
-        slot_checkpoint: SlotCheckpoint,
-        staked_oc: ConsensusOwnCoin,
-    ) -> Result<(Transaction, ConsensusProposalParamsV1, SecretKey, SecretKey)> {
-        let wallet = self.holders.get_mut(&holder).unwrap();
-        let (proposal_pk, proposal_zkbin) =
-            self.proving_keys.get(&CONSENSUS_CONTRACT_ZKAS_PROPOSAL_NS_V1).unwrap();
-        let tx_action_benchmark = self.tx_action_benchmarks.get_mut(&TxAction::Proposal).unwrap();
-        let timer = Instant::now();
-
-        // Proposals always extend genesis block
-        let fork_hash = wallet.state.read().await.consensus.genesis_block;
-
-        // Building Consensus::Propose params
-        let proposal_call_debris = ConsensusProposalCallBuilder {
-            owncoin: staked_oc,
-            slot_checkpoint,
-            fork_hash,
-            fork_previous_hash: fork_hash,
-            merkle_tree: wallet.consensus_staked_merkle_tree.clone(),
-            proposal_zkbin: proposal_zkbin.clone(),
-            proposal_pk: proposal_pk.clone(),
-        }
-        .build()?;
-
-        let (params, proofs, output_keypair, signature_secret_key) = (
-            proposal_call_debris.params,
-            proposal_call_debris.proofs,
-            proposal_call_debris.keypair,
-            proposal_call_debris.signature_secret,
-        );
-
-        let mut data = vec![ConsensusFunction::ProposalV1 as u8];
-        params.encode(&mut data)?;
-        let call = ContractCall { contract_id: *CONSENSUS_CONTRACT_ID, data };
-
-        let calls = vec![call];
-        let proofs = vec![proofs];
-        let mut tx = Transaction { calls, proofs, signatures: vec![] };
-        let sigs = tx.create_sigs(&mut OsRng, &[signature_secret_key])?;
-        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, params, signature_secret_key, output_keypair.secret))
-    }
-
-    pub async fn execute_proposal_tx(
-        &mut self,
-        holder: Holder,
-        tx: Transaction,
-        params: &ConsensusProposalParamsV1,
-        slot: u64,
-    ) -> Result<()> {
-        let wallet = self.holders.get_mut(&holder).unwrap();
-        let tx_action_benchmark = self.tx_action_benchmarks.get_mut(&TxAction::Proposal).unwrap();
-        let timer = Instant::now();
-
-        let erroneous_txs =
-            wallet.state.read().await.verify_transactions(&[tx], slot, true).await?;
-        assert!(erroneous_txs.is_empty());
-        wallet.consensus_staked_merkle_tree.append(MerkleNode::from(params.output.coin.inner()));
-        tx_action_benchmark.verify_times.push(timer.elapsed());
-
-        Ok(())
-    }
-
-    pub async fn execute_erroneous_proposal_txs(
-        &mut self,
-        holder: Holder,
-        txs: Vec<Transaction>,
-        slot: u64,
-        erroneous: usize,
-    ) -> Result<()> {
-        let wallet = self.holders.get_mut(&holder).unwrap();
-        let tx_action_benchmark = self.tx_action_benchmarks.get_mut(&TxAction::Proposal).unwrap();
-        let timer = Instant::now();
-
-        let erroneous_txs =
-            wallet.state.read().await.verify_transactions(&txs, slot, false).await?;
-        assert_eq!(erroneous_txs.len(), erroneous);
-        tx_action_benchmark.verify_times.push(timer.elapsed());
-
-        Ok(())
-    }
-
-    pub async fn unstake_request(
-        &mut self,
-        holder: Holder,
-        slot: u64,
-        staked_oc: ConsensusOwnCoin,
-    ) -> Result<(Transaction, ConsensusUnstakeReqParamsV1, SecretKey, SecretKey)> {
-        let wallet = self.holders.get_mut(&holder).unwrap();
-        let (burn_pk, burn_zkbin) =
-            self.proving_keys.get(&CONSENSUS_CONTRACT_ZKAS_BURN_NS_V1).unwrap();
-        let (mint_pk, mint_zkbin) =
-            self.proving_keys.get(&CONSENSUS_CONTRACT_ZKAS_MINT_NS_V1).unwrap();
-        let tx_action_benchmark =
-            self.tx_action_benchmarks.get_mut(&TxAction::UnstakeRequest).unwrap();
-        let epoch = wallet.state.read().await.consensus.time_keeper.slot_epoch(slot);
-        let timer = Instant::now();
-
-        // Building Consensus::Unstake params
-        let unstake_request_call_debris = ConsensusUnstakeRequestCallBuilder {
-            owncoin: staked_oc.clone(),
-            epoch,
-            tree: wallet.consensus_staked_merkle_tree.clone(),
-            burn_zkbin: burn_zkbin.clone(),
-            burn_pk: burn_pk.clone(),
-            mint_zkbin: mint_zkbin.clone(),
-            mint_pk: mint_pk.clone(),
-        }
-        .build()?;
-
-        let (
-            unstake_request_params,
-            unstake_request_proofs,
-            unstake_request_output_keypair,
-            unstake_request_signature_secret_key,
-        ) = (
-            unstake_request_call_debris.params,
-            unstake_request_call_debris.proofs,
-            unstake_request_call_debris.keypair,
-            unstake_request_call_debris.signature_secret,
-        );
-
-        // Building unstake request tx
-        let mut data = vec![ConsensusFunction::UnstakeRequestV1 as u8];
-        unstake_request_params.encode(&mut data)?;
-        let call = ContractCall { contract_id: *CONSENSUS_CONTRACT_ID, data };
-        let calls = vec![call];
-        let proofs = vec![unstake_request_proofs];
-        let mut unstake_request_tx = Transaction { calls, proofs, signatures: vec![] };
-        let sigs =
-            unstake_request_tx.create_sigs(&mut OsRng, &[unstake_request_signature_secret_key])?;
-        unstake_request_tx.signatures = vec![sigs];
-        tx_action_benchmark.creation_times.push(timer.elapsed());
-
-        // Calculate transaction sizes
-        let encoded: Vec<u8> = serialize(&unstake_request_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((
-            unstake_request_tx,
-            unstake_request_params,
-            unstake_request_output_keypair.secret,
-            unstake_request_signature_secret_key,
-        ))
-    }
-
-    pub async fn execute_unstake_request_tx(
-        &mut self,
-        holder: Holder,
-        tx: Transaction,
-        params: &ConsensusUnstakeReqParamsV1,
-        slot: u64,
-    ) -> Result<()> {
-        let wallet = self.holders.get_mut(&holder).unwrap();
-        let tx_action_benchmark =
-            self.tx_action_benchmarks.get_mut(&TxAction::UnstakeRequest).unwrap();
-        let timer = Instant::now();
-
-        let erroneous_txs =
-            wallet.state.read().await.verify_transactions(&[tx], slot, true).await?;
-        assert!(erroneous_txs.is_empty());
-        wallet.consensus_unstaked_merkle_tree.append(MerkleNode::from(params.output.coin.inner()));
-        tx_action_benchmark.verify_times.push(timer.elapsed());
-
-        Ok(())
-    }
-
-    pub async fn execute_erroneous_unstake_request_txs(
-        &mut self,
-        holder: Holder,
-        txs: Vec<Transaction>,
-        slot: u64,
-        erroneous: usize,
-    ) -> Result<()> {
-        let wallet = self.holders.get_mut(&holder).unwrap();
-        let tx_action_benchmark =
-            self.tx_action_benchmarks.get_mut(&TxAction::UnstakeRequest).unwrap();
-        let timer = Instant::now();
-
-        let erroneous_txs =
-            wallet.state.read().await.verify_transactions(&txs, slot, false).await?;
-        assert_eq!(erroneous_txs.len(), erroneous);
-        tx_action_benchmark.verify_times.push(timer.elapsed());
-
-        Ok(())
-    }
-
-    pub fn unstake(
-        &mut self,
-        holder: Holder,
-        staked_oc: ConsensusOwnCoin,
-    ) -> Result<(Transaction, MoneyUnstakeParamsV1, SecretKey)> {
-        let wallet = self.holders.get_mut(&holder).unwrap();
-        let (burn_pk, burn_zkbin) =
-            self.proving_keys.get(&CONSENSUS_CONTRACT_ZKAS_BURN_NS_V1).unwrap();
-        let (mint_pk, mint_zkbin) = self.proving_keys.get(&MONEY_CONTRACT_ZKAS_MINT_NS_V1).unwrap();
-        let tx_action_benchmark = self.tx_action_benchmarks.get_mut(&TxAction::Unstake).unwrap();
-        let timer = Instant::now();
-
-        // Building Consensus::Unstake params
-        let consensus_unstake_call_debris = ConsensusUnstakeCallBuilder {
-            owncoin: staked_oc.clone(),
-            tree: wallet.consensus_unstaked_merkle_tree.clone(),
-            burn_zkbin: burn_zkbin.clone(),
-            burn_pk: burn_pk.clone(),
-        }
-        .build()?;
-        let (
-            consensus_unstake_params,
-            consensus_unstake_proofs,
-            consensus_unstake_secret_key,
-            consensus_unstake_value_blind,
-        ) = (
-            consensus_unstake_call_debris.params,
-            consensus_unstake_call_debris.proofs,
-            consensus_unstake_call_debris.signature_secret,
-            consensus_unstake_call_debris.value_blind,
-        );
-
-        // Building Money::Unstake params
-        let money_unstake_call_debris = MoneyUnstakeCallBuilder {
-            owncoin: staked_oc.into(),
-            recipient: self.holders.get_mut(&holder).unwrap().keypair.public,
-            value_blind: consensus_unstake_value_blind,
-            nullifier: consensus_unstake_params.input.nullifier,
-            merkle_root: consensus_unstake_params.input.merkle_root,
-            signature_public: consensus_unstake_params.input.signature_public,
-            mint_zkbin: mint_zkbin.clone(),
-            mint_pk: mint_pk.clone(),
-        }
-        .build()?;
-        let (money_unstake_params, money_unstake_proofs) =
-            (money_unstake_call_debris.params, money_unstake_call_debris.proofs);
-
-        // Building unstake tx
-        let mut data = vec![ConsensusFunction::UnstakeV1 as u8];
-        consensus_unstake_params.encode(&mut data)?;
-        let consensus_call = ContractCall { contract_id: *CONSENSUS_CONTRACT_ID, data };
-
-        let mut data = vec![MoneyFunction::UnstakeV1 as u8];
-        money_unstake_params.encode(&mut data)?;
-        let money_call = ContractCall { contract_id: *MONEY_CONTRACT_ID, data };
-
-        let calls = vec![consensus_call, money_call];
-        let proofs = vec![consensus_unstake_proofs, money_unstake_proofs];
-        let mut unstake_tx = Transaction { calls, proofs, signatures: vec![] };
-        let consensus_sigs = unstake_tx.create_sigs(&mut OsRng, &[consensus_unstake_secret_key])?;
-        let money_sigs = unstake_tx.create_sigs(&mut OsRng, &[consensus_unstake_secret_key])?;
-        unstake_tx.signatures = vec![consensus_sigs, money_sigs];
-        tx_action_benchmark.creation_times.push(timer.elapsed());
-
-        // Calculate transaction sizes
-        let encoded: Vec<u8> = serialize(&unstake_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((unstake_tx, money_unstake_params, consensus_unstake_secret_key))
-    }
-
-    pub async fn execute_unstake_tx(
-        &mut self,
-        holder: Holder,
-        tx: Transaction,
-        params: &MoneyUnstakeParamsV1,
-        slot: u64,
-    ) -> Result<()> {
-        let wallet = self.holders.get_mut(&holder).unwrap();
-        let tx_action_benchmark = self.tx_action_benchmarks.get_mut(&TxAction::Unstake).unwrap();
-        let timer = Instant::now();
-
-        let erroneous_txs =
-            wallet.state.read().await.verify_transactions(&[tx], 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 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: Coin::from(output.coin),
-            note: note.clone(),
-            secret: secret_key,
-            nullifier: Nullifier::from(poseidon_hash([wallet.keypair.secret.inner(), note.serial])),
-            leaf_position,
-        };
-
-        Ok(oc)
-    }
-
-    pub fn gather_consensus_staked_owncoin(
-        &mut self,
-        holder: Holder,
-        output: ConsensusOutput,
-        secret_key: Option<SecretKey>,
-    ) -> Result<ConsensusOwnCoin> {
-        let wallet = self.holders.get_mut(&holder).unwrap();
-        let leaf_position = wallet.consensus_staked_merkle_tree.mark().unwrap();
-        let secret_key = match secret_key {
-            Some(key) => key,
-            None => wallet.keypair.secret,
-        };
-        let note: ConsensusNote = output.note.decrypt(&secret_key)?;
-        let oc = ConsensusOwnCoin {
-            coin: Coin::from(output.coin),
-            note: note.clone(),
-            secret: secret_key,
-            nullifier: Nullifier::from(poseidon_hash([wallet.keypair.secret.inner(), note.serial])),
-            leaf_position,
-        };
-
-        Ok(oc)
-    }
-
-    pub fn gather_consensus_unstaked_owncoin(
-        &mut self,
-        holder: Holder,
-        output: ConsensusOutput,
-        secret_key: Option<SecretKey>,
-    ) -> Result<ConsensusOwnCoin> {
-        let wallet = self.holders.get_mut(&holder).unwrap();
-        let leaf_position = wallet.consensus_unstaked_merkle_tree.mark().unwrap();
-        let secret_key = match secret_key {
-            Some(key) => key,
-            None => wallet.keypair.secret,
-        };
-        let note: ConsensusNote = output.note.decrypt(&secret_key)?;
-        let oc = ConsensusOwnCoin {
-            coin: Coin::from(output.coin),
-            note: note.clone(),
-            secret: secret_key,
-            nullifier: Nullifier::from(poseidon_hash([wallet.keypair.secret.inner(), note.serial])),
-            leaf_position,
-        };
-
-        Ok(oc)
-    }
-
-    pub async fn get_slot_checkpoint_by_slot(&self, slot: u64) -> Result<SlotCheckpoint> {
-        let faucet = self.holders.get(&Holder::Faucet).unwrap();
-        let slot_checkpoint =
-            faucet.state.read().await.blockchain.get_slot_checkpoints_by_slot(&[slot])?[0]
-                .clone()
-                .unwrap();
-
-        Ok(slot_checkpoint)
-    }
-
-    pub async fn generate_slot_checkpoint(&self, slot: u64) -> Result<SlotCheckpoint> {
-        // We grab the genesis slot to generate slot checkpoint
-        // using same consensus parameters
-        let faucet = self.holders.get(&Holder::Faucet).unwrap();
-        let genesis_block = faucet.state.read().await.consensus.genesis_block;
-        let fork_hashes = vec![genesis_block];
-        let fork_previous_hashes = vec![genesis_block];
-        let genesis_slot = self.get_slot_checkpoint_by_slot(0).await?;
-        let slot_checkpoint = SlotCheckpoint {
-            slot,
-            previous_eta: genesis_slot.previous_eta,
-            fork_hashes,
-            fork_previous_hashes,
-            sigma1: genesis_slot.sigma1,
-            sigma2: genesis_slot.sigma2,
-        };
-
-        // Store generated slot checkpoint
-        for wallet in self.holders.values() {
-            wallet.state.write().await.receive_slot_checkpoints(&[slot_checkpoint.clone()]).await?;
-        }
-
-        Ok(slot_checkpoint)
-    }
-
-    pub fn assert_trees(&self) {
-        let faucet = self.holders.get(&Holder::Faucet).unwrap();
-        let money_root = faucet.money_merkle_tree.root(0).unwrap();
-        let consensus_stake_root = faucet.consensus_staked_merkle_tree.root(0).unwrap();
-        let consensus_unstake_root = faucet.consensus_unstaked_merkle_tree.root(0).unwrap();
-        for wallet in self.holders.values() {
-            assert!(money_root == wallet.money_merkle_tree.root(0).unwrap());
-            assert!(consensus_stake_root == wallet.consensus_staked_merkle_tree.root(0).unwrap());
-            assert!(
-                consensus_unstake_root == wallet.consensus_unstaked_merkle_tree.root(0).unwrap()
-            );
-        }
-    }
-
-    pub fn statistics(&self) {
-        info!(target: "consensus", "==================== Statistics ====================");
-        for (action, tx_action_benchmark) in &self.tx_action_benchmarks {
-            tx_action_benchmark.statistics(action);
-        }
-        info!(target: "consensus", "====================================================");
-    }
-}

+ 22 - 21
src/contract/consensus/tests/stake_unstake.rs

@@ -30,14 +30,15 @@ use darkfi::Result;
 use log::info;
 
 use darkfi_consensus_contract::model::{calculate_grace_period, EPOCH_LENGTH, REWARD};
-
-mod harness;
-use harness::{init_logger, ConsensusTestHarness, Holder};
+use darkfi_contract_test_harness::{init_logger, Holder, TestHarness};
 
 #[async_std::test]
 async fn consensus_contract_stake_unstake() -> Result<()> {
     init_logger();
 
+    // Holders this test will use
+    const HOLDERS: [Holder; 2] = [Holder::Faucet, Holder::Alice];
+
     // Some numbers we want to assert
     const ALICE_AIRDROP: u64 = 1000;
 
@@ -45,7 +46,9 @@ async fn consensus_contract_stake_unstake() -> Result<()> {
     let mut current_slot = 1;
 
     // Initialize harness
-    let mut th = ConsensusTestHarness::new().await?;
+    let mut th = TestHarness::new(&["money".to_string(), "consensus".to_string()]).await?;
+
+    // Now Alice can airdrop some native tokens to herself
     info!(target: "consensus", "[Faucet] =========================");
     info!(target: "consensus", "[Faucet] Building Alice airdrop tx");
     info!(target: "consensus", "[Faucet] =========================");
@@ -54,15 +57,15 @@ async fn consensus_contract_stake_unstake() -> Result<()> {
     info!(target: "consensus", "[Faucet] ==========================");
     info!(target: "consensus", "[Faucet] Executing Alice airdrop tx");
     info!(target: "consensus", "[Faucet] ==========================");
-    th.execute_airdrop_native_tx(Holder::Faucet, airdrop_tx.clone(), &airdrop_params, current_slot)
+    th.execute_airdrop_native_tx(Holder::Faucet, &airdrop_tx, &airdrop_params, current_slot)
         .await?;
 
     info!(target: "consensus", "[Alice] ==========================");
     info!(target: "consensus", "[Alice] Executing Alice airdrop tx");
     info!(target: "consensus", "[Alice] ==========================");
-    th.execute_airdrop_native_tx(Holder::Alice, airdrop_tx, &airdrop_params, current_slot).await?;
+    th.execute_airdrop_native_tx(Holder::Alice, &airdrop_tx, &airdrop_params, current_slot).await?;
 
-    th.assert_trees();
+    th.assert_trees(&HOLDERS);
 
     // Gather new owncoin
     let alice_oc = th.gather_owncoin(Holder::Alice, airdrop_params.outputs[0].clone(), None)?;
@@ -77,14 +80,14 @@ async fn consensus_contract_stake_unstake() -> Result<()> {
     info!(target: "consensus", "[Faucet] ========================");
     info!(target: "consensus", "[Faucet] Executing Alice stake tx");
     info!(target: "consensus", "[Faucet] ========================");
-    th.execute_stake_tx(Holder::Faucet, stake_tx.clone(), &stake_params, current_slot).await?;
+    th.execute_stake_tx(Holder::Faucet, &stake_tx, &stake_params, current_slot).await?;
 
     info!(target: "consensus", "[Alice] ========================");
     info!(target: "consensus", "[Alice] Executing Alice stake tx");
     info!(target: "consensus", "[Alice] ========================");
-    th.execute_stake_tx(Holder::Alice, stake_tx, &stake_params, current_slot).await?;
+    th.execute_stake_tx(Holder::Alice, &stake_tx, &stake_params, current_slot).await?;
 
-    th.assert_trees();
+    th.assert_trees(&HOLDERS);
 
     // Gather new staked owncoin
     let alice_staked_oc = th.gather_consensus_staked_owncoin(
@@ -128,15 +131,14 @@ async fn consensus_contract_stake_unstake() -> Result<()> {
     info!(target: "consensus", "[Faucet] ===========================");
     info!(target: "consensus", "[Faucet] Executing Alice proposal tx");
     info!(target: "consensus", "[Faucet] ===========================");
-    th.execute_proposal_tx(Holder::Faucet, proposal_tx.clone(), &proposal_params, current_slot)
-        .await?;
+    th.execute_proposal_tx(Holder::Faucet, &proposal_tx, &proposal_params, current_slot).await?;
 
     info!(target: "consensus", "[Alice] ===========================");
     info!(target: "consensus", "[Alice] Executing Alice proposal tx");
     info!(target: "consensus", "[Alice] ===========================");
-    th.execute_proposal_tx(Holder::Alice, proposal_tx, &proposal_params, current_slot).await?;
+    th.execute_proposal_tx(Holder::Alice, &proposal_tx, &proposal_params, current_slot).await?;
 
-    th.assert_trees();
+    th.assert_trees(&HOLDERS);
 
     // Gather new staked owncoin which includes the reward
     let alice_rewarded_staked_oc = th.gather_consensus_staked_owncoin(
@@ -168,7 +170,7 @@ async fn consensus_contract_stake_unstake() -> Result<()> {
     info!(target: "consensus", "[Faucet] ==================================");
     th.execute_unstake_request_tx(
         Holder::Faucet,
-        unstake_request_tx.clone(),
+        &unstake_request_tx,
         &unstake_request_params,
         current_slot,
     )
@@ -179,13 +181,13 @@ async fn consensus_contract_stake_unstake() -> Result<()> {
     info!(target: "consensus", "[Alice] ==================================");
     th.execute_unstake_request_tx(
         Holder::Alice,
-        unstake_request_tx,
+        &unstake_request_tx,
         &unstake_request_params,
         current_slot,
     )
     .await?;
 
-    th.assert_trees();
+    th.assert_trees(&HOLDERS);
 
     // Gather new unstake request owncoin
     let alice_unstake_request_oc = th.gather_consensus_unstaked_owncoin(
@@ -234,15 +236,14 @@ async fn consensus_contract_stake_unstake() -> Result<()> {
     info!(target: "consensus", "[Faucet] ==========================");
     info!(target: "consensus", "[Faucet] Executing Alice unstake tx");
     info!(target: "consensus", "[Faucet] ==========================");
-    th.execute_unstake_tx(Holder::Faucet, unstake_tx.clone(), &unstake_params, current_slot)
-        .await?;
+    th.execute_unstake_tx(Holder::Faucet, &unstake_tx, &unstake_params, current_slot).await?;
 
     info!(target: "consensus", "[Alice] ==========================");
     info!(target: "consensus", "[Alice] Executing Alice unstake tx");
     info!(target: "consensus", "[Alice] ==========================");
-    th.execute_unstake_tx(Holder::Alice, unstake_tx, &unstake_params, current_slot).await?;
+    th.execute_unstake_tx(Holder::Alice, &unstake_tx, &unstake_params, current_slot).await?;
 
-    th.assert_trees();
+    th.assert_trees(&HOLDERS);
 
     // Gather new unstaked owncoin
     let alice_unstaked_oc = th.gather_owncoin(Holder::Alice, unstake_params.output, None)?;

+ 123 - 0
src/contract/test-harness/src/consensus_genesis_stake.rs

@@ -0,0 +1,123 @@
+/* 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, Result};
+use darkfi_consensus_contract::{
+    client::genesis_stake_v1::ConsensusGenesisStakeCallBuilder,
+    model::ConsensusGenesisStakeParamsV1, ConsensusFunction,
+};
+use darkfi_money_contract::CONSENSUS_CONTRACT_ZKAS_MINT_NS_V1;
+use darkfi_sdk::{
+    crypto::{MerkleNode, CONSENSUS_CONTRACT_ID},
+    ContractCall,
+};
+use darkfi_serial::{serialize, Encodable};
+use rand::rngs::OsRng;
+
+use super::{Holder, TestHarness, TxAction};
+
+impl TestHarness {
+    pub fn genesis_stake(
+        &mut self,
+        holder: Holder,
+        amount: u64,
+    ) -> Result<(Transaction, ConsensusGenesisStakeParamsV1)> {
+        let wallet = self.holders.get_mut(&holder).unwrap();
+        let (mint_pk, mint_zkbin) =
+            self.proving_keys.get(&CONSENSUS_CONTRACT_ZKAS_MINT_NS_V1).unwrap();
+        let tx_action_benchmark =
+            self.tx_action_benchmarks.get_mut(&TxAction::ConsensusGenesisStake).unwrap();
+        let timer = Instant::now();
+
+        // Building Consensus::GenesisStake params
+        let genesis_stake_call_debris = ConsensusGenesisStakeCallBuilder {
+            keypair: wallet.keypair,
+            recipient: wallet.keypair.public,
+            amount,
+            mint_zkbin: mint_zkbin.clone(),
+            mint_pk: mint_pk.clone(),
+        }
+        .build()?;
+        let (genesis_stake_params, genesis_stake_proofs) =
+            (genesis_stake_call_debris.params, genesis_stake_call_debris.proofs);
+
+        // Building genesis stake tx
+        let mut data = vec![ConsensusFunction::GenesisStakeV1 as u8];
+        genesis_stake_params.encode(&mut data)?;
+        let contract_call = ContractCall { contract_id: *CONSENSUS_CONTRACT_ID, data };
+        let calls = vec![contract_call];
+        let proofs = vec![genesis_stake_proofs];
+        let mut genesis_stake_tx = Transaction { calls, proofs, signatures: vec![] };
+        let sigs = genesis_stake_tx.create_sigs(&mut OsRng, &[wallet.keypair.secret])?;
+        genesis_stake_tx.signatures = vec![sigs];
+        tx_action_benchmark.creation_times.push(timer.elapsed());
+
+        // Calculate transaction sizes
+        let encoded: Vec<u8> = serialize(&genesis_stake_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((genesis_stake_tx, genesis_stake_params))
+    }
+
+    pub async fn execute_genesis_stake_tx(
+        &mut self,
+        holder: Holder,
+        tx: &Transaction,
+        params: &ConsensusGenesisStakeParamsV1,
+        slot: u64,
+    ) -> Result<()> {
+        let wallet = self.holders.get_mut(&holder).unwrap();
+        let tx_action_benchmark =
+            self.tx_action_benchmarks.get_mut(&TxAction::ConsensusGenesisStake).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.consensus_staked_merkle_tree.append(MerkleNode::from(params.output.coin.inner()));
+        tx_action_benchmark.verify_times.push(timer.elapsed());
+
+        Ok(())
+    }
+
+    pub async fn execute_erroneous_genesis_stake_txs(
+        &mut self,
+        holder: Holder,
+        txs: Vec<Transaction>,
+        slot: u64,
+        erroneous: usize,
+    ) -> Result<()> {
+        let wallet = self.holders.get_mut(&holder).unwrap();
+        let tx_action_benchmark =
+            self.tx_action_benchmarks.get_mut(&TxAction::ConsensusGenesisStake).unwrap();
+        let timer = Instant::now();
+
+        let erroneous_txs =
+            wallet.state.read().await.verify_transactions(&txs, slot, false).await?;
+        assert_eq!(erroneous_txs.len(), erroneous);
+        tx_action_benchmark.verify_times.push(timer.elapsed());
+
+        Ok(())
+    }
+}

+ 134 - 0
src/contract/test-harness/src/consensus_proposal.rs

@@ -0,0 +1,134 @@
+/* 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::{consensus::SlotCheckpoint, tx::Transaction, Result};
+use darkfi_consensus_contract::{
+    client::proposal_v1::ConsensusProposalCallBuilder, model::ConsensusProposalParamsV1,
+    ConsensusFunction,
+};
+use darkfi_money_contract::{client::ConsensusOwnCoin, CONSENSUS_CONTRACT_ZKAS_PROPOSAL_NS_V1};
+use darkfi_sdk::{
+    crypto::{MerkleNode, SecretKey, CONSENSUS_CONTRACT_ID},
+    ContractCall,
+};
+use darkfi_serial::{serialize, Encodable};
+use rand::rngs::OsRng;
+
+use super::{Holder, TestHarness, TxAction};
+
+impl TestHarness {
+    pub async fn proposal(
+        &mut self,
+        holder: Holder,
+        slot_checkpoint: SlotCheckpoint,
+        staked_oc: ConsensusOwnCoin,
+    ) -> Result<(Transaction, ConsensusProposalParamsV1, SecretKey, SecretKey)> {
+        let wallet = self.holders.get_mut(&holder).unwrap();
+        let (proposal_pk, proposal_zkbin) =
+            self.proving_keys.get(&CONSENSUS_CONTRACT_ZKAS_PROPOSAL_NS_V1).unwrap();
+        let tx_action_benchmark =
+            self.tx_action_benchmarks.get_mut(&TxAction::ConsensusProposal).unwrap();
+        let timer = Instant::now();
+
+        // Proposals always extend genesis block
+        let fork_hash = wallet.state.read().await.consensus.genesis_block;
+
+        // Building Consensus::Propose params
+        let proposal_call_debris = ConsensusProposalCallBuilder {
+            owncoin: staked_oc,
+            slot_checkpoint,
+            fork_hash,
+            fork_previous_hash: fork_hash,
+            merkle_tree: wallet.consensus_staked_merkle_tree.clone(),
+            proposal_zkbin: proposal_zkbin.clone(),
+            proposal_pk: proposal_pk.clone(),
+        }
+        .build()?;
+
+        let (params, proofs, output_keypair, signature_secret_key) = (
+            proposal_call_debris.params,
+            proposal_call_debris.proofs,
+            proposal_call_debris.keypair,
+            proposal_call_debris.signature_secret,
+        );
+
+        let mut data = vec![ConsensusFunction::ProposalV1 as u8];
+        params.encode(&mut data)?;
+        let call = ContractCall { contract_id: *CONSENSUS_CONTRACT_ID, data };
+
+        let calls = vec![call];
+        let proofs = vec![proofs];
+        let mut tx = Transaction { calls, proofs, signatures: vec![] };
+        let sigs = tx.create_sigs(&mut OsRng, &[signature_secret_key])?;
+        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, params, signature_secret_key, output_keypair.secret))
+    }
+
+    pub async fn execute_proposal_tx(
+        &mut self,
+        holder: Holder,
+        tx: &Transaction,
+        params: &ConsensusProposalParamsV1,
+        slot: u64,
+    ) -> Result<()> {
+        let wallet = self.holders.get_mut(&holder).unwrap();
+        let tx_action_benchmark =
+            self.tx_action_benchmarks.get_mut(&TxAction::ConsensusProposal).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.consensus_staked_merkle_tree.append(MerkleNode::from(params.output.coin.inner()));
+        tx_action_benchmark.verify_times.push(timer.elapsed());
+
+        Ok(())
+    }
+
+    pub async fn execute_erroneous_proposal_txs(
+        &mut self,
+        holder: Holder,
+        txs: Vec<Transaction>,
+        slot: u64,
+        erroneous: usize,
+    ) -> Result<()> {
+        let wallet = self.holders.get_mut(&holder).unwrap();
+        let tx_action_benchmark =
+            self.tx_action_benchmarks.get_mut(&TxAction::ConsensusProposal).unwrap();
+        let timer = Instant::now();
+
+        let erroneous_txs =
+            wallet.state.read().await.verify_transactions(&txs, slot, false).await?;
+        assert_eq!(erroneous_txs.len(), erroneous);
+        tx_action_benchmark.verify_times.push(timer.elapsed());
+
+        Ok(())
+    }
+}

+ 139 - 0
src/contract/test-harness/src/consensus_stake.rs

@@ -0,0 +1,139 @@
+/* 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, Result};
+use darkfi_consensus_contract::{client::stake_v1::ConsensusStakeCallBuilder, ConsensusFunction};
+use darkfi_money_contract::{
+    client::{stake_v1::MoneyStakeCallBuilder, OwnCoin},
+    model::ConsensusStakeParamsV1,
+    MoneyFunction, CONSENSUS_CONTRACT_ZKAS_MINT_NS_V1, MONEY_CONTRACT_ZKAS_BURN_NS_V1,
+};
+use darkfi_sdk::{
+    crypto::{MerkleNode, SecretKey, CONSENSUS_CONTRACT_ID, MONEY_CONTRACT_ID},
+    ContractCall,
+};
+use darkfi_serial::{serialize, Encodable};
+use rand::rngs::OsRng;
+
+use super::{Holder, TestHarness, TxAction};
+
+impl TestHarness {
+    pub async fn stake(
+        &mut self,
+        holder: Holder,
+        slot: u64,
+        owncoin: OwnCoin,
+    ) -> Result<(Transaction, ConsensusStakeParamsV1, SecretKey)> {
+        let wallet = self.holders.get_mut(&holder).unwrap();
+        let (mint_pk, mint_zkbin) =
+            self.proving_keys.get(&CONSENSUS_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::ConsensusStake).unwrap();
+        let epoch = wallet.state.read().await.consensus.time_keeper.slot_epoch(slot);
+        let timer = Instant::now();
+
+        // Building Money::Stake params
+        let money_stake_call_debris = MoneyStakeCallBuilder {
+            coin: owncoin.clone(),
+            tree: wallet.money_merkle_tree.clone(),
+            burn_zkbin: burn_zkbin.clone(),
+            burn_pk: burn_pk.clone(),
+        }
+        .build()?;
+
+        let (
+            money_stake_params,
+            money_stake_proofs,
+            money_stake_secret_key,
+            money_stake_value_blind,
+        ) = (
+            money_stake_call_debris.params,
+            money_stake_call_debris.proofs,
+            money_stake_call_debris.signature_secret,
+            money_stake_call_debris.value_blind,
+        );
+
+        // Building Consensus::Stake params
+        let consensus_stake_call_debris = ConsensusStakeCallBuilder {
+            coin: owncoin,
+            epoch,
+            value_blind: money_stake_value_blind,
+            money_input: money_stake_params.input.clone(),
+            mint_zkbin: mint_zkbin.clone(),
+            mint_pk: mint_pk.clone(),
+        }
+        .build()?;
+
+        let (consensus_stake_params, consensus_stake_proofs, consensus_stake_secret_key) = (
+            consensus_stake_call_debris.params,
+            consensus_stake_call_debris.proofs,
+            consensus_stake_call_debris.signature_secret,
+        );
+
+        // Building stake tx
+        let mut data = vec![MoneyFunction::StakeV1 as u8];
+        money_stake_params.encode(&mut data)?;
+        let money_call = ContractCall { contract_id: *MONEY_CONTRACT_ID, data };
+
+        let mut data = vec![ConsensusFunction::StakeV1 as u8];
+        consensus_stake_params.encode(&mut data)?;
+        let consensus_call = ContractCall { contract_id: *CONSENSUS_CONTRACT_ID, data };
+
+        let calls = vec![money_call, consensus_call];
+        let proofs = vec![money_stake_proofs, consensus_stake_proofs];
+        let mut stake_tx = Transaction { calls, proofs, signatures: vec![] };
+        let money_sigs = stake_tx.create_sigs(&mut OsRng, &[money_stake_secret_key])?;
+        let consensus_sigs = stake_tx.create_sigs(&mut OsRng, &[consensus_stake_secret_key])?;
+        stake_tx.signatures = vec![money_sigs, consensus_sigs];
+        tx_action_benchmark.creation_times.push(timer.elapsed());
+
+        // Calculate transaction sizes
+        let encoded: Vec<u8> = serialize(&stake_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((stake_tx, consensus_stake_params, consensus_stake_secret_key))
+    }
+
+    pub async fn execute_stake_tx(
+        &mut self,
+        holder: Holder,
+        tx: &Transaction,
+        params: &ConsensusStakeParamsV1,
+        slot: u64,
+    ) -> Result<()> {
+        let wallet = self.holders.get_mut(&holder).unwrap();
+        let tx_action_benchmark =
+            self.tx_action_benchmarks.get_mut(&TxAction::ConsensusStake).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.consensus_staked_merkle_tree.append(MerkleNode::from(params.output.coin.inner()));
+        tx_action_benchmark.verify_times.push(timer.elapsed());
+
+        Ok(())
+    }
+}

+ 136 - 0
src/contract/test-harness/src/consensus_unstake.rs

@@ -0,0 +1,136 @@
+/* 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, Result};
+use darkfi_consensus_contract::{
+    client::unstake_v1::ConsensusUnstakeCallBuilder, ConsensusFunction,
+};
+use darkfi_money_contract::{
+    client::{unstake_v1::MoneyUnstakeCallBuilder, ConsensusOwnCoin},
+    model::MoneyUnstakeParamsV1,
+    MoneyFunction, CONSENSUS_CONTRACT_ZKAS_BURN_NS_V1, MONEY_CONTRACT_ZKAS_MINT_NS_V1,
+};
+use darkfi_sdk::{
+    crypto::{MerkleNode, SecretKey, CONSENSUS_CONTRACT_ID, MONEY_CONTRACT_ID},
+    ContractCall,
+};
+use darkfi_serial::{serialize, Encodable};
+use rand::rngs::OsRng;
+
+use super::{Holder, TestHarness, TxAction};
+
+impl TestHarness {
+    pub fn unstake(
+        &mut self,
+        holder: Holder,
+        staked_oc: ConsensusOwnCoin,
+    ) -> Result<(Transaction, MoneyUnstakeParamsV1, SecretKey)> {
+        let wallet = self.holders.get_mut(&holder).unwrap();
+        let (burn_pk, burn_zkbin) =
+            self.proving_keys.get(&CONSENSUS_CONTRACT_ZKAS_BURN_NS_V1).unwrap();
+        let (mint_pk, mint_zkbin) = self.proving_keys.get(&MONEY_CONTRACT_ZKAS_MINT_NS_V1).unwrap();
+        let tx_action_benchmark =
+            self.tx_action_benchmarks.get_mut(&TxAction::ConsensusUnstake).unwrap();
+        let timer = Instant::now();
+
+        // Building Consensus::Unstake params
+        let consensus_unstake_call_debris = ConsensusUnstakeCallBuilder {
+            owncoin: staked_oc.clone(),
+            tree: wallet.consensus_unstaked_merkle_tree.clone(),
+            burn_zkbin: burn_zkbin.clone(),
+            burn_pk: burn_pk.clone(),
+        }
+        .build()?;
+        let (
+            consensus_unstake_params,
+            consensus_unstake_proofs,
+            consensus_unstake_secret_key,
+            consensus_unstake_value_blind,
+        ) = (
+            consensus_unstake_call_debris.params,
+            consensus_unstake_call_debris.proofs,
+            consensus_unstake_call_debris.signature_secret,
+            consensus_unstake_call_debris.value_blind,
+        );
+
+        // Building Money::Unstake params
+        let money_unstake_call_debris = MoneyUnstakeCallBuilder {
+            owncoin: staked_oc.into(),
+            recipient: self.holders.get_mut(&holder).unwrap().keypair.public,
+            value_blind: consensus_unstake_value_blind,
+            nullifier: consensus_unstake_params.input.nullifier,
+            merkle_root: consensus_unstake_params.input.merkle_root,
+            signature_public: consensus_unstake_params.input.signature_public,
+            mint_zkbin: mint_zkbin.clone(),
+            mint_pk: mint_pk.clone(),
+        }
+        .build()?;
+        let (money_unstake_params, money_unstake_proofs) =
+            (money_unstake_call_debris.params, money_unstake_call_debris.proofs);
+
+        // Building unstake tx
+        let mut data = vec![ConsensusFunction::UnstakeV1 as u8];
+        consensus_unstake_params.encode(&mut data)?;
+        let consensus_call = ContractCall { contract_id: *CONSENSUS_CONTRACT_ID, data };
+
+        let mut data = vec![MoneyFunction::UnstakeV1 as u8];
+        money_unstake_params.encode(&mut data)?;
+        let money_call = ContractCall { contract_id: *MONEY_CONTRACT_ID, data };
+
+        let calls = vec![consensus_call, money_call];
+        let proofs = vec![consensus_unstake_proofs, money_unstake_proofs];
+        let mut unstake_tx = Transaction { calls, proofs, signatures: vec![] };
+        let consensus_sigs = unstake_tx.create_sigs(&mut OsRng, &[consensus_unstake_secret_key])?;
+        let money_sigs = unstake_tx.create_sigs(&mut OsRng, &[consensus_unstake_secret_key])?;
+        unstake_tx.signatures = vec![consensus_sigs, money_sigs];
+        tx_action_benchmark.creation_times.push(timer.elapsed());
+
+        // Calculate transaction sizes
+        let encoded: Vec<u8> = serialize(&unstake_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((unstake_tx, money_unstake_params, consensus_unstake_secret_key))
+    }
+
+    pub async fn execute_unstake_tx(
+        &mut self,
+        holder: Holder,
+        tx: &Transaction,
+        params: &MoneyUnstakeParamsV1,
+        slot: u64,
+    ) -> Result<()> {
+        let wallet = self.holders.get_mut(&holder).unwrap();
+        let tx_action_benchmark =
+            self.tx_action_benchmarks.get_mut(&TxAction::ConsensusUnstake).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(())
+    }
+}

+ 147 - 0
src/contract/test-harness/src/consensus_unstake_request.rs

@@ -0,0 +1,147 @@
+/* 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, Result};
+use darkfi_consensus_contract::{
+    client::unstake_request_v1::ConsensusUnstakeRequestCallBuilder, ConsensusFunction,
+};
+use darkfi_money_contract::{
+    client::ConsensusOwnCoin, model::ConsensusUnstakeReqParamsV1,
+    CONSENSUS_CONTRACT_ZKAS_BURN_NS_V1, CONSENSUS_CONTRACT_ZKAS_MINT_NS_V1,
+};
+use darkfi_sdk::{
+    crypto::{MerkleNode, SecretKey, CONSENSUS_CONTRACT_ID},
+    ContractCall,
+};
+use darkfi_serial::{serialize, Encodable};
+use rand::rngs::OsRng;
+
+use super::{Holder, TestHarness, TxAction};
+
+impl TestHarness {
+    pub async fn unstake_request(
+        &mut self,
+        holder: Holder,
+        slot: u64,
+        staked_oc: ConsensusOwnCoin,
+    ) -> Result<(Transaction, ConsensusUnstakeReqParamsV1, SecretKey, SecretKey)> {
+        let wallet = self.holders.get_mut(&holder).unwrap();
+        let (burn_pk, burn_zkbin) =
+            self.proving_keys.get(&CONSENSUS_CONTRACT_ZKAS_BURN_NS_V1).unwrap();
+        let (mint_pk, mint_zkbin) =
+            self.proving_keys.get(&CONSENSUS_CONTRACT_ZKAS_MINT_NS_V1).unwrap();
+        let tx_action_benchmark =
+            self.tx_action_benchmarks.get_mut(&TxAction::ConsensusUnstakeRequest).unwrap();
+        let epoch = wallet.state.read().await.consensus.time_keeper.slot_epoch(slot);
+        let timer = Instant::now();
+
+        // Building Consensus::Unstake params
+        let unstake_request_call_debris = ConsensusUnstakeRequestCallBuilder {
+            owncoin: staked_oc.clone(),
+            epoch,
+            tree: wallet.consensus_staked_merkle_tree.clone(),
+            burn_zkbin: burn_zkbin.clone(),
+            burn_pk: burn_pk.clone(),
+            mint_zkbin: mint_zkbin.clone(),
+            mint_pk: mint_pk.clone(),
+        }
+        .build()?;
+
+        let (
+            unstake_request_params,
+            unstake_request_proofs,
+            unstake_request_output_keypair,
+            unstake_request_signature_secret_key,
+        ) = (
+            unstake_request_call_debris.params,
+            unstake_request_call_debris.proofs,
+            unstake_request_call_debris.keypair,
+            unstake_request_call_debris.signature_secret,
+        );
+
+        // Building unstake request tx
+        let mut data = vec![ConsensusFunction::UnstakeRequestV1 as u8];
+        unstake_request_params.encode(&mut data)?;
+        let call = ContractCall { contract_id: *CONSENSUS_CONTRACT_ID, data };
+        let calls = vec![call];
+        let proofs = vec![unstake_request_proofs];
+        let mut unstake_request_tx = Transaction { calls, proofs, signatures: vec![] };
+        let sigs =
+            unstake_request_tx.create_sigs(&mut OsRng, &[unstake_request_signature_secret_key])?;
+        unstake_request_tx.signatures = vec![sigs];
+        tx_action_benchmark.creation_times.push(timer.elapsed());
+
+        // Calculate transaction sizes
+        let encoded: Vec<u8> = serialize(&unstake_request_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((
+            unstake_request_tx,
+            unstake_request_params,
+            unstake_request_output_keypair.secret,
+            unstake_request_signature_secret_key,
+        ))
+    }
+
+    pub async fn execute_unstake_request_tx(
+        &mut self,
+        holder: Holder,
+        tx: &Transaction,
+        params: &ConsensusUnstakeReqParamsV1,
+        slot: u64,
+    ) -> Result<()> {
+        let wallet = self.holders.get_mut(&holder).unwrap();
+        let tx_action_benchmark =
+            self.tx_action_benchmarks.get_mut(&TxAction::ConsensusUnstakeRequest).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.consensus_unstaked_merkle_tree.append(MerkleNode::from(params.output.coin.inner()));
+        tx_action_benchmark.verify_times.push(timer.elapsed());
+
+        Ok(())
+    }
+
+    pub async fn execute_erroneous_unstake_request_txs(
+        &mut self,
+        holder: Holder,
+        txs: Vec<Transaction>,
+        slot: u64,
+        erroneous: usize,
+    ) -> Result<()> {
+        let wallet = self.holders.get_mut(&holder).unwrap();
+        let tx_action_benchmark =
+            self.tx_action_benchmarks.get_mut(&TxAction::ConsensusUnstakeRequest).unwrap();
+        let timer = Instant::now();
+
+        let erroneous_txs =
+            wallet.state.read().await.verify_transactions(&txs, slot, false).await?;
+        assert_eq!(erroneous_txs.len(), erroneous);
+        tx_action_benchmark.verify_times.push(timer.elapsed());
+
+        Ok(())
+    }
+}

+ 131 - 12
src/contract/test-harness/src/lib.rs

@@ -20,8 +20,8 @@ use std::collections::HashMap;
 
 use darkfi::{
     consensus::{
-        ValidatorState, ValidatorStatePtr, TESTNET_BOOTSTRAP_TIMESTAMP, TESTNET_GENESIS_HASH_BYTES,
-        TESTNET_GENESIS_TIMESTAMP, TESTNET_INITIAL_DISTRIBUTION,
+        SlotCheckpoint, 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},
@@ -35,8 +35,8 @@ use darkfi_dao_contract::{
     DAO_CONTRACT_ZKAS_DAO_VOTE_BURN_NS, DAO_CONTRACT_ZKAS_DAO_VOTE_MAIN_NS,
 };
 use darkfi_money_contract::{
-    client::{MoneyNote, OwnCoin},
-    model::Output,
+    client::{ConsensusNote, ConsensusOwnCoin, MoneyNote, OwnCoin},
+    model::{Coin, ConsensusOutput, 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,
@@ -54,6 +54,11 @@ mod benchmarks;
 use benchmarks::TxActionBenchmarks;
 mod vks;
 
+mod consensus_genesis_stake;
+mod consensus_proposal;
+mod consensus_stake;
+mod consensus_unstake;
+mod consensus_unstake_request;
 mod money_airdrop;
 mod money_token;
 
@@ -92,6 +97,11 @@ pub enum TxAction {
     MoneyGenesisMint,
     MoneyTransfer,
     MoneyOtcSwap,
+    ConsensusGenesisStake,
+    ConsensusStake,
+    ConsensusProposal,
+    ConsensusUnstakeRequest,
+    ConsensusUnstake,
 }
 
 pub struct Wallet {
@@ -244,6 +254,11 @@ impl TestHarness {
         tx_action_benchmarks.insert(TxAction::MoneyGenesisMint, TxActionBenchmarks::new());
         tx_action_benchmarks.insert(TxAction::MoneyOtcSwap, TxActionBenchmarks::new());
         tx_action_benchmarks.insert(TxAction::MoneyTransfer, TxActionBenchmarks::new());
+        tx_action_benchmarks.insert(TxAction::ConsensusGenesisStake, TxActionBenchmarks::new());
+        tx_action_benchmarks.insert(TxAction::ConsensusStake, TxActionBenchmarks::new());
+        tx_action_benchmarks.insert(TxAction::ConsensusProposal, TxActionBenchmarks::new());
+        tx_action_benchmarks.insert(TxAction::ConsensusUnstakeRequest, TxActionBenchmarks::new());
+        tx_action_benchmarks.insert(TxAction::ConsensusUnstake, TxActionBenchmarks::new());
 
         // Alice jumps down the rabbit hole
         holders.insert(Holder::Alice, alice);
@@ -251,14 +266,6 @@ impl TestHarness {
         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,
@@ -285,4 +292,116 @@ impl TestHarness {
 
         Ok(oc)
     }
+
+    pub fn gather_consensus_staked_owncoin(
+        &mut self,
+        holder: Holder,
+        output: ConsensusOutput,
+        secret_key: Option<SecretKey>,
+    ) -> Result<ConsensusOwnCoin> {
+        let wallet = self.holders.get_mut(&holder).unwrap();
+        let leaf_position = wallet.consensus_staked_merkle_tree.mark().unwrap();
+        let secret_key = match secret_key {
+            Some(key) => key,
+            None => wallet.keypair.secret,
+        };
+        let note: ConsensusNote = output.note.decrypt(&secret_key)?;
+        let oc = ConsensusOwnCoin {
+            coin: Coin::from(output.coin),
+            note: note.clone(),
+            secret: secret_key,
+            nullifier: Nullifier::from(poseidon_hash([wallet.keypair.secret.inner(), note.serial])),
+            leaf_position,
+        };
+
+        Ok(oc)
+    }
+
+    pub fn gather_consensus_unstaked_owncoin(
+        &mut self,
+        holder: Holder,
+        output: ConsensusOutput,
+        secret_key: Option<SecretKey>,
+    ) -> Result<ConsensusOwnCoin> {
+        let wallet = self.holders.get_mut(&holder).unwrap();
+        let leaf_position = wallet.consensus_unstaked_merkle_tree.mark().unwrap();
+        let secret_key = match secret_key {
+            Some(key) => key,
+            None => wallet.keypair.secret,
+        };
+        let note: ConsensusNote = output.note.decrypt(&secret_key)?;
+        let oc = ConsensusOwnCoin {
+            coin: Coin::from(output.coin),
+            note: note.clone(),
+            secret: secret_key,
+            nullifier: Nullifier::from(poseidon_hash([wallet.keypair.secret.inner(), note.serial])),
+            leaf_position,
+        };
+
+        Ok(oc)
+    }
+
+    pub async fn get_slot_checkpoint_by_slot(&self, slot: u64) -> Result<SlotCheckpoint> {
+        let faucet = self.holders.get(&Holder::Faucet).unwrap();
+        let slot_checkpoint =
+            faucet.state.read().await.blockchain.get_slot_checkpoints_by_slot(&[slot])?[0]
+                .clone()
+                .unwrap();
+
+        Ok(slot_checkpoint)
+    }
+
+    pub async fn generate_slot_checkpoint(&self, slot: u64) -> Result<SlotCheckpoint> {
+        // We grab the genesis slot to generate slot checkpoint
+        // using same consensus parameters
+        let faucet = self.holders.get(&Holder::Faucet).unwrap();
+        let genesis_block = faucet.state.read().await.consensus.genesis_block;
+        let fork_hashes = vec![genesis_block];
+        let fork_previous_hashes = vec![genesis_block];
+        let genesis_slot = self.get_slot_checkpoint_by_slot(0).await?;
+        let slot_checkpoint = SlotCheckpoint {
+            slot,
+            previous_eta: genesis_slot.previous_eta,
+            fork_hashes,
+            fork_previous_hashes,
+            sigma1: genesis_slot.sigma1,
+            sigma2: genesis_slot.sigma2,
+        };
+
+        // Store generated slot checkpoint
+        for wallet in self.holders.values() {
+            wallet.state.write().await.receive_slot_checkpoints(&[slot_checkpoint.clone()]).await?;
+        }
+
+        Ok(slot_checkpoint)
+    }
+
+    pub fn assert_trees(&self, holders: &[Holder]) {
+        assert!(holders.len() > 1);
+        // Gather wallets
+        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 consensus_stake_root = wallet.consensus_staked_merkle_tree.root(0).unwrap();
+        let consensus_unstake_root = wallet.consensus_unstaked_merkle_tree.root(0).unwrap();
+        for wallet in &wallets[1..] {
+            assert!(money_root == wallet.money_merkle_tree.root(0).unwrap());
+            assert!(consensus_stake_root == wallet.consensus_staked_merkle_tree.root(0).unwrap());
+            assert!(
+                consensus_unstake_root == wallet.consensus_unstaked_merkle_tree.root(0).unwrap()
+            );
+        }
+    }
+
+    pub fn statistics(&self) {
+        info!("==================== Statistics ====================");
+        for (action, tx_action_benchmark) in &self.tx_action_benchmarks {
+            tx_action_benchmark.statistics(action);
+        }
+        info!("====================================================");
+    }
 }