Răsfoiți Sursa

drk: refactor to support SMT

Additionaly, some cleanup was done, minor bugz fixed and updated DAO Propose call with the new setup and added its fee call.
skoupidi 2 ani în urmă
părinte
comite
96a8068ec3

+ 1 - 0
Cargo.lock

@@ -2667,6 +2667,7 @@ dependencies = [
  "easy-parallel",
  "lazy_static",
  "log",
+ "num-bigint",
  "prettytable-rs",
  "rand 0.8.5",
  "rodio",

+ 1 - 0
bin/darkfid/src/rpc.rs

@@ -60,6 +60,7 @@ impl RequestHandler for Darkfid {
             "blockchain.get_block" => self.blockchain_get_block(req.id, req.params).await,
             "blockchain.get_tx" => self.blockchain_get_tx(req.id, req.params).await,
             "blockchain.last_known_block" => self.blockchain_last_known_block(req.id, req.params).await,
+            "blockchain.best_fork_next_block_height" => self.blockchain_best_fork_next_block_height(req.id, req.params).await,
             "blockchain.lookup_zkas" => self.blockchain_lookup_zkas(req.id, req.params).await,
             "blockchain.subscribe_blocks" => self.blockchain_subscribe_blocks(req.id, req.params).await,
             "blockchain.subscribe_txs" =>  self.blockchain_subscribe_txs(req.id, req.params).await,

+ 31 - 3
bin/darkfid/src/rpc_blockchain.rs

@@ -117,16 +117,16 @@ impl Darkfid {
     }
 
     // RPCAPI:
-    // Queries the blockchain database to find the last known block
+    // Queries the blockchain database to find the last known block.
     //
     // **Params:**
     // * `None`
     //
     // **Returns:**
-    // * `u64` Height of the last known block, as string
+    // * `f64` Height of the last known block
     //
     // --> {"jsonrpc": "2.0", "method": "blockchain.last_known_block", "params": [], "id": 1}
-    // <-- {"jsonrpc": "2.0", "result": "1234", "id": 1}
+    // <-- {"jsonrpc": "2.0", "result": 1234, "id": 1}
     pub async fn blockchain_last_known_block(&self, id: u16, params: JsonValue) -> JsonResult {
         let params = params.get::<Vec<JsonValue>>().unwrap();
         if !params.is_empty() {
@@ -141,6 +141,34 @@ impl Darkfid {
         JsonResponse::new(JsonValue::Number(last_block_height.0 as f64), id).into()
     }
 
+    // RPCAPI:
+    // Queries the validator to find the current best fork next block height.
+    //
+    // **Params:**
+    // * `None`
+    //
+    // **Returns:**
+    // * `f64` Height of the last known block
+    //
+    // --> {"jsonrpc": "2.0", "method": "blockchain.best_fork_next_block_height", "params": [], "id": 1}
+    // <-- {"jsonrpc": "2.0", "result": 1234, "id": 1}
+    pub async fn blockchain_best_fork_next_block_height(
+        &self,
+        id: u16,
+        params: JsonValue,
+    ) -> JsonResult {
+        let params = params.get::<Vec<JsonValue>>().unwrap();
+        if !params.is_empty() {
+            return JsonError::new(InvalidParams, None, id).into()
+        }
+
+        let Ok(next_block_height) = self.validator.best_fork_next_block_height().await else {
+            return JsonError::new(InternalError, None, id).into()
+        };
+
+        JsonResponse::new(JsonValue::Number(next_block_height as f64), id).into()
+    }
+
     // RPCAPI:
     // Initializes a subscription to new incoming blocks.
     // Once a subscription is established, `darkfid` will send JSON-RPC notifications of

+ 21 - 3
bin/darkfid/src/task/garbage_collect.rs

@@ -24,7 +24,7 @@ use darkfi::{
     Error, Result,
 };
 use darkfi_sdk::crypto::MerkleTree;
-use log::{error, info};
+use log::{debug, error, info};
 
 use crate::Darkfid;
 
@@ -50,6 +50,7 @@ pub async fn garbage_collect_task(node: Arc<Darkfid>) -> Result<()> {
         for tx in txs {
             let tx_hash = tx.hash();
             let tx_vec = [tx.clone()];
+            let mut valid = false;
 
             // Grab a lock over current consensus forks state
             let mut forks = node.validator.consensus.forks.write().await;
@@ -108,15 +109,32 @@ pub async fn garbage_collect_task(node: Arc<Darkfid>) -> Result<()> {
                 )
                 .await
                 {
-                    Ok(_) => {}
+                    Ok(_) => valid = true,
                     Err(Error::TxVerifyFailed(TxVerifyFailed::ErroneousTxs(_))) => {
                         // Remove transaction from fork's mempool
                         fork.mempool.retain(|tx| *tx != tx_hash);
                     }
-                    Err(e) => return Err(e),
+                    Err(e) => {
+                        error!(
+                            target: "darkfid::task::garbage_collect_task",
+                            "Verifying transaction {tx_hash} failed: {e}"
+                        );
+                        return Err(e)
+                    }
                 }
             }
 
+            // Remove transaction if its invalid for all the forks
+            if !valid {
+                debug!(target: "darkfid::task::garbage_collect_task", "Removing invalid transaction: {tx_hash}");
+                if let Err(e) = node.validator.blockchain.remove_pending_txs_hashes(&[tx_hash]) {
+                    error!(
+                        target: "darkfid::task::garbage_collect_task",
+                        "Removing invalid transaction {tx_hash} failed: {e}"
+                    );
+                };
+            }
+
             // Drop forks lock
             drop(forks);
         }

+ 1 - 0
bin/drk/Cargo.toml

@@ -22,6 +22,7 @@ blake3 = "1.5.0"
 bs58 = "0.5.0"
 lazy_static = "1.4.0"
 log = "0.4.21"
+num-bigint = "0.4.4"
 prettytable-rs = "0.10.0"
 rand = "0.8.5"
 rodio = {version = "0.17.3", default-features = false, features = ["minimp3"]}

+ 6 - 0
bin/drk/money.sql

@@ -11,6 +11,12 @@ CREATE TABLE IF NOT EXISTS BZHKGQ26bzmBithTQYTJtjo2QdCqpkR9tjSBopT4yf4o_money_tr
 	tree BLOB NOT NULL
 );
 
+-- The Sparse Merkle tree containing coins nullifiers
+CREATE TABLE IF NOT EXISTS BZHKGQ26bzmBithTQYTJtjo2QdCqpkR9tjSBopT4yf4o_money_smt (
+	smt_key BLOB INTEGER PRIMARY KEY NOT NULL,
+	smt_value BLOB NOT NULL
+);
+
 -- The keypairs in our wallet
 CREATE TABLE IF NOT EXISTS BZHKGQ26bzmBithTQYTJtjo2QdCqpkR9tjSBopT4yf4o_money_keys (
 	key_id INTEGER PRIMARY KEY NOT NULL,

+ 21 - 13
bin/drk/src/cli_util.rs

@@ -192,9 +192,14 @@ pub fn generate_completions(shell: &str) -> Result<()> {
 
     let user_data = Arg::with_name("user-data").help("Optional user data to use");
 
-    let transfer = SubCommand::with_name("transfer")
-        .about("Create a payment transaction")
-        .args(&vec![amount, token, recipient, spend_hook.clone(), user_data.clone()]);
+    let transfer =
+        SubCommand::with_name("transfer").about("Create a payment transaction").args(&vec![
+            amount.clone(),
+            token.clone(),
+            recipient.clone(),
+            spend_hook.clone(),
+            user_data.clone(),
+        ]);
 
     // Otc
     let value_pair = Arg::with_name("value-pair")
@@ -286,16 +291,19 @@ pub fn generate_completions(shell: &str) -> Result<()> {
         .about("Mint an imported DAO on-chain")
         .args(&vec![name.clone()]);
 
-    let recipient =
-        Arg::with_name("recipient").help("Pubkey to send tokens to with proposal success");
+    let duration = Arg::with_name("duration").help("Duration of the proposal, in days");
 
-    let amount = Arg::with_name("amount").help("Amount to send from DAO with proposal success");
-
-    let token = Arg::with_name("token").help("Token ID to send from DAO with proposal success");
-
-    let propose = SubCommand::with_name("propose")
-        .about("Create a proposal for a DAO")
-        .args(&vec![name.clone(), recipient, amount, token]);
+    let propose_transfer = SubCommand::with_name("propose-transfer")
+        .about("Create a transfer proposal for a DAO")
+        .args(&vec![
+            name.clone(),
+            duration,
+            amount,
+            token,
+            recipient,
+            spend_hook.clone(),
+            user_data.clone(),
+        ]);
 
     let proposals =
         SubCommand::with_name("proposals").about("List DAO proposals").args(&vec![name.clone()]);
@@ -332,7 +340,7 @@ pub fn generate_completions(shell: &str) -> Result<()> {
         list,
         balance,
         mint,
-        propose,
+        propose_transfer,
         proposals,
         proposal,
         vote,

+ 227 - 195
bin/drk/src/dao.rs

@@ -24,12 +24,13 @@ use rusqlite::types::Value;
 
 use darkfi::{
     tx::{ContractCallLeaf, Transaction, TransactionBuilder},
-    util::parse::encode_base10,
+    util::parse::{decode_base10, encode_base10},
     zk::{empty_witnesses, halo2::Field, ProvingKey, ZkCircuit},
     zkas::ZkBinary,
     Error, Result,
 };
 use darkfi_dao_contract::{
+    blockwindow,
     client::{make_mint_call, DaoProposeCall, DaoProposeStakeInput, DaoVoteCall, DaoVoteInput},
     model::{Dao, DaoAuthCall, DaoBulla, DaoMintParams, DaoProposeParams, DaoVoteParams},
     DaoFunction, DAO_CONTRACT_ZKAS_DAO_MINT_NS, DAO_CONTRACT_ZKAS_DAO_PROPOSE_INPUT_NS,
@@ -37,7 +38,9 @@ use darkfi_dao_contract::{
     DAO_CONTRACT_ZKAS_DAO_VOTE_MAIN_NS,
 };
 use darkfi_money_contract::{
-    client::OwnCoin, model::TokenId, MoneyFunction, MONEY_CONTRACT_ZKAS_FEE_NS_V1,
+    client::OwnCoin,
+    model::{CoinAttributes, TokenId},
+    MoneyFunction, MONEY_CONTRACT_ZKAS_FEE_NS_V1,
 };
 use darkfi_sdk::{
     bridgetree,
@@ -60,7 +63,8 @@ use darkfi_serial::{
 use crate::{
     convert_named_params,
     error::{WalletDbError, WalletDbResult},
-    money::BALANCE_BASE10_DECIMALS,
+    money::{BALANCE_BASE10_DECIMALS, MONEY_SMT_COL_KEY, MONEY_SMT_COL_VALUE, MONEY_SMT_TABLE},
+    walletdb::{WalletSmt, WalletStorage},
     Drk,
 };
 
@@ -372,19 +376,14 @@ impl Drk {
     pub async fn initialize_dao(&self) -> WalletDbResult<()> {
         // Initialize DAO wallet schema
         let wallet_schema = include_str!("../dao.sql");
-        self.wallet.exec_batch_sql(wallet_schema).await?;
+        self.wallet.exec_batch_sql(wallet_schema)?;
 
         // Check if we have to initialize the Merkle trees.
         // We check if one exists, but we actually create two. This should be written
         // a bit better and safer.
         // For now, on success, we don't care what's returned, but in the future
         // we should actually check it.
-        if self
-            .wallet
-            .query_single(&DAO_TREES_TABLE, &[DAO_TREES_COL_DAOS_TREE], &[])
-            .await
-            .is_err()
-        {
+        if self.wallet.query_single(&DAO_TREES_TABLE, &[DAO_TREES_COL_DAOS_TREE], &[]).is_err() {
             println!("Initializing DAO Merkle trees");
             let tree = MerkleTree::new(1);
             self.put_dao_trees(&tree, &tree).await?;
@@ -402,27 +401,25 @@ impl Drk {
     ) -> WalletDbResult<()> {
         // First we remove old records
         let query = format!("DELETE FROM {};", *DAO_TREES_TABLE);
-        self.wallet.exec_sql(&query, &[]).await?;
+        self.wallet.exec_sql(&query, &[])?;
 
         // then we insert the new one
         let query = format!(
             "INSERT INTO {} ({}, {}) VALUES (?1, ?2);",
             *DAO_TREES_TABLE, DAO_TREES_COL_DAOS_TREE, DAO_TREES_COL_PROPOSALS_TREE,
         );
-        self.wallet
-            .exec_sql(
-                &query,
-                rusqlite::params![
-                    serialize_async(daos_tree).await,
-                    serialize_async(proposals_tree).await
-                ],
-            )
-            .await
+        self.wallet.exec_sql(
+            &query,
+            rusqlite::params![
+                serialize_async(daos_tree).await,
+                serialize_async(proposals_tree).await
+            ],
+        )
     }
 
     /// Fetch DAO Merkle trees from the wallet.
     pub async fn get_dao_trees(&self) -> Result<(MerkleTree, MerkleTree)> {
-        let row = match self.wallet.query_single(&DAO_TREES_TABLE, &[], &[]).await {
+        let row = match self.wallet.query_single(&DAO_TREES_TABLE, &[], &[]) {
             Ok(r) => r,
             Err(e) => {
                 return Err(Error::RusqliteError(format!(
@@ -507,7 +504,7 @@ impl Drk {
 
     /// Fetch all known DAOs from the wallet.
     pub async fn get_daos(&self) -> Result<Vec<DaoRecord>> {
-        let rows = match self.wallet.query_multiple(&DAO_DAOS_TABLE, &[], &[]).await {
+        let rows = match self.wallet.query_multiple(&DAO_DAOS_TABLE, &[], &[]) {
             Ok(r) => r,
             Err(e) => {
                 return Err(Error::RusqliteError(format!("[get_daos] DAOs retrieval failed: {e:?}")))
@@ -630,15 +627,11 @@ impl Drk {
             )))
         };
 
-        let rows = match self
-            .wallet
-            .query_multiple(
-                &DAO_PROPOSALS_TABLE,
-                &[],
-                convert_named_params! {(DAO_PROPOSALS_COL_DAO_NAME, name)},
-            )
-            .await
-        {
+        let rows = match self.wallet.query_multiple(
+            &DAO_PROPOSALS_TABLE,
+            &[],
+            convert_named_params! {(DAO_PROPOSALS_COL_DAO_NAME, name)},
+        ) {
             Ok(r) => r,
             Err(e) => {
                 return Err(Error::RusqliteError(format!(
@@ -854,23 +847,21 @@ impl Drk {
                 DAO_DAOS_COL_NAME,
                 dao.name,
             );
-            self.wallet
-                .exec_sql(
-                    &query,
-                    rusqlite::params![
-                        serialize_async(&dao.leaf_position.unwrap()).await,
-                        serialize_async(&dao.tx_hash.unwrap()).await,
-                        dao.call_index.unwrap()
-                    ],
-                )
-                .await?;
+            self.wallet.exec_sql(
+                &query,
+                rusqlite::params![
+                    serialize_async(&dao.leaf_position.unwrap()).await,
+                    serialize_async(&dao.tx_hash.unwrap()).await,
+                    dao.call_index.unwrap()
+                ],
+            )?;
         }
 
         Ok(())
     }
 
     /// Unconfirm imported DAOs by removing the leaf position, txid, and call index.
-    pub async fn unconfirm_daos(&self, daos: &[DaoRecord]) -> WalletDbResult<()> {
+    pub fn unconfirm_daos(&self, daos: &[DaoRecord]) -> WalletDbResult<()> {
         for dao in daos {
             let query = format!(
                 "UPDATE {} SET {} = ?1, {} = ?2, {} = ?3 WHERE {} = \'{}\';",
@@ -881,9 +872,10 @@ impl Drk {
                 DAO_DAOS_COL_NAME,
                 dao.name,
             );
-            self.wallet
-                .exec_sql(&query, rusqlite::params![None::<Vec<u8>>, None::<Vec<u8>>, None::<u64>,])
-                .await?;
+            self.wallet.exec_sql(
+                &query,
+                rusqlite::params![None::<Vec<u8>>, None::<Vec<u8>>, None::<u64>,],
+            )?;
         }
 
         Ok(())
@@ -914,24 +906,20 @@ impl Drk {
                 DAO_PROPOSALS_COL_CALL_INDEX,
             );
 
-            if let Err(e) = self
-                .wallet
-                .exec_sql(
-                    &query,
-                    rusqlite::params![
-                        dao.name,
-                        serialize_async(&proposal.recipient).await,
-                        serialize_async(&proposal.amount).await,
-                        serialize_async(&proposal.token_id).await,
-                        serialize_async(&proposal.bulla_blind).await,
-                        serialize_async(&proposal.leaf_position.unwrap()).await,
-                        serialize_async(&proposal.money_snapshot_tree.clone().unwrap()).await,
-                        serialize_async(&proposal.tx_hash.unwrap()).await,
-                        proposal.call_index,
-                    ],
-                )
-                .await
-            {
+            if let Err(e) = self.wallet.exec_sql(
+                &query,
+                rusqlite::params![
+                    dao.name,
+                    serialize_async(&proposal.recipient).await,
+                    serialize_async(&proposal.amount).await,
+                    serialize_async(&proposal.token_id).await,
+                    serialize_async(&proposal.bulla_blind).await,
+                    serialize_async(&proposal.leaf_position.unwrap()).await,
+                    serialize_async(&proposal.money_snapshot_tree.clone().unwrap()).await,
+                    serialize_async(&proposal.tx_hash.unwrap()).await,
+                    proposal.call_index,
+                ],
+            ) {
                 return Err(Error::RusqliteError(format!(
                     "[put_dao_proposals] Proposal insert failed: {e:?}"
                 )))
@@ -958,20 +946,18 @@ impl Drk {
                 DAO_VOTES_COL_CALL_INDEX,
             );
 
-            self.wallet
-                .exec_sql(
-                    &query,
-                    rusqlite::params![
-                        vote.proposal_id,
-                        vote.vote_option as u64,
-                        serialize_async(&vote.yes_vote_blind).await,
-                        serialize_async(&vote.all_vote_value).await,
-                        serialize_async(&vote.all_vote_blind).await,
-                        serialize_async(&vote.tx_hash.unwrap()).await,
-                        vote.call_index.unwrap(),
-                    ],
-                )
-                .await?;
+            self.wallet.exec_sql(
+                &query,
+                rusqlite::params![
+                    vote.proposal_id,
+                    vote.vote_option as u64,
+                    serialize_async(&vote.yes_vote_blind).await,
+                    serialize_async(&vote.all_vote_value).await,
+                    serialize_async(&vote.all_vote_blind).await,
+                    serialize_async(&vote.tx_hash.unwrap()).await,
+                    vote.call_index.unwrap(),
+                ],
+            )?;
 
             println!("DAO vote added to wallet");
         }
@@ -999,24 +985,24 @@ impl Drk {
                 return Err(WalletDbError::GenericError);
             }
         };
-        self.unconfirm_daos(&daos).await?;
+        self.unconfirm_daos(&daos)?;
         println!("Successfully unconfirmed DAOs");
 
         Ok(())
     }
 
     /// Reset all DAO proposals in the wallet.
-    pub async fn reset_dao_proposals(&self) -> WalletDbResult<()> {
+    pub fn reset_dao_proposals(&self) -> WalletDbResult<()> {
         println!("Resetting DAO proposals");
         let query = format!("DELETE FROM {};", *DAO_PROPOSALS_TABLE);
-        self.wallet.exec_sql(&query, &[]).await
+        self.wallet.exec_sql(&query, &[])
     }
 
     /// Reset all DAO votes in the wallet.
-    pub async fn reset_dao_votes(&self) -> WalletDbResult<()> {
+    pub fn reset_dao_votes(&self) -> WalletDbResult<()> {
         println!("Resetting DAO votes");
         let query = format!("DELETE FROM {};", *DAO_VOTES_TABLE);
-        self.wallet.exec_sql(&query, &[]).await
+        self.wallet.exec_sql(&query, &[])
     }
 
     /// Import given DAO params into the wallet with a given name.
@@ -1034,10 +1020,8 @@ impl Drk {
             "INSERT INTO {} ({}, {}) VALUES (?1, ?2);",
             *DAO_DAOS_TABLE, DAO_DAOS_COL_NAME, DAO_DAOS_COL_PARAMS,
         );
-        if let Err(e) = self
-            .wallet
-            .exec_sql(&query, rusqlite::params![name, serialize_async(&params).await,])
-            .await
+        if let Err(e) =
+            self.wallet.exec_sql(&query, rusqlite::params![name, serialize_async(&params).await,])
         {
             return Err(Error::RusqliteError(format!("[import_dao] DAO insert failed: {e:?}")))
         };
@@ -1047,11 +1031,11 @@ impl Drk {
 
     /// Fetch a DAO given its name.
     pub async fn get_dao_by_name(&self, name: &str) -> Result<DaoRecord> {
-        let row = match self
-            .wallet
-            .query_single(&DAO_DAOS_TABLE, &[], convert_named_params! {(DAO_DAOS_COL_NAME, name)})
-            .await
-        {
+        let row = match self.wallet.query_single(
+            &DAO_DAOS_TABLE,
+            &[],
+            convert_named_params! {(DAO_DAOS_COL_NAME, name)},
+        ) {
             Ok(r) => r,
             Err(e) => {
                 return Err(Error::RusqliteError(format!(
@@ -1111,15 +1095,11 @@ impl Drk {
     /// Fetch a DAO proposal by its ID
     pub async fn get_dao_proposal_by_id(&self, proposal_id: u64) -> Result<DaoProposal> {
         // Grab the proposal record
-        let row = match self
-            .wallet
-            .query_single(
-                &DAO_PROPOSALS_TABLE,
-                &[],
-                convert_named_params! {(DAO_PROPOSALS_COL_PROPOSAL_ID, proposal_id)},
-            )
-            .await
-        {
+        let row = match self.wallet.query_single(
+            &DAO_PROPOSALS_TABLE,
+            &[],
+            convert_named_params! {(DAO_PROPOSALS_COL_PROPOSAL_ID, proposal_id)},
+        ) {
             Ok(r) => r,
             Err(e) => {
                 return Err(Error::RusqliteError(format!(
@@ -1140,15 +1120,11 @@ impl Drk {
 
     // Fetch all known DAO proposal votes from the wallet given a proposal ID
     pub async fn get_dao_proposal_votes(&self, proposal_id: u64) -> Result<Vec<DaoVote>> {
-        let rows = match self
-            .wallet
-            .query_multiple(
-                &DAO_VOTES_TABLE,
-                &[],
-                convert_named_params! {(DAO_VOTES_COL_PROPOSAL_ID, proposal_id)},
-            )
-            .await
-        {
+        let rows = match self.wallet.query_multiple(
+            &DAO_VOTES_TABLE,
+            &[],
+            convert_named_params! {(DAO_VOTES_COL_PROPOSAL_ID, proposal_id)},
+        ) {
             Ok(r) => r,
             Err(e) => {
                 return Err(Error::RusqliteError(format!(
@@ -1323,89 +1299,120 @@ impl Drk {
         Ok(tx)
     }
 
-    /// Create a DAO proposal
-    pub async fn dao_propose(
+    /// Create a DAO transfer proposal.
+    #[allow(clippy::too_many_arguments)]
+    pub async fn dao_propose_transfer(
         &self,
         name: &str,
-        _recipient: PublicKey,
-        amount: u64,
+        duration_days: u64,
+        amount: &str,
         token_id: TokenId,
+        recipient: PublicKey,
+        spend_hook: Option<FuncId>,
+        user_data: Option<pallas::Base>,
     ) -> Result<Transaction> {
+        // Fetch DAO and check its deployed
         let dao = self.get_dao_by_name(name).await?;
         if dao.leaf_position.is_none() || dao.tx_hash.is_none() {
             return Err(Error::Custom(
-                "[dao_propose] DAO seems to not have been deployed yet".to_string(),
+                "[dao_propose_transfer] DAO seems to not have been deployed yet".to_string(),
             ))
         }
 
-        let bulla = dao.bulla();
-        let owncoins = self.get_coins(false).await?;
-
+        // Fetch DAO unspent OwnCoins to see what its balance is
         let dao_spend_hook =
             FuncRef { contract_id: *DAO_CONTRACT_ID, func_code: DaoFunction::Exec as u8 }
                 .to_func_id();
-
-        let mut dao_owncoins: Vec<OwnCoin> = owncoins.iter().map(|x| x.0.clone()).collect();
-        dao_owncoins.retain(|x| {
-            x.note.token_id == token_id &&
-                x.note.spend_hook == dao_spend_hook &&
-                x.note.user_data == bulla.inner()
-        });
-
-        let mut gov_owncoins: Vec<OwnCoin> = owncoins.iter().map(|x| x.0.clone()).collect();
-        gov_owncoins.retain(|x| x.note.token_id == dao.params.dao.gov_token_id);
-
+        let dao_bulla = dao.bulla();
+        let dao_owncoins =
+            self.get_contract_token_coins(&token_id, &dao_spend_hook, &dao_bulla.inner()).await?;
         if dao_owncoins.is_empty() {
             return Err(Error::Custom(format!(
-                "[dao_propose] Did not find any {token_id} coins owned by this DAO"
+                "[dao_propose_transfer] Did not find any {token_id} unspent coins owned by this DAO"
             )))
         }
 
-        if gov_owncoins.is_empty() {
+        // Check DAO balance is sufficient
+        let amount = decode_base10(amount, BALANCE_BASE10_DECIMALS, false)?;
+        if dao_owncoins.iter().map(|x| x.note.value).sum::<u64>() < amount {
             return Err(Error::Custom(format!(
-                "[dao_propose] Did not find any governance {} coins in wallet",
-                dao.params.dao.gov_token_id
+                "[dao_propose_transfer] Not enough DAO balance for token ID: {token_id}",
             )))
         }
 
-        if dao_owncoins.iter().map(|x| x.note.value).sum::<u64>() < amount {
+        // Fetch our own governance OwnCoins to see what our balance is
+        let gov_owncoins = self.get_token_coins(&dao.params.dao.gov_token_id).await?;
+        if gov_owncoins.is_empty() {
             return Err(Error::Custom(format!(
-                "[dao_propose] Not enough DAO balance for token ID: {}",
-                token_id
+                "[dao_propose_transfer] Did not find any governance {} coins in wallet",
+                dao.params.dao.gov_token_id
             )))
         }
 
-        if gov_owncoins.iter().map(|x| x.note.value).sum::<u64>() < dao.params.dao.proposer_limit {
+        // Find which governance coins we can use
+        let mut total_value = 0;
+        let mut gov_owncoins_to_use = vec![];
+        for gov_owncoin in gov_owncoins {
+            if total_value >= amount {
+                break
+            }
+
+            total_value += gov_owncoin.note.value;
+            gov_owncoins_to_use.push(gov_owncoin);
+        }
+
+        // Check our governance coins balance is sufficient
+        if total_value < amount {
             return Err(Error::Custom(format!(
-                "[dao_propose] Not enough gov token {} balance to propose",
+                "[dao_propose_transfer] Not enough gov token {} balance to propose",
                 dao.params.dao.gov_token_id
             )))
         }
 
-        // FIXME: Here we're looking for a coin == proposer_limit but this shouldn't have to
-        // be the case {
-        let Some(gov_coin) =
-            gov_owncoins.iter().find(|x| x.note.value == dao.params.dao.proposer_limit)
+        // Generate proposal coin attributes
+        let proposal_coinattrs = vec![CoinAttributes {
+            public_key: recipient,
+            value: amount,
+            token_id,
+            spend_hook: spend_hook.unwrap_or(FuncId::none()),
+            user_data: user_data.unwrap_or(pallas::Base::ZERO),
+            blind: Blind::random(&mut OsRng),
+        }];
+
+        // Now we need to do a lookup for the zkas proof bincodes, and create
+        // the circuit objects and proving keys so we can build the transaction.
+        // We also do this through the RPC. First we grab the fee call from money.
+        let zkas_bins = self.lookup_zkas(&MONEY_CONTRACT_ID).await?;
+
+        let Some(fee_zkbin) = zkas_bins.iter().find(|x| x.0 == MONEY_CONTRACT_ZKAS_FEE_NS_V1)
         else {
-            return Err(Error::Custom(format!(
-                "[dao_propose] Did not find a single gov coin of value {}",
-                dao.params.dao.proposer_limit
-            )))
+            return Err(Error::Custom("[dao_propose_transfer] Fee circuit not found".to_string()))
         };
-        // }
 
-        // Lookup the zkas bins
+        let fee_zkbin = ZkBinary::decode(&fee_zkbin.1)?;
+
+        let fee_circuit = ZkCircuit::new(empty_witnesses(&fee_zkbin)?, &fee_zkbin);
+
+        // Creating Fee circuit proving key
+        let fee_pk = ProvingKey::build(fee_zkbin.k, &fee_circuit);
+
+        // Now we grab the DAO bins
         let zkas_bins = self.lookup_zkas(&DAO_CONTRACT_ID).await?;
+
         let Some(propose_burn_zkbin) =
             zkas_bins.iter().find(|x| x.0 == DAO_CONTRACT_ZKAS_DAO_PROPOSE_INPUT_NS)
         else {
-            return Err(Error::Custom("[dao_propose] Propose Burn circuit not found".to_string()))
+            return Err(Error::Custom(
+                "[dao_propose_transfer] Propose Burn circuit not found".to_string(),
+            ))
         };
 
         let Some(propose_main_zkbin) =
             zkas_bins.iter().find(|x| x.0 == DAO_CONTRACT_ZKAS_DAO_PROPOSE_MAIN_NS)
         else {
-            return Err(Error::Custom("[dao_propose] Propose Main circuit not found".to_string()))
+            return Err(Error::Custom(
+                "[dao_propose_transfer] Propose Main circuit not found".to_string(),
+            ))
         };
 
         let propose_burn_zkbin = ZkBinary::decode(&propose_burn_zkbin.1)?;
@@ -1416,55 +1423,36 @@ impl Drk {
         let propose_main_circuit =
             ZkCircuit::new(empty_witnesses(&propose_main_zkbin)?, &propose_main_zkbin);
 
-        println!("Creating Propose Burn circuit proving key");
+        // Creating DAO ProposeBurn and ProposeMain circuits proving keys
         let propose_burn_pk = ProvingKey::build(propose_burn_zkbin.k, &propose_burn_circuit);
-        println!("Creating Propose Main circuit proving key");
         let propose_main_pk = ProvingKey::build(propose_main_zkbin.k, &propose_main_circuit);
 
-        // Now create the parameters for the proposal tx
-        let signature_secret = SecretKey::random(&mut OsRng);
-
-        // Get the Merkle path for the gov coin in the money tree
+        // Fetch our money Merkle tree
         let money_merkle_tree = self.get_money_tree().await?;
-        let gov_coin_merkle_path = money_merkle_tree.witness(gov_coin.leaf_position, 0).unwrap();
 
-        // Fetch the daos Merkle tree
-        let (daos_tree, _) = self.get_dao_trees().await?;
-
-        // This is complete wrong and needs to be fixed later
-        // Non-trivial to fix, so we just make a workaround for now
-
-        let hasher = PoseidonFp::new();
-        let store = MemoryStorageFp::new();
-        let money_null_smt = SmtMemoryFp::new(store, hasher.clone(), &EMPTY_NODES_FP);
-
-        let input = DaoProposeStakeInput {
-            secret: gov_coin.secret, // <-- TODO: Is this correct?
-            note: gov_coin.note.clone(),
-            leaf_position: gov_coin.leaf_position,
-            merkle_path: gov_coin_merkle_path,
-            money_null_smt: &money_null_smt,
-            signature_secret,
-        };
-
-        let (dao_merkle_path, dao_merkle_root) = {
-            let root = daos_tree.root(0).unwrap();
-            let leaf_pos = dao.leaf_position.unwrap();
-            let dao_merkle_path = daos_tree.witness(leaf_pos, 0).unwrap();
-            (dao_merkle_path, root)
-        };
+        // Now we can create the proposal transaction parameters.
+        // We first generate the `DaoProposeStakeInput` inputs,
+        // using our governance OwnCoins.
+        let mut inputs = Vec::with_capacity(gov_owncoins_to_use.len());
+        for gov_owncoin in gov_owncoins_to_use {
+            let input = DaoProposeStakeInput {
+                secret: gov_owncoin.secret,
+                note: gov_owncoin.note.clone(),
+                leaf_position: gov_owncoin.leaf_position,
+                merkle_path: money_merkle_tree.witness(gov_owncoin.leaf_position, 0).unwrap(),
+            };
+            inputs.push(input);
+        }
 
-        // TODO:
-        /*
         // Convert coin_params to actual coins
         let mut proposal_coins = vec![];
         for coin_params in proposal_coinattrs {
             proposal_coins.push(coin_params.to_coin());
         }
-        */
-        let proposal_data = vec![];
-        //proposal_coins.encode_async(&mut proposal_data).await.unwrap();
+        let mut proposal_data = vec![];
+        proposal_coins.encode_async(&mut proposal_data).await?;
 
+        // Create Auth calls
         let auth_calls = vec![
             DaoAuthCall {
                 contract_id: *DAO_CONTRACT_ID,
@@ -1478,31 +1466,55 @@ impl Drk {
             },
         ];
 
-        // TODO: get current height to calculate day
-        // Also contract must check we don't mint a proposal that its creation day is
-        // less than current height
+        // Retrieve current block height and compute current day
+        let current_block_height = self.get_next_block_height().await?;
+        let creation_day = blockwindow(current_block_height);
 
+        // Create the actual proposal
         // TODO: Simplify this model struct import once
         // we use the structs from contract everwhere
         let proposal = darkfi_dao_contract::model::DaoProposal {
             auth_calls,
-            creation_day: 0,
-            duration_days: 30,
-            user_data: pallas::Base::ZERO,
-            dao_bulla: dao.bulla(),
+            creation_day,
+            duration_days,
+            user_data: user_data.unwrap_or(pallas::Base::ZERO),
+            dao_bulla,
             blind: Blind::random(&mut OsRng),
         };
 
+        // Now create the parameters for the proposal tx
+        let signature_secret = SecretKey::random(&mut OsRng);
+
+        // Fetch the daos Merkle tree to compute the DAO Merkle path and root
+        let (daos_tree, _) = self.get_dao_trees().await?;
+        let (dao_merkle_path, dao_merkle_root) = {
+            let root = daos_tree.root(0).unwrap();
+            let leaf_pos = dao.leaf_position.unwrap();
+            let dao_merkle_path = daos_tree.witness(leaf_pos, 0).unwrap();
+            (dao_merkle_path, root)
+        };
+
+        // Generate the Money nullifiers Sparse Merkle Tree
+        let store = WalletStorage::new(
+            &self.wallet,
+            &MONEY_SMT_TABLE,
+            MONEY_SMT_COL_KEY,
+            MONEY_SMT_COL_VALUE,
+        );
+        let money_null_smt = WalletSmt::new(store, PoseidonFp::new(), &EMPTY_NODES_FP);
+
+        // Create the proposal call
         let call = DaoProposeCall {
-            inputs: vec![input],
+            money_null_smt: &money_null_smt,
+            inputs,
             proposal,
             dao: dao.params.dao,
             dao_leaf_position: dao.leaf_position.unwrap(),
             dao_merkle_path,
             dao_merkle_root,
+            signature_secret,
         };
 
-        println!("Creating ZK proofs...");
         let (params, proofs) = call.make(
             &propose_burn_zkbin,
             &propose_burn_pk,
@@ -1510,14 +1522,34 @@ impl Drk {
             &propose_main_pk,
         )?;
 
+        // Encode the call
         let mut data = vec![DaoFunction::Propose as u8];
         params.encode_async(&mut data).await?;
         let call = ContractCall { contract_id: *DAO_CONTRACT_ID, data };
+
+        // Create the TransactionBuilder containing above call
         let mut tx_builder = TransactionBuilder::new(ContractCallLeaf { call, proofs }, vec![])?;
+
+        // We first have to execute the fee-less tx to gather its used gas, and then we feed
+        // it into the fee-creating function.
         let mut tx = tx_builder.build()?;
         let sigs = tx.create_sigs(&[signature_secret])?;
         tx.signatures = vec![sigs];
 
+        let tree = self.get_money_tree().await?;
+        let (fee_call, fee_proofs, fee_secrets) =
+            self.append_fee_call(&tx, &tree, &fee_pk, &fee_zkbin, None).await?;
+
+        // Append the fee call to the transaction
+        tx_builder.append(ContractCallLeaf { call: fee_call, proofs: fee_proofs }, vec![])?;
+
+        // Now build the actual transaction and sign it with all necessary keys.
+        let mut tx = tx_builder.build()?;
+        let sigs = tx.create_sigs(&[signature_secret])?;
+        tx.signatures.push(sigs);
+        let sigs = tx.create_sigs(&fee_secrets)?;
+        tx.signatures.push(sigs);
+
         Ok(tx)
     }
 

+ 9 - 13
bin/drk/src/deploy.rs

@@ -50,10 +50,10 @@ pub const DEPLOY_AUTH_COL_IS_FROZEN: &str = "is_frozen";
 
 impl Drk {
     /// Initialize wallet with tables for the Deployooor contract.
-    pub async fn initialize_deployooor(&self) -> WalletDbResult<()> {
+    pub fn initialize_deployooor(&self) -> WalletDbResult<()> {
         // Initialize Deployooor wallet schema
         let wallet_schema = include_str!("../deploy.sql");
-        self.wallet.exec_batch_sql(wallet_schema).await?;
+        self.wallet.exec_batch_sql(wallet_schema)?;
 
         Ok(())
     }
@@ -68,7 +68,7 @@ impl Drk {
             "INSERT INTO {} ({}, {}) VALUES (?1, ?2);",
             *DEPLOY_AUTH_TABLE, DEPLOY_AUTH_COL_DEPLOY_AUTHORITY, DEPLOY_AUTH_COL_IS_FROZEN,
         );
-        self.wallet.exec_sql(&query, rusqlite::params![serialize_async(&keypair).await, 0]).await?;
+        self.wallet.exec_sql(&query, rusqlite::params![serialize_async(&keypair).await, 0])?;
 
         eprintln!("Created new contract deploy authority");
         println!("Contract ID: {}", ContractId::derive_public(keypair.public));
@@ -78,7 +78,7 @@ impl Drk {
 
     /// List contract deploy authorities from the wallet
     pub async fn list_deploy_auth(&self) -> Result<Vec<(i64, ContractId, bool)>> {
-        let rows = match self.wallet.query_multiple(&DEPLOY_AUTH_TABLE, &[], &[]).await {
+        let rows = match self.wallet.query_multiple(&DEPLOY_AUTH_TABLE, &[], &[]) {
             Ok(r) => r,
             Err(e) => {
                 return Err(Error::RusqliteError(format!(
@@ -111,15 +111,11 @@ impl Drk {
     /// Retrieve a deploy authority keypair given an index
     async fn get_deploy_auth(&self, idx: u64) -> Result<Keypair> {
         // Find the deploy authority keypair
-        let row = match self
-            .wallet
-            .query_single(
-                &DEPLOY_AUTH_TABLE,
-                &[DEPLOY_AUTH_COL_DEPLOY_AUTHORITY],
-                convert_named_params! {(DEPLOY_AUTH_COL_ID, idx)},
-            )
-            .await
-        {
+        let row = match self.wallet.query_single(
+            &DEPLOY_AUTH_TABLE,
+            &[DEPLOY_AUTH_COL_DEPLOY_AUTHORITY],
+            convert_named_params! {(DEPLOY_AUTH_COL_ID, idx)},
+        ) {
             Ok(v) => v,
             Err(e) => {
                 return Err(Error::RusqliteError(format!(

+ 1 - 0
bin/drk/src/error.rs

@@ -25,6 +25,7 @@ pub type WalletDbResult<T> = std::result::Result<T, WalletDbError>;
 pub enum WalletDbError {
     // Connection related errors
     ConnectionFailed = -32100,
+    FailedToAquireLock = -32101,
 
     // Configuration related errors
     PragmaUpdateError = -32110,

+ 74 - 18
bin/drk/src/main.rs

@@ -352,19 +352,28 @@ enum DaoSubcmd {
         name: String,
     },
 
-    /// Create a proposal for a DAO
-    Propose {
+    /// Create a transfer proposal for a DAO
+    ProposeTransfer {
         /// Name identifier for the DAO
         name: String,
 
-        /// Pubkey to send tokens to with proposal success
-        recipient: String,
+        /// Duration of the proposal, in days
+        duration: u64,
 
-        /// Amount to send from DAO with proposal success
+        /// Amount to send
         amount: String,
 
-        /// Token ID to send from DAO with proposal success
+        /// Token ID to send
         token: String,
+
+        /// Recipient address
+        recipient: String,
+
+        /// Optional contract spend hook to use
+        spend_hook: Option<String>,
+
+        /// Optional user data to use
+        user_data: Option<String>,
     },
 
     /// List DAO proposals
@@ -586,9 +595,9 @@ impl Drk {
     }
 
     /// Initialize wallet with tables for drk
-    async fn initialize_wallet(&self) -> Result<()> {
+    fn initialize_wallet(&self) -> Result<()> {
         let wallet_schema = include_str!("../wallet.sql");
-        if let Err(e) = self.wallet.exec_batch_sql(wallet_schema).await {
+        if let Err(e) = self.wallet.exec_batch_sql(wallet_schema) {
             eprintln!("Error initializing wallet: {e:?}");
             exit(2);
         }
@@ -655,7 +664,7 @@ async fn realmain(args: Args, ex: Arc<smol::Executor<'static>>) -> Result<()> {
             let drk = Drk::new(args.wallet_path, args.wallet_pass, None, ex).await?;
 
             if initialize {
-                drk.initialize_wallet().await?;
+                drk.initialize_wallet()?;
                 if let Err(e) = drk.initialize_money().await {
                     eprintln!("Failed to initialize Money: {e:?}");
                     exit(2);
@@ -664,7 +673,7 @@ async fn realmain(args: Args, ex: Arc<smol::Executor<'static>>) -> Result<()> {
                     eprintln!("Failed to initialize DAO: {e:?}");
                     exit(2);
                 }
-                if let Err(e) = drk.initialize_deployooor().await {
+                if let Err(e) = drk.initialize_deployooor() {
                     eprintln!("Failed to initialize Deployooor: {e:?}");
                     exit(2);
                 }
@@ -749,7 +758,7 @@ async fn realmain(args: Args, ex: Arc<smol::Executor<'static>>) -> Result<()> {
             }
 
             if let Some(idx) = default_address {
-                if let Err(e) = drk.set_default_address(idx).await {
+                if let Err(e) = drk.set_default_address(idx) {
                     eprintln!("Failed to set default address: {e:?}");
                     exit(2);
                 }
@@ -1197,12 +1206,23 @@ async fn realmain(args: Args, ex: Arc<smol::Executor<'static>>) -> Result<()> {
                 Ok(())
             }
 
-            DaoSubcmd::Propose { name, recipient, amount, token } => {
+            DaoSubcmd::ProposeTransfer {
+                name,
+                duration,
+                amount,
+                token,
+                recipient,
+                spend_hook,
+                user_data,
+            } => {
+                let drk =
+                    Drk::new(args.wallet_path, args.wallet_pass, Some(args.endpoint), ex).await?;
+
                 if let Err(e) = f64::from_str(&amount) {
                     eprintln!("Invalid amount: {e:?}");
                     exit(2);
                 }
-                let amount = decode_base10(&amount, BALANCE_BASE10_DECIMALS, true)?;
+
                 let rcpt = match PublicKey::from_str(&recipient) {
                     Ok(r) => r,
                     Err(e) => {
@@ -1211,8 +1231,6 @@ async fn realmain(args: Args, ex: Arc<smol::Executor<'static>>) -> Result<()> {
                     }
                 };
 
-                let drk =
-                    Drk::new(args.wallet_path, args.wallet_pass, Some(args.endpoint), ex).await?;
                 let token_id = match drk.get_token(token).await {
                     Ok(t) => t,
                     Err(e) => {
@@ -1221,13 +1239,51 @@ async fn realmain(args: Args, ex: Arc<smol::Executor<'static>>) -> Result<()> {
                     }
                 };
 
-                let tx = match drk.dao_propose(&name, rcpt, amount, token_id).await {
+                let spend_hook = match spend_hook {
+                    Some(s) => match FuncId::from_str(&s) {
+                        Ok(s) => Some(s),
+                        Err(e) => {
+                            eprintln!("Invalid spend hook: {e:?}");
+                            exit(2);
+                        }
+                    },
+                    None => None,
+                };
+
+                let user_data = match user_data {
+                    Some(u) => {
+                        let bytes: [u8; 32] = match bs58::decode(&u).into_vec()?.try_into() {
+                            Ok(b) => b,
+                            Err(e) => {
+                                eprintln!("Invalid user data: {e:?}");
+                                exit(2);
+                            }
+                        };
+
+                        match pallas::Base::from_repr(bytes).into() {
+                            Some(v) => Some(v),
+                            None => {
+                                eprintln!("Invalid user data");
+                                exit(2);
+                            }
+                        }
+                    }
+                    None => None,
+                };
+
+                let tx = match drk
+                    .dao_propose_transfer(
+                        &name, duration, &amount, token_id, rcpt, spend_hook, user_data,
+                    )
+                    .await
+                {
                     Ok(tx) => tx,
                     Err(e) => {
-                        eprintln!("Failed to create DAO proposal: {e:?}");
+                        eprintln!("Failed to create DAO transfer proposal: {e:?}");
                         exit(2);
                     }
                 };
+
                 println!("{}", base64::encode(&serialize_async(&tx).await));
                 Ok(())
             }
@@ -1498,7 +1554,7 @@ async fn realmain(args: Args, ex: Arc<smol::Executor<'static>>) -> Result<()> {
                     return Ok(())
                 }
 
-                let map = match drk.get_txs_history().await {
+                let map = match drk.get_txs_history() {
                     Ok(m) => m,
                     Err(e) => {
                         eprintln!("Failed to retrieve transactions history records: {e:?}");

+ 150 - 108
bin/drk/src/money.rs

@@ -44,8 +44,10 @@ use darkfi_money_contract::{
 use darkfi_sdk::{
     bridgetree,
     crypto::{
-        note::AeadEncryptedNote, BaseBlind, FuncId, Keypair, MerkleNode, MerkleTree, PublicKey,
-        ScalarBlind, SecretKey, MONEY_CONTRACT_ID,
+        note::AeadEncryptedNote,
+        smt::{PoseidonFp, EMPTY_NODES_FP},
+        BaseBlind, FuncId, Keypair, MerkleNode, MerkleTree, PublicKey, ScalarBlind, SecretKey,
+        MONEY_CONTRACT_ID,
     },
     dark_tree::DarkLeaf,
     pasta::pallas,
@@ -56,7 +58,9 @@ use darkfi_serial::{deserialize_async, serialize_async, AsyncEncodable};
 use crate::{
     convert_named_params,
     error::{WalletDbError, WalletDbResult},
-    kaching, Drk,
+    kaching,
+    walletdb::{WalletSmt, WalletStorage},
+    Drk,
 };
 
 // Wallet SQL table constant names. These have to represent the `wallet.sql`
@@ -66,6 +70,7 @@ lazy_static! {
         format!("{}_money_info", MONEY_CONTRACT_ID.to_string());
     pub static ref MONEY_TREE_TABLE: String =
         format!("{}_money_tree", MONEY_CONTRACT_ID.to_string());
+    pub static ref MONEY_SMT_TABLE: String = format!("{}_money_smt", MONEY_CONTRACT_ID.to_string());
     pub static ref MONEY_KEYS_TABLE: String =
         format!("{}_money_keys", MONEY_CONTRACT_ID.to_string());
     pub static ref MONEY_COINS_TABLE: String =
@@ -82,6 +87,10 @@ pub const MONEY_INFO_COL_LAST_SCANNED_BLOCK: &str = "last_scanned_block";
 // MONEY_TREE_TABLE
 pub const MONEY_TREE_COL_TREE: &str = "tree";
 
+// MONEY_SMT_TABLE
+pub const MONEY_SMT_COL_KEY: &str = "smt_key";
+pub const MONEY_SMT_COL_VALUE: &str = "smt_value";
+
 // MONEY_KEYS_TABLE
 pub const MONEY_KEYS_COL_KEY_ID: &str = "key_id";
 pub const MONEY_KEYS_COL_IS_DEFAULT: &str = "is_default";
@@ -120,7 +129,7 @@ impl Drk {
     pub async fn initialize_money(&self) -> WalletDbResult<()> {
         // Initialize Money wallet schema
         let wallet_schema = include_str!("../money.sql");
-        self.wallet.exec_batch_sql(wallet_schema).await?;
+        self.wallet.exec_batch_sql(wallet_schema)?;
 
         // Check if we have to initialize the Merkle tree.
         // We check if we find a row in the tree table, and if not, we create a
@@ -138,12 +147,12 @@ impl Drk {
 
         // We maintain the last scanned block as part of the Money contract,
         // but at this moment it is also somewhat applicable to DAO scans.
-        if self.last_scanned_block().await.is_err() {
+        if self.last_scanned_block().is_err() {
             let query = format!(
                 "INSERT INTO {} ({}) VALUES (?1);",
                 *MONEY_INFO_TABLE, MONEY_INFO_COL_LAST_SCANNED_BLOCK
             );
-            self.wallet.exec_sql(&query, rusqlite::params![0]).await?;
+            self.wallet.exec_sql(&query, rusqlite::params![0])?;
         }
 
         // Insert DRK alias
@@ -167,16 +176,14 @@ impl Drk {
             MONEY_KEYS_COL_PUBLIC,
             MONEY_KEYS_COL_SECRET
         );
-        self.wallet
-            .exec_sql(
-                &query,
-                rusqlite::params![
-                    is_default,
-                    serialize_async(&keypair.public).await,
-                    serialize_async(&keypair.secret).await
-                ],
-            )
-            .await?;
+        self.wallet.exec_sql(
+            &query,
+            rusqlite::params![
+                is_default,
+                serialize_async(&keypair.public).await,
+                serialize_async(&keypair.secret).await
+            ],
+        )?;
 
         println!("New address:");
         println!("{}", keypair.public);
@@ -186,15 +193,11 @@ impl Drk {
 
     /// Fetch default secret key from the wallet.
     pub async fn default_secret(&self) -> Result<SecretKey> {
-        let row = match self
-            .wallet
-            .query_single(
-                &MONEY_KEYS_TABLE,
-                &[MONEY_KEYS_COL_SECRET],
-                convert_named_params! {(MONEY_KEYS_COL_IS_DEFAULT, 1)},
-            )
-            .await
-        {
+        let row = match self.wallet.query_single(
+            &MONEY_KEYS_TABLE,
+            &[MONEY_KEYS_COL_SECRET],
+            convert_named_params! {(MONEY_KEYS_COL_IS_DEFAULT, 1)},
+        ) {
             Ok(r) => r,
             Err(e) => {
                 return Err(Error::RusqliteError(format!(
@@ -213,15 +216,11 @@ impl Drk {
 
     /// Fetch default pubkey from the wallet.
     pub async fn default_address(&self) -> Result<PublicKey> {
-        let row = match self
-            .wallet
-            .query_single(
-                &MONEY_KEYS_TABLE,
-                &[MONEY_KEYS_COL_PUBLIC],
-                convert_named_params! {(MONEY_KEYS_COL_IS_DEFAULT, 1)},
-            )
-            .await
-        {
+        let row = match self.wallet.query_single(
+            &MONEY_KEYS_TABLE,
+            &[MONEY_KEYS_COL_PUBLIC],
+            convert_named_params! {(MONEY_KEYS_COL_IS_DEFAULT, 1)},
+        ) {
             Ok(r) => r,
             Err(e) => {
                 return Err(Error::RusqliteError(format!(
@@ -239,11 +238,11 @@ impl Drk {
     }
 
     /// Set provided index address as default in the wallet.
-    pub async fn set_default_address(&self, idx: usize) -> WalletDbResult<()> {
+    pub fn set_default_address(&self, idx: usize) -> WalletDbResult<()> {
         // First we update previous default record
         let is_default = 0;
         let query = format!("UPDATE {} SET {} = ?1", *MONEY_KEYS_TABLE, MONEY_KEYS_COL_IS_DEFAULT,);
-        self.wallet.exec_sql(&query, rusqlite::params![is_default]).await?;
+        self.wallet.exec_sql(&query, rusqlite::params![is_default])?;
 
         // and then we set the new one
         let is_default = 1;
@@ -251,12 +250,12 @@ impl Drk {
             "UPDATE {} SET {} = ?1 WHERE {} = ?2",
             *MONEY_KEYS_TABLE, MONEY_KEYS_COL_IS_DEFAULT, MONEY_KEYS_COL_KEY_ID,
         );
-        self.wallet.exec_sql(&query, rusqlite::params![is_default, idx]).await
+        self.wallet.exec_sql(&query, rusqlite::params![is_default, idx])
     }
 
     /// Fetch all pukeys from the wallet.
     pub async fn addresses(&self) -> Result<Vec<(u64, PublicKey, SecretKey, u64)>> {
-        let rows = match self.wallet.query_multiple(&MONEY_KEYS_TABLE, &[], &[]).await {
+        let rows = match self.wallet.query_multiple(&MONEY_KEYS_TABLE, &[], &[]) {
             Ok(r) => r,
             Err(e) => {
                 return Err(Error::RusqliteError(format!(
@@ -299,18 +298,15 @@ impl Drk {
 
     /// Fetch all secret keys from the wallet.
     pub async fn get_money_secrets(&self) -> Result<Vec<SecretKey>> {
-        let rows = match self
-            .wallet
-            .query_multiple(&MONEY_KEYS_TABLE, &[MONEY_KEYS_COL_SECRET], &[])
-            .await
-        {
-            Ok(r) => r,
-            Err(e) => {
-                return Err(Error::RusqliteError(format!(
-                    "[get_money_secrets] Secret keys retrieval failed: {e:?}"
-                )))
-            }
-        };
+        let rows =
+            match self.wallet.query_multiple(&MONEY_KEYS_TABLE, &[MONEY_KEYS_COL_SECRET], &[]) {
+                Ok(r) => r,
+                Err(e) => {
+                    return Err(Error::RusqliteError(format!(
+                        "[get_money_secrets] Secret keys retrieval failed: {e:?}"
+                    )))
+                }
+            };
 
         let mut secrets = Vec::with_capacity(rows.len());
 
@@ -356,7 +352,7 @@ impl Drk {
                 MONEY_KEYS_COL_SECRET
             );
             if let Err(e) =
-                self.wallet.exec_sql(&query, rusqlite::params![is_default, public, secret]).await
+                self.wallet.exec_sql(&query, rusqlite::params![is_default, public, secret])
             {
                 return Err(Error::RusqliteError(format!(
                     "[import_money_secrets] Inserting new address failed: {e:?}"
@@ -393,15 +389,13 @@ impl Drk {
     /// The boolean in the returned tuple notes if the coin was marked as spent.
     pub async fn get_coins(&self, fetch_spent: bool) -> Result<Vec<(OwnCoin, bool, String)>> {
         let query = if fetch_spent {
-            self.wallet.query_multiple(&MONEY_COINS_TABLE, &[], &[]).await
+            self.wallet.query_multiple(&MONEY_COINS_TABLE, &[], &[])
         } else {
-            self.wallet
-                .query_multiple(
-                    &MONEY_COINS_TABLE,
-                    &[],
-                    convert_named_params! {(MONEY_COINS_COL_IS_SPENT, false)},
-                )
-                .await
+            self.wallet.query_multiple(
+                &MONEY_COINS_TABLE,
+                &[],
+                convert_named_params! {(MONEY_COINS_COL_IS_SPENT, false)},
+            )
         };
 
         let rows = match query {
@@ -427,8 +421,7 @@ impl Drk {
             &MONEY_COINS_TABLE,
             &[],
             convert_named_params! {(MONEY_COINS_COL_IS_SPENT, false), (MONEY_COINS_COL_TOKEN_ID, serialize_async(token_id).await), (MONEY_COINS_COL_SPEND_HOOK, serialize_async(&FuncId::none()).await)},
-        )
-        .await;
+        );
 
         let rows = match query {
             Ok(r) => r,
@@ -447,6 +440,36 @@ impl Drk {
         Ok(owncoins)
     }
 
+    /// Fetch provided contract specified token unspend balances from the wallet.
+    pub async fn get_contract_token_coins(
+        &self,
+        token_id: &TokenId,
+        spend_hook: &FuncId,
+        user_data: &pallas::Base,
+    ) -> Result<Vec<OwnCoin>> {
+        let query = self.wallet.query_multiple(
+            &MONEY_COINS_TABLE,
+            &[],
+            convert_named_params! {(MONEY_COINS_COL_IS_SPENT, false), (MONEY_COINS_COL_TOKEN_ID, serialize_async(token_id).await), (MONEY_COINS_COL_SPEND_HOOK, serialize_async(spend_hook).await), (MONEY_COINS_COL_USER_DATA, serialize_async(user_data).await)},
+        );
+
+        let rows = match query {
+            Ok(r) => r,
+            Err(e) => {
+                return Err(Error::RusqliteError(format!(
+                    "[get_contract_token_coins] Coins retrieval failed: {e:?}"
+                )))
+            }
+        };
+
+        let mut owncoins = Vec::with_capacity(rows.len());
+        for row in rows {
+            owncoins.push(self.parse_coin_record(&row).await?.0)
+        }
+
+        Ok(owncoins)
+    }
+
     /// Auxiliary function to parse a `MONEY_COINS_TABLE` record.
     /// The boolean in the returned tuple notes if the coin was marked as spent.
     async fn parse_coin_record(&self, row: &[Value]) -> Result<(OwnCoin, bool, String)> {
@@ -539,12 +562,10 @@ impl Drk {
             "INSERT OR REPLACE INTO {} ({}, {}) VALUES (?1, ?2);",
             *MONEY_ALIASES_TABLE, MONEY_ALIASES_COL_ALIAS, MONEY_ALIASES_COL_TOKEN_ID,
         );
-        self.wallet
-            .exec_sql(
-                &query,
-                rusqlite::params![serialize_async(&alias).await, serialize_async(&token_id).await],
-            )
-            .await
+        self.wallet.exec_sql(
+            &query,
+            rusqlite::params![serialize_async(&alias).await, serialize_async(&token_id).await],
+        )
     }
 
     /// Fetch all aliases from the wallet.
@@ -554,7 +575,7 @@ impl Drk {
         alias_filter: Option<String>,
         token_id_filter: Option<TokenId>,
     ) -> Result<HashMap<String, TokenId>> {
-        let rows = match self.wallet.query_multiple(&MONEY_ALIASES_TABLE, &[], &[]).await {
+        let rows = match self.wallet.query_multiple(&MONEY_ALIASES_TABLE, &[], &[]) {
             Ok(r) => r,
             Err(e) => {
                 return Err(Error::RusqliteError(format!(
@@ -612,7 +633,7 @@ impl Drk {
             "DELETE FROM {} WHERE {} = ?1;",
             *MONEY_ALIASES_TABLE, MONEY_ALIASES_COL_ALIAS,
         );
-        self.wallet.exec_sql(&query, rusqlite::params![serialize_async(&alias).await]).await
+        self.wallet.exec_sql(&query, rusqlite::params![serialize_async(&alias).await])
     }
 
     /// Mark a given coin in the wallet as unspent.
@@ -625,37 +646,34 @@ impl Drk {
             MONEY_COINS_COL_SPENT_TX_HASH,
             MONEY_COINS_COL_COIN
         );
-        self.wallet
-            .exec_sql(
-                &query,
-                rusqlite::params![is_spend, "-", serialize_async(&coin.inner()).await],
-            )
-            .await
+        self.wallet.exec_sql(
+            &query,
+            rusqlite::params![is_spend, "-", serialize_async(&coin.inner()).await],
+        )
     }
 
     /// Replace the Money Merkle tree in the wallet.
     pub async fn put_money_tree(&self, tree: &MerkleTree) -> WalletDbResult<()> {
         // First we remove old record
         let query = format!("DELETE FROM {};", *MONEY_TREE_TABLE);
-        self.wallet.exec_sql(&query, &[]).await?;
+        self.wallet.exec_sql(&query, &[])?;
 
         // then we insert the new one
         let query =
             format!("INSERT INTO {} ({}) VALUES (?1);", *MONEY_TREE_TABLE, MONEY_TREE_COL_TREE,);
-        self.wallet.exec_sql(&query, rusqlite::params![serialize_async(tree).await]).await
+        self.wallet.exec_sql(&query, rusqlite::params![serialize_async(tree).await])
     }
 
     /// Fetch the Money Merkle tree from the wallet.
     pub async fn get_money_tree(&self) -> Result<MerkleTree> {
-        let row =
-            match self.wallet.query_single(&MONEY_TREE_TABLE, &[MONEY_TREE_COL_TREE], &[]).await {
-                Ok(r) => r,
-                Err(e) => {
-                    return Err(Error::RusqliteError(format!(
-                        "[get_money_tree] Tree retrieval failed: {e:?}"
-                    )))
-                }
-            };
+        let row = match self.wallet.query_single(&MONEY_TREE_TABLE, &[MONEY_TREE_COL_TREE], &[]) {
+            Ok(r) => r,
+            Err(e) => {
+                return Err(Error::RusqliteError(format!(
+                    "[get_money_tree] Tree retrieval failed: {e:?}"
+                )))
+            }
+        };
 
         let Value::Blob(ref tree_bytes) = row[0] else {
             return Err(Error::ParseFailed("[get_money_tree] Tree bytes parsing failed"))
@@ -665,11 +683,12 @@ impl Drk {
     }
 
     /// Get the last scanned block height from the wallet.
-    pub async fn last_scanned_block(&self) -> WalletDbResult<u32> {
-        let ret = self
-            .wallet
-            .query_single(&MONEY_INFO_TABLE, &[MONEY_INFO_COL_LAST_SCANNED_BLOCK], &[])
-            .await?;
+    pub fn last_scanned_block(&self) -> WalletDbResult<u32> {
+        let ret = self.wallet.query_single(
+            &MONEY_INFO_TABLE,
+            &[MONEY_INFO_COL_LAST_SCANNED_BLOCK],
+            &[],
+        )?;
         let Value::Integer(height) = ret[0] else {
             return Err(WalletDbError::ParseColumnValueError);
         };
@@ -803,6 +822,7 @@ impl Drk {
                 "[apply_tx_money_data] Put Money tree failed: {e:?}"
             )))
         }
+        self.smt_insert(&nullifiers)?;
         self.mark_spent_coins(&nullifiers, tx_hash).await?;
 
         // This is the SQL query we'll be executing to insert new coins
@@ -842,7 +862,7 @@ impl Drk {
                 serialize_async(&owncoin.note.memo).await,
             ];
 
-            if let Err(e) = self.wallet.exec_sql(&query, params).await {
+            if let Err(e) = self.wallet.exec_sql(&query, params) {
                 return Err(Error::RusqliteError(format!(
                     "[apply_tx_money_data] Inserting Money coin failed: {e:?}"
                 )))
@@ -855,10 +875,8 @@ impl Drk {
                 *MONEY_TOKENS_TABLE, MONEY_TOKENS_COL_IS_FROZEN, MONEY_TOKENS_COL_TOKEN_ID,
             );
 
-            if let Err(e) = self
-                .wallet
-                .exec_sql(&query, rusqlite::params![serialize_async(&token_id).await])
-                .await
+            if let Err(e) =
+                self.wallet.exec_sql(&query, rusqlite::params![serialize_async(&token_id).await])
             {
                 return Err(Error::RusqliteError(format!(
                     "[apply_tx_money_data] Inserting Money coin failed: {e:?}"
@@ -920,7 +938,7 @@ impl Drk {
         Ok(())
     }
 
-    /// Mark a coin in the wallet as spent
+    /// Mark a coin in the wallet as spent.
     pub async fn mark_spent_coin(&self, coin: &Coin, spent_tx_hash: &String) -> WalletDbResult<()> {
         let query = format!(
             "UPDATE {} SET {} = ?1, {} = ?2 WHERE {} = ?3;",
@@ -930,15 +948,13 @@ impl Drk {
             MONEY_COINS_COL_COIN
         );
         let is_spent = 1;
-        self.wallet
-            .exec_sql(
-                &query,
-                rusqlite::params![is_spent, spent_tx_hash, serialize_async(&coin.inner()).await],
-            )
-            .await
+        self.wallet.exec_sql(
+            &query,
+            rusqlite::params![is_spent, spent_tx_hash, serialize_async(&coin.inner()).await],
+        )
     }
 
-    /// Marks all coins in the wallet as spent, if their nullifier is in the given set
+    /// Marks all coins in the wallet as spent, if their nullifier is in the given set.
     pub async fn mark_spent_coins(
         &self,
         nullifiers: &[Nullifier],
@@ -961,7 +977,23 @@ impl Drk {
         Ok(())
     }
 
-    /// Reset the Money Merkle tree in the wallet
+    /// Inserts given slice to the wallets nullifiers Sparse Merkle Tree.
+    pub fn smt_insert(&self, nullifiers: &[Nullifier]) -> Result<()> {
+        let store = WalletStorage::new(
+            &self.wallet,
+            &MONEY_SMT_TABLE,
+            MONEY_SMT_COL_KEY,
+            MONEY_SMT_COL_VALUE,
+        );
+        let mut smt = WalletSmt::new(store, PoseidonFp::new(), &EMPTY_NODES_FP);
+
+        let leaves: Vec<_> = nullifiers.iter().map(|x| (x.inner(), x.inner())).collect();
+        smt.insert_batch(leaves)?;
+
+        Ok(())
+    }
+
+    /// Reset the Money Merkle tree in the wallet.
     pub async fn reset_money_tree(&self) -> WalletDbResult<()> {
         println!("Resetting Money Merkle tree");
         let mut tree = MerkleTree::new(1);
@@ -973,11 +1005,21 @@ impl Drk {
         Ok(())
     }
 
-    /// Reset the Money coins in the wallet
-    pub async fn reset_money_coins(&self) -> WalletDbResult<()> {
+    /// Reset the Money nullifiers Sparse Merkle Tree in the wallet.
+    pub fn reset_money_smt(&self) -> WalletDbResult<()> {
+        println!("Resetting Money Sparse Merkle tree");
+        let query = format!("DELETE FROM {};", *MONEY_SMT_TABLE);
+        self.wallet.exec_sql(&query, &[])?;
+        println!("Successfully reset Money Sparse Merkle tree");
+
+        Ok(())
+    }
+
+    /// Reset the Money coins in the wallet.
+    pub fn reset_money_coins(&self) -> WalletDbResult<()> {
         println!("Resetting coins");
         let query = format!("DELETE FROM {};", *MONEY_COINS_TABLE);
-        self.wallet.exec_sql(&query, &[]).await?;
+        self.wallet.exec_sql(&query, &[])?;
         println!("Successfully reset coins");
 
         Ok(())

+ 21 - 9
bin/drk/src/rpc.rs

@@ -58,7 +58,7 @@ impl Drk {
         let req = JsonRequest::new("blockchain.last_known_block", JsonValue::Array(vec![]));
         let rep = self.rpc_client.as_ref().unwrap().request(req).await?;
         let last_known = *rep.get::<f64>().unwrap() as u32;
-        let last_scanned = match self.last_scanned_block().await {
+        let last_scanned = match self.last_scanned_block() {
             Ok(l) => l,
             Err(e) => {
                 return Err(Error::RusqliteError(format!(
@@ -149,7 +149,7 @@ impl Drk {
                             },
                         };
                         if let Err(e) =
-                            self.update_tx_history_records_status(&txs_hashes, "Finalized").await
+                            self.update_tx_history_records_status(&txs_hashes, "Finalized")
                         {
                             return Err(Error::RusqliteError(format!(
                                 "[subscribe_blocks] Update transaction history record status failed: {e:?}"
@@ -216,7 +216,7 @@ impl Drk {
         // Write this block height into `last_scanned_block`
         let query =
             format!("UPDATE {} SET {} = ?1;", *MONEY_INFO_TABLE, MONEY_INFO_COL_LAST_SCANNED_BLOCK);
-        if let Err(e) = self.wallet.exec_sql(&query, rusqlite::params![block.header.height]).await {
+        if let Err(e) = self.wallet.exec_sql(&query, rusqlite::params![block.header.height]) {
             return Err(Error::RusqliteError(format!(
                 "[scan_block] Update last scanned block failed: {e:?}"
             )))
@@ -231,18 +231,19 @@ impl Drk {
     /// it looks for a checkpoint in the wallet to reset and start scanning from.
     pub async fn scan_blocks(&self, reset: bool) -> WalletDbResult<()> {
         // Grab last scanned block height
-        let mut height = self.last_scanned_block().await?;
+        let mut height = self.last_scanned_block()?;
         // If last scanned block is genesis (0) or reset flag
         // has been provided we reset, otherwise continue with
         // the next block height
         if height == 0 || reset {
             self.reset_money_tree().await?;
-            self.reset_money_coins().await?;
+            self.reset_money_smt()?;
+            self.reset_money_coins()?;
             self.reset_dao_trees().await?;
             self.reset_daos().await?;
-            self.reset_dao_proposals().await?;
-            self.reset_dao_votes().await?;
-            self.update_all_tx_history_records_status("Rejected").await?;
+            self.reset_dao_proposals()?;
+            self.reset_dao_votes()?;
+            self.update_all_tx_history_records_status("Rejected")?;
             height = 0;
         } else {
             height += 1;
@@ -281,7 +282,7 @@ impl Drk {
                     return Err(WalletDbError::GenericError)
                 };
                 let txs_hashes = self.insert_tx_history_records(&block.txs).await?;
-                self.update_tx_history_records_status(&txs_hashes, "Finalized").await?;
+                self.update_tx_history_records_status(&txs_hashes, "Finalized")?;
                 height += 1;
             }
         }
@@ -384,4 +385,15 @@ impl Drk {
 
         Ok(gas)
     }
+
+    /// Queries darkfid for current best fork next height.
+    pub async fn get_next_block_height(&self) -> Result<u32> {
+        let req =
+            JsonRequest::new("blockchain.best_fork_next_block_height", JsonValue::Array(vec![]));
+        let rep = self.rpc_client.as_ref().unwrap().request(req).await?;
+
+        let next_height = *rep.get::<f64>().unwrap() as u32;
+
+        Ok(next_height)
+    }
 }

+ 22 - 23
bin/drk/src/token.rs

@@ -98,19 +98,15 @@ impl Drk {
             MONEY_TOKENS_COL_IS_FROZEN,
         );
 
-        if let Err(e) = self
-            .wallet
-            .exec_sql(
-                &query,
-                rusqlite::params![
-                    serialize_async(&token_id).await,
-                    serialize_async(&mint_authority).await,
-                    serialize_async(&token_blind).await,
-                    is_frozen,
-                ],
-            )
-            .await
-        {
+        if let Err(e) = self.wallet.exec_sql(
+            &query,
+            rusqlite::params![
+                serialize_async(&token_id).await,
+                serialize_async(&mint_authority).await,
+                serialize_async(&token_blind).await,
+                is_frozen,
+            ],
+        ) {
             return Err(Error::RusqliteError(format!(
                 "[import_mint_authority] Inserting mint authority failed: {e:?}"
             )))
@@ -158,7 +154,7 @@ impl Drk {
 
     /// Fetch all token mint authorities from the wallet.
     pub async fn get_mint_authorities(&self) -> Result<Vec<(TokenId, SecretKey, BaseBlind, bool)>> {
-        let rows = match self.wallet.query_multiple(&MONEY_TOKENS_TABLE, &[], &[]).await {
+        let rows = match self.wallet.query_multiple(&MONEY_TOKENS_TABLE, &[], &[]) {
             Ok(r) => r,
             Err(e) => {
                 return Err(Error::RusqliteError(format!(
@@ -180,15 +176,18 @@ impl Drk {
         &self,
         token_id: &TokenId,
     ) -> Result<(TokenId, SecretKey, BaseBlind, bool)> {
-        let row =
-            match self.wallet.query_single(&MONEY_TOKENS_TABLE, &[], convert_named_params! {(MONEY_TOKENS_COL_TOKEN_ID, serialize_async(token_id).await)}).await {
-                Ok(r) => r,
-                Err(e) => {
-                    return Err(Error::RusqliteError(format!(
-                        "[get_token_mint_authority] Token mint autority retrieval failed: {e:?}"
-                    )))
-                }
-            };
+        let row = match self.wallet.query_single(
+            &MONEY_TOKENS_TABLE,
+            &[],
+            convert_named_params! {(MONEY_TOKENS_COL_TOKEN_ID, serialize_async(token_id).await)},
+        ) {
+            Ok(r) => r,
+            Err(e) => {
+                return Err(Error::RusqliteError(format!(
+                    "[get_token_mint_authority] Token mint autority retrieval failed: {e:?}"
+                )))
+            }
+        };
 
         let token = self.parse_mint_authority_record(&row).await?;
 

+ 19 - 28
bin/drk/src/txs_history.rs

@@ -45,12 +45,10 @@ impl Drk {
             WALLET_TXS_HISTORY_COL_TX,
         );
         let tx_hash = tx.hash().to_string();
-        self.wallet
-            .exec_sql(
-                &query,
-                rusqlite::params![tx_hash, "Broadcasted", &serialize_async(tx).await,],
-            )
-            .await?;
+        self.wallet.exec_sql(
+            &query,
+            rusqlite::params![tx_hash, "Broadcasted", &serialize_async(tx).await,],
+        )?;
 
         Ok(tx_hash)
     }
@@ -72,15 +70,11 @@ impl Drk {
         &self,
         tx_hash: &str,
     ) -> Result<(String, String, Transaction)> {
-        let row = match self
-            .wallet
-            .query_single(
-                WALLET_TXS_HISTORY_TABLE,
-                &[],
-                convert_named_params! {(WALLET_TXS_HISTORY_COL_TX_HASH, tx_hash)},
-            )
-            .await
-        {
+        let row = match self.wallet.query_single(
+            WALLET_TXS_HISTORY_TABLE,
+            &[],
+            convert_named_params! {(WALLET_TXS_HISTORY_COL_TX_HASH, tx_hash)},
+        ) {
             Ok(r) => r,
             Err(e) => {
                 return Err(Error::RusqliteError(format!(
@@ -110,15 +104,12 @@ impl Drk {
     }
 
     /// Fetch all transactions history records, excluding bytes column.
-    pub async fn get_txs_history(&self) -> WalletDbResult<Vec<(String, String)>> {
-        let rows = self
-            .wallet
-            .query_multiple(
-                WALLET_TXS_HISTORY_TABLE,
-                &[WALLET_TXS_HISTORY_COL_TX_HASH, WALLET_TXS_HISTORY_COL_STATUS],
-                &[],
-            )
-            .await?;
+    pub fn get_txs_history(&self) -> WalletDbResult<Vec<(String, String)>> {
+        let rows = self.wallet.query_multiple(
+            WALLET_TXS_HISTORY_TABLE,
+            &[WALLET_TXS_HISTORY_COL_TX_HASH, WALLET_TXS_HISTORY_COL_STATUS],
+            &[],
+        )?;
 
         let mut ret = Vec::with_capacity(rows.len());
         for row in rows {
@@ -137,7 +128,7 @@ impl Drk {
     }
 
     /// Update given transactions history record statuses to the given one.
-    pub async fn update_tx_history_records_status(
+    pub fn update_tx_history_records_status(
         &self,
         txs_hashes: &[String],
         status: &str,
@@ -155,15 +146,15 @@ impl Drk {
             txs_hashes_string
         );
 
-        self.wallet.exec_sql(&query, rusqlite::params![status]).await
+        self.wallet.exec_sql(&query, rusqlite::params![status])
     }
 
     /// Update all transaction history records statuses to the given one.
-    pub async fn update_all_tx_history_records_status(&self, status: &str) -> WalletDbResult<()> {
+    pub fn update_all_tx_history_records_status(&self, status: &str) -> WalletDbResult<()> {
         let query = format!(
             "UPDATE {} SET {} = ?1",
             WALLET_TXS_HISTORY_TABLE, WALLET_TXS_HISTORY_COL_STATUS,
         );
-        self.wallet.exec_sql(&query, rusqlite::params![status]).await
+        self.wallet.exec_sql(&query, rusqlite::params![status])
     }
 }

+ 255 - 120
bin/drk/src/walletdb.rs

@@ -16,14 +16,25 @@
  * along with this program.  If not, see <https://www.gnu.org/licenses/>.
  */
 
-use std::{path::PathBuf, sync::Arc};
+use std::{
+    path::PathBuf,
+    sync::{Arc, Mutex},
+};
 
+use darkfi_sdk::{
+    crypto::{
+        pasta_prelude::PrimeField,
+        smt::{PoseidonFp, SparseMerkleTree, StorageAdapter, SMT_FP_DEPTH},
+    },
+    error::{ContractError, ContractResult},
+    pasta::pallas,
+};
 use log::{debug, error};
+use num_bigint::BigUint;
 use rusqlite::{
     types::{ToSql, Value},
     Connection,
 };
-use smol::lock::Mutex;
 
 use crate::error::{WalletDbError, WalletDbResult};
 
@@ -62,9 +73,10 @@ impl WalletDb {
 
     /// This function executes a given SQL query that contains multiple SQL statements,
     /// that don't contain any parameters.
-    pub async fn exec_batch_sql(&self, query: &str) -> WalletDbResult<()> {
+    pub fn exec_batch_sql(&self, query: &str) -> WalletDbResult<()> {
         debug!(target: "walletdb::exec_batch_sql", "[WalletDb] Executing batch SQL query:\n{query}");
-        if let Err(e) = self.conn.lock().await.execute_batch(query) {
+        let Ok(conn) = self.conn.lock() else { return Err(WalletDbError::FailedToAquireLock) };
+        if let Err(e) = conn.execute_batch(query) {
             error!(target: "walletdb::exec_batch_sql", "[WalletDb] Query failed: {e}");
             return Err(WalletDbError::QueryExecutionFailed)
         };
@@ -74,11 +86,13 @@ impl WalletDb {
 
     /// This function executes a given SQL query, but isn't able to return anything.
     /// Therefore it's best to use it for initializing a table or similar things.
-    pub async fn exec_sql(&self, query: &str, params: &[&dyn ToSql]) -> WalletDbResult<()> {
+    pub fn exec_sql(&self, query: &str, params: &[&dyn ToSql]) -> WalletDbResult<()> {
         debug!(target: "walletdb::exec_sql", "[WalletDb] Executing SQL query:\n{query}");
+        let Ok(conn) = self.conn.lock() else { return Err(WalletDbError::FailedToAquireLock) };
+
         // If no params are provided, execute directly
         if params.is_empty() {
-            if let Err(e) = self.conn.lock().await.execute(query, ()) {
+            if let Err(e) = conn.execute(query, ()) {
                 error!(target: "walletdb::exec_sql", "[WalletDb] Query failed: {e}");
                 return Err(WalletDbError::QueryExecutionFailed)
             };
@@ -86,7 +100,6 @@ impl WalletDb {
         }
 
         // First we prepare the query
-        let conn = self.conn.lock().await;
         let Ok(mut stmt) = conn.prepare(query) else {
             return Err(WalletDbError::QueryPreparationFailed)
         };
@@ -137,7 +150,7 @@ impl WalletDb {
 
     /// Query provided table from selected column names and provided `WHERE` clauses,
     /// for a single row.
-    pub async fn query_single(
+    pub fn query_single(
         &self,
         table: &str,
         col_names: &[&str],
@@ -148,7 +161,8 @@ impl WalletDb {
         debug!(target: "walletdb::query_single", "[WalletDb] Executing SQL query:\n{query}");
 
         // First we prepare the query
-        let conn = self.conn.lock().await;
+        let Ok(conn) = self.conn.lock() else { return Err(WalletDbError::FailedToAquireLock) };
+
         let Ok(mut stmt) = conn.prepare(&query) else {
             return Err(WalletDbError::QueryPreparationFailed)
         };
@@ -188,7 +202,7 @@ impl WalletDb {
 
     /// Query provided table from selected column names and provided `WHERE` clauses,
     /// for multiple rows.
-    pub async fn query_multiple(
+    pub fn query_multiple(
         &self,
         table: &str,
         col_names: &[&str],
@@ -199,16 +213,13 @@ impl WalletDb {
         debug!(target: "walletdb::multiple", "[WalletDb] Executing SQL query:\n{query}");
 
         // First we prepare the query
-        let conn = self.conn.lock().await;
+        let Ok(conn) = self.conn.lock() else { return Err(WalletDbError::FailedToAquireLock) };
         let Ok(mut stmt) = conn.prepare(&query) else {
             return Err(WalletDbError::QueryPreparationFailed)
         };
 
         // Execute the query using provided converted params
         let Ok(mut rows) = stmt.query(params) else {
-            if let Err(e) = stmt.query(params) {
-                println!("eeer: {e:?}");
-            }
             return Err(WalletDbError::QueryExecutionFailed)
         };
 
@@ -263,127 +274,251 @@ macro_rules! convert_named_params {
     };
 }
 
+/// Wallet SMT definition
+pub type WalletSmt<'a> = SparseMerkleTree<
+    'static,
+    SMT_FP_DEPTH,
+    { SMT_FP_DEPTH + 1 },
+    pallas::Base,
+    PoseidonFp,
+    WalletStorage<'a>,
+>;
+
+/// An SMT adapter for wallet SQLite database storage.
+pub struct WalletStorage<'a> {
+    wallet: &'a WalletPtr,
+    table: &'a str,
+    key_col: &'a str,
+    value_col: &'a str,
+}
+
+impl<'a> WalletStorage<'a> {
+    pub fn new(
+        wallet: &'a WalletPtr,
+        table: &'a str,
+        key_col: &'a str,
+        value_col: &'a str,
+    ) -> Self {
+        Self { wallet, table, key_col, value_col }
+    }
+}
+
+impl<'a> StorageAdapter for WalletStorage<'a> {
+    type Value = pallas::Base;
+
+    fn put(&mut self, key: BigUint, value: pallas::Base) -> ContractResult {
+        let query = format!(
+            "INSERT OR REPLACE INTO {} ({}, {}) VALUES (?1, ?2);",
+            self.table, self.key_col, self.value_col
+        );
+        if let Err(e) =
+            self.wallet.exec_sql(&query, rusqlite::params![key.to_bytes_le(), value.to_repr()])
+        {
+            error!(target: "walletdb::StorageAdapter::put", "Inserting key {key:?}, value {value:?} into DB failed: {e:?}");
+            return Err(ContractError::SmtPutFailed)
+        }
+
+        Ok(())
+    }
+
+    fn get(&self, key: &BigUint) -> Option<pallas::Base> {
+        let row = match self.wallet.query_single(
+            self.table,
+            &[self.value_col],
+            convert_named_params! {(self.key_col, key.to_bytes_le())},
+        ) {
+            Ok(r) => r,
+            Err(WalletDbError::RowNotFound) => return None,
+            Err(e) => {
+                error!(target: "walletdb::StorageAdapter::get", "Fetching key {key:?} from DB failed: {e:?}");
+                return None
+            }
+        };
+
+        let Value::Blob(ref value_bytes) = row[0] else {
+            error!(target: "walletdb::StorageAdapter::get", "Parsing key {key:?} value bytes");
+            return None
+        };
+
+        let mut repr = [0; 32];
+        repr.copy_from_slice(value_bytes);
+
+        pallas::Base::from_repr(repr).into()
+    }
+
+    fn del(&mut self, key: &BigUint) -> ContractResult {
+        let query = format!("DELETE FROM {} WHERE {} = ?1;", self.table, self.key_col);
+        if let Err(e) = self.wallet.exec_sql(&query, rusqlite::params![key.to_bytes_le()]) {
+            error!(target: "walletdb::StorageAdapter::del", "Removing key {key:?} from DB failed: {e:?}");
+            return Err(ContractError::SmtDelFailed)
+        }
+
+        Ok(())
+    }
+}
+
 #[cfg(test)]
 mod tests {
+    use darkfi::zk::halo2::Field;
+    use darkfi_sdk::{
+        crypto::smt::{gen_empty_nodes, util::FieldHasher, PoseidonFp, SparseMerkleTree},
+        pasta::pallas,
+    };
+    use rand::rngs::OsRng;
     use rusqlite::types::Value;
 
-    use crate::walletdb::WalletDb;
+    use crate::walletdb::{WalletDb, WalletStorage};
 
     #[test]
     fn test_mem_wallet() {
-        smol::block_on(async {
-            let wallet = WalletDb::new(None, Some("foobar")).unwrap();
-            wallet.exec_sql("CREATE TABLE mista ( numba INTEGER );", &[]).await.unwrap();
-            wallet.exec_sql("INSERT INTO mista ( numba ) VALUES ( 42 );", &[]).await.unwrap();
-
-            let ret = wallet.query_single("mista", &["numba"], &[]).await.unwrap();
-            assert_eq!(ret.len(), 1);
-            let numba: i64 = if let Value::Integer(numba) = ret[0] { numba } else { -1 };
-            assert_eq!(numba, 42);
-        });
+        let wallet = WalletDb::new(None, Some("foobar")).unwrap();
+        wallet.exec_sql("CREATE TABLE mista ( numba INTEGER );", &[]).unwrap();
+        wallet.exec_sql("INSERT INTO mista ( numba ) VALUES ( 42 );", &[]).unwrap();
+
+        let ret = wallet.query_single("mista", &["numba"], &[]).unwrap();
+        assert_eq!(ret.len(), 1);
+        let numba: i64 = if let Value::Integer(numba) = ret[0] { numba } else { -1 };
+        assert_eq!(numba, 42);
     }
 
     #[test]
     fn test_query_single() {
-        smol::block_on(async {
-            let wallet = WalletDb::new(None, None).unwrap();
-            wallet
-                .exec_sql(
-                    "CREATE TABLE mista ( why INTEGER, are TEXT, you INTEGER, gae BLOB );",
-                    &[],
-                )
-                .await
-                .unwrap();
-
-            let why = 42;
-            let are = "are".to_string();
-            let you = 69;
-            let gae = vec![42u8; 32];
-
-            wallet
-                .exec_sql(
-                    "INSERT INTO mista ( why, are, you, gae ) VALUES (?1, ?2, ?3, ?4);",
-                    rusqlite::params![why, are, you, gae],
-                )
-                .await
-                .unwrap();
-
-            let ret =
-                wallet.query_single("mista", &["why", "are", "you", "gae"], &[]).await.unwrap();
-            assert_eq!(ret.len(), 4);
-            assert_eq!(ret[0], Value::Integer(why));
-            assert_eq!(ret[1], Value::Text(are.clone()));
-            assert_eq!(ret[2], Value::Integer(you));
-            assert_eq!(ret[3], Value::Blob(gae.clone()));
-
-            let ret = wallet
-                .query_single(
-                    "mista",
-                    &["gae"],
-                    rusqlite::named_params! {":why": why, ":are": are, ":you": you},
-                )
-                .await
-                .unwrap();
-            assert_eq!(ret.len(), 1);
-            assert_eq!(ret[0], Value::Blob(gae));
-        });
+        let wallet = WalletDb::new(None, None).unwrap();
+        wallet
+            .exec_sql("CREATE TABLE mista ( why INTEGER, are TEXT, you INTEGER, gae BLOB );", &[])
+            .unwrap();
+
+        let why = 42;
+        let are = "are".to_string();
+        let you = 69;
+        let gae = vec![42u8; 32];
+
+        wallet
+            .exec_sql(
+                "INSERT INTO mista ( why, are, you, gae ) VALUES (?1, ?2, ?3, ?4);",
+                rusqlite::params![why, are, you, gae],
+            )
+            .unwrap();
+
+        let ret = wallet.query_single("mista", &["why", "are", "you", "gae"], &[]).unwrap();
+        assert_eq!(ret.len(), 4);
+        assert_eq!(ret[0], Value::Integer(why));
+        assert_eq!(ret[1], Value::Text(are.clone()));
+        assert_eq!(ret[2], Value::Integer(you));
+        assert_eq!(ret[3], Value::Blob(gae.clone()));
+
+        let ret = wallet
+            .query_single(
+                "mista",
+                &["gae"],
+                rusqlite::named_params! {":why": why, ":are": are, ":you": you},
+            )
+            .unwrap();
+        assert_eq!(ret.len(), 1);
+        assert_eq!(ret[0], Value::Blob(gae));
     }
 
     #[test]
     fn test_query_multi() {
-        smol::block_on(async {
-            let wallet = WalletDb::new(None, None).unwrap();
-            wallet
-                .exec_sql(
-                    "CREATE TABLE mista ( why INTEGER, are TEXT, you INTEGER, gae BLOB );",
-                    &[],
-                )
-                .await
-                .unwrap();
-
-            let why = 42;
-            let are = "are".to_string();
-            let you = 69;
-            let gae = vec![42u8; 32];
-
-            wallet
-                .exec_sql(
-                    "INSERT INTO mista ( why, are, you, gae ) VALUES (?1, ?2, ?3, ?4);",
-                    rusqlite::params![why, are, you, gae],
-                )
-                .await
-                .unwrap();
-            wallet
-                .exec_sql(
-                    "INSERT INTO mista ( why, are, you, gae ) VALUES (?1, ?2, ?3, ?4);",
-                    rusqlite::params![why, are, you, gae],
-                )
-                .await
-                .unwrap();
-
-            let ret = wallet.query_multiple("mista", &[], &[]).await.unwrap();
-            assert_eq!(ret.len(), 2);
-            for row in ret {
-                assert_eq!(row.len(), 4);
-                assert_eq!(row[0], Value::Integer(why));
-                assert_eq!(row[1], Value::Text(are.clone()));
-                assert_eq!(row[2], Value::Integer(you));
-                assert_eq!(row[3], Value::Blob(gae.clone()));
-            }
+        let wallet = WalletDb::new(None, None).unwrap();
+        wallet
+            .exec_sql("CREATE TABLE mista ( why INTEGER, are TEXT, you INTEGER, gae BLOB );", &[])
+            .unwrap();
+
+        let why = 42;
+        let are = "are".to_string();
+        let you = 69;
+        let gae = vec![42u8; 32];
+
+        wallet
+            .exec_sql(
+                "INSERT INTO mista ( why, are, you, gae ) VALUES (?1, ?2, ?3, ?4);",
+                rusqlite::params![why, are, you, gae],
+            )
+            .unwrap();
+        wallet
+            .exec_sql(
+                "INSERT INTO mista ( why, are, you, gae ) VALUES (?1, ?2, ?3, ?4);",
+                rusqlite::params![why, are, you, gae],
+            )
+            .unwrap();
+
+        let ret = wallet.query_multiple("mista", &[], &[]).unwrap();
+        assert_eq!(ret.len(), 2);
+        for row in ret {
+            assert_eq!(row.len(), 4);
+            assert_eq!(row[0], Value::Integer(why));
+            assert_eq!(row[1], Value::Text(are.clone()));
+            assert_eq!(row[2], Value::Integer(you));
+            assert_eq!(row[3], Value::Blob(gae.clone()));
+        }
 
-            let ret = wallet
-                .query_multiple(
-                    "mista",
-                    &["gae"],
-                    convert_named_params! {("why", why), ("are", are), ("you", you)},
-                )
-                .await
-                .unwrap();
-            assert_eq!(ret.len(), 2);
-            for row in ret {
-                assert_eq!(row.len(), 1);
-                assert_eq!(row[0], Value::Blob(gae.clone()));
-            }
-        });
+        let ret = wallet
+            .query_multiple(
+                "mista",
+                &["gae"],
+                convert_named_params! {("why", why), ("are", are), ("you", you)},
+            )
+            .unwrap();
+        assert_eq!(ret.len(), 2);
+        for row in ret {
+            assert_eq!(row.len(), 1);
+            assert_eq!(row[0], Value::Blob(gae.clone()));
+        }
+    }
+
+    #[test]
+    fn test_sqlite_smt() {
+        // Setup SQLite database
+        let table = &"smt";
+        let key_col = &"smt_key";
+        let value_col = &"smt_value";
+        let wallet = WalletDb::new(None, None).unwrap();
+        wallet.exec_sql(&format!("CREATE TABLE {table} ( {key_col} BLOB INTEGER PRIMARY KEY NOT NULL, {value_col} BLOB NOT NULL);"), &[]).unwrap();
+
+        // Setup SMT
+        const HEIGHT: usize = 3;
+        let hasher = PoseidonFp::new();
+        let empty_leaf = pallas::Base::ZERO;
+        let empty_nodes = gen_empty_nodes::<{ HEIGHT + 1 }, _, _>(&hasher, empty_leaf);
+        let store = WalletStorage::new(&wallet, table, key_col, value_col);
+        let mut smt = SparseMerkleTree::<HEIGHT, { HEIGHT + 1 }, _, _, _>::new(
+            store,
+            hasher.clone(),
+            &empty_nodes,
+        );
+
+        let leaves = vec![
+            (pallas::Base::from(1), pallas::Base::random(&mut OsRng)),
+            (pallas::Base::from(2), pallas::Base::random(&mut OsRng)),
+            (pallas::Base::from(3), pallas::Base::random(&mut OsRng)),
+        ];
+        smt.insert_batch(leaves.clone()).unwrap();
+
+        let hash1 = leaves[0].1;
+        let hash2 = leaves[1].1;
+        let hash3 = leaves[2].1;
+
+        let hash = |l, r| hasher.hash([l, r]);
+
+        let hash01 = hash(empty_nodes[3], hash1);
+        let hash23 = hash(hash2, hash3);
+
+        let hash0123 = hash(hash01, hash23);
+        let root = hash(hash0123, empty_nodes[1]);
+        assert_eq!(root, smt.root());
+
+        // Now try to construct a membership proof for leaf 3
+        let pos = leaves[2].0;
+        let path = smt.prove_membership(&pos);
+        assert_eq!(path.path[0], empty_nodes[1]);
+        assert_eq!(path.path[1], hash01);
+        assert_eq!(path.path[2], hash2);
+
+        assert_eq!(hash23, hash(path.path[2], hash3));
+        assert_eq!(hash0123, hash(path.path[1], hash(path.path[2], hash3)));
+        assert_eq!(root, hash(hash(path.path[1], hash(path.path[2], hash3)), path.path[0]));
+
+        assert!(path.verify(&root, &hash3, &pos));
     }
 }

+ 19 - 16
src/contract/dao/src/client/propose.rs

@@ -21,9 +21,12 @@ use darkfi_sdk::{
     bridgetree,
     bridgetree::Hashable,
     crypto::{
-        note::AeadEncryptedNote, pasta_prelude::*, pedersen::pedersen_commitment_u64,
-        poseidon_hash, smt::SmtMemoryFp, Blind, FuncId, MerkleNode, PublicKey, ScalarBlind,
-        SecretKey,
+        note::AeadEncryptedNote,
+        pasta_prelude::*,
+        pedersen::pedersen_commitment_u64,
+        poseidon_hash,
+        smt::{PoseidonFp, SparseMerkleTree, StorageAdapter, SMT_FP_DEPTH},
+        Blind, FuncId, MerkleNode, PublicKey, ScalarBlind, SecretKey,
     },
     pasta::pallas,
 };
@@ -40,25 +43,26 @@ use crate::{
     model::{Dao, DaoProposal, DaoProposeParams, DaoProposeParamsInput, VecAuthCallCommit},
 };
 
-pub struct DaoProposeStakeInput<'a> {
+pub struct DaoProposeStakeInput {
     pub secret: SecretKey,
     pub note: darkfi_money_contract::client::MoneyNote,
     pub leaf_position: bridgetree::Position,
     pub merkle_path: Vec<MerkleNode>,
-    pub money_null_smt: &'a SmtMemoryFp,
-    pub signature_secret: SecretKey,
 }
 
-pub struct DaoProposeCall<'a> {
-    pub inputs: Vec<DaoProposeStakeInput<'a>>,
+pub struct DaoProposeCall<'a, T: StorageAdapter<Value = pallas::Base>> {
+    pub money_null_smt:
+        &'a SparseMerkleTree<'a, SMT_FP_DEPTH, { SMT_FP_DEPTH + 1 }, pallas::Base, PoseidonFp, T>,
+    pub inputs: Vec<DaoProposeStakeInput>,
     pub proposal: DaoProposal,
     pub dao: Dao,
     pub dao_leaf_position: bridgetree::Position,
     pub dao_merkle_path: Vec<MerkleNode>,
     pub dao_merkle_root: MerkleNode,
+    pub signature_secret: SecretKey,
 }
 
-impl<'a> DaoProposeCall<'a> {
+impl<'a, T: StorageAdapter<Value = pallas::Base>> DaoProposeCall<'a, T> {
     pub fn make(
         self,
         burn_zkbin: &ZkBinary,
@@ -70,6 +74,10 @@ impl<'a> DaoProposeCall<'a> {
 
         let gov_token_blind = Blind::random(&mut OsRng);
 
+        let smt_null_root = self.money_null_smt.root();
+        let signature_public = PublicKey::from_secret(self.signature_secret);
+        let (sig_x, sig_y) = signature_public.xy();
+
         let mut inputs = vec![];
         let mut total_funds = 0;
         let mut total_funds_blinds = ScalarBlind::ZERO;
@@ -79,8 +87,6 @@ impl<'a> DaoProposeCall<'a> {
             total_funds += input.note.value;
             total_funds_blinds += funds_blind;
 
-            let signature_public = PublicKey::from_secret(input.signature_secret);
-
             // Note from the previous output
             let note = input.note;
             let leaf_pos: u64 = input.leaf_position.into();
@@ -97,8 +103,7 @@ impl<'a> DaoProposeCall<'a> {
             .to_coin();
             let nullifier = poseidon_hash([input.secret.inner(), coin.inner()]);
 
-            let smt_null_root = input.money_null_smt.root();
-            let smt_null_path = input.money_null_smt.prove_membership(&nullifier);
+            let smt_null_path = self.money_null_smt.prove_membership(&nullifier);
             if !smt_null_path.verify(&smt_null_root, &pallas::Base::ZERO, &nullifier) {
                 return Err(
                     ClientFailed::VerifyError(DaoError::InvalidInputMerkleRoot.to_string()).into()
@@ -117,7 +122,7 @@ impl<'a> DaoProposeCall<'a> {
                 Witness::Uint32(Value::known(leaf_pos.try_into().unwrap())),
                 Witness::MerklePath(Value::known(input.merkle_path.clone().try_into().unwrap())),
                 Witness::SparseMerklePath(Value::known(smt_null_path.path)),
-                Witness::Base(Value::known(input.signature_secret.inner())),
+                Witness::Base(Value::known(self.signature_secret.inner())),
             ];
 
             // TODO: We need a generic ZkSet widget to avoid doing this all the time
@@ -144,8 +149,6 @@ impl<'a> DaoProposeCall<'a> {
             let value_commit = pedersen_commitment_u64(note.value, funds_blind);
             let value_coords = value_commit.to_affine().coordinates().unwrap();
 
-            let (sig_x, sig_y) = signature_public.xy();
-
             let public_inputs = vec![
                 smt_null_root,
                 *value_coords.x(),

+ 4 - 4
src/contract/test-harness/src/dao_propose.rs

@@ -71,8 +71,6 @@ impl TestHarness {
             .unwrap()
             .clone();
 
-        let signature_secret = SecretKey::random(&mut OsRng);
-
         // Useful code snippet to dump a sled contract DB
         /*{
             let blockchain = &wallet.validator.blockchain;
@@ -95,8 +93,6 @@ impl TestHarness {
                 .money_merkle_tree
                 .witness(propose_owncoin.leaf_position, 0)
                 .unwrap(),
-            money_null_smt: &wallet.money_null_smt,
-            signature_secret,
         };
 
         // Convert coin_params to actual coins
@@ -131,7 +127,10 @@ impl TestHarness {
             blind: Blind::random(&mut OsRng),
         };
 
+        let signature_secret = SecretKey::random(&mut OsRng);
+
         let call = DaoProposeCall {
+            money_null_smt: &wallet.money_null_smt,
             inputs: vec![input],
             proposal: proposal.clone(),
             dao: dao.clone(),
@@ -141,6 +140,7 @@ impl TestHarness {
                 .witness(*wallet.dao_leafs.get(dao_bulla).unwrap(), 0)
                 .unwrap(),
             dao_merkle_root: wallet.dao_merkle_tree.root(0).unwrap(),
+            signature_secret,
         };
 
         let (params, proofs) = call.make(

+ 1 - 1
src/contract/test-harness/src/lib.rs

@@ -179,7 +179,7 @@ impl Wallet {
 
         let hasher = PoseidonFp::new();
         let store = MemoryStorageFp::new();
-        let money_null_smt = SmtMemoryFp::new(store, hasher.clone(), &EMPTY_NODES_FP);
+        let money_null_smt = SmtMemoryFp::new(store, hasher, &EMPTY_NODES_FP);
 
         Ok(Self {
             keypair,

+ 1 - 1
src/sdk/src/crypto/smt/mod.rs

@@ -72,7 +72,7 @@ pub use empty::EMPTY_NODES_FP;
 #[cfg(test)]
 mod test;
 
-mod util;
+pub mod util;
 pub use util::Poseidon;
 
 pub mod wasmdb;

+ 10 - 0
src/validator/mod.rs

@@ -721,4 +721,14 @@ impl Validator {
 
         Ok(())
     }
+
+    /// Auxiliary function to retrieve current best fork next block height.
+    pub async fn best_fork_next_block_height(&self) -> Result<u32> {
+        let forks = self.consensus.forks.read().await;
+        let fork = &forks[best_fork_index(&forks)?];
+        let next_block_height = fork.get_next_block_height()?;
+        drop(forks);
+
+        Ok(next_block_height)
+    }
 }