skoupidi 2 лет назад
Родитель
Сommit
5e0b592603

+ 2 - 0
bin/drk/dao.sql

@@ -140,6 +140,8 @@ CREATE TABLE IF NOT EXISTS Fd8kfCuqU8BoFFp6GcXv5pC8XXRkBK7gUPQX5XDz7iXj_dao_prop
     leaf_position BLOB,
     -- Money merkle tree snapshot for reproducing the snapshot Merkle root
     money_snapshot_tree BLOB,
+    -- Money nullifiers SMT snapshot for reproducing the snapshot Merkle root
+    nullifiers_smt_snapshot BLOB,
     -- The transaction hash where the proposal was deployed
     tx_hash BLOB,
     -- The call index in the transaction where the proposal was deployed

+ 1 - 1
bin/drk/money.sql

@@ -13,7 +13,7 @@ CREATE TABLE IF NOT EXISTS BZHKGQ26bzmBithTQYTJtjo2QdCqpkR9tjSBopT4yf4o_money_tr
 
 -- The Sparse Merkle tree containing coins nullifiers
 CREATE TABLE IF NOT EXISTS BZHKGQ26bzmBithTQYTJtjo2QdCqpkR9tjSBopT4yf4o_money_smt (
-	smt_key BLOB INTEGER PRIMARY KEY NOT NULL,
+	smt_key BLOB PRIMARY KEY NOT NULL,
 	smt_value BLOB NOT NULL
 );
 

+ 92 - 45
bin/drk/src/dao.rs

@@ -19,6 +19,7 @@
 use std::{collections::HashMap, fmt};
 
 use lazy_static::lazy_static;
+use num_bigint::BigUint;
 use rand::rngs::OsRng;
 use rusqlite::types::Value;
 
@@ -47,7 +48,7 @@ use darkfi_money_contract::{
 use darkfi_sdk::{
     bridgetree,
     crypto::{
-        smt::{PoseidonFp, EMPTY_NODES_FP},
+        smt::{MemoryStorageFp, PoseidonFp, SmtMemoryFp, EMPTY_NODES_FP},
         util::{fp_mod_fv, fp_to_u64},
         BaseBlind, Blind, FuncId, FuncRef, Keypair, MerkleNode, MerkleTree, PublicKey, ScalarBlind,
         SecretKey, DAO_CONTRACT_ID, MONEY_CONTRACT_ID,
@@ -92,10 +93,6 @@ pub const DAO_DAOS_COL_CALL_INDEX: &str = "call_index";
 pub const DAO_TREES_COL_DAOS_TREE: &str = "daos_tree";
 pub const DAO_TREES_COL_PROPOSALS_TREE: &str = "proposals_tree";
 
-// DAO_COINS_TABLE
-pub const _DAO_COINS_COL_COIN_ID: &str = "coin_id";
-pub const _DAO_COINS_COL_DAO_ID: &str = "dao_id";
-
 // DAO_PROPOSALS_TABLE
 pub const DAO_PROPOSALS_COL_BULLA: &str = "bulla";
 pub const DAO_PROPOSALS_COL_DAO_BULLA: &str = "dao_bulla";
@@ -103,11 +100,11 @@ pub const DAO_PROPOSALS_COL_PROPOSAL: &str = "proposal";
 pub const DAO_PROPOSALS_COL_DATA: &str = "data";
 pub const DAO_PROPOSALS_COL_LEAF_POSITION: &str = "leaf_position";
 pub const DAO_PROPOSALS_COL_MONEY_SNAPSHOT_TREE: &str = "money_snapshot_tree";
+pub const DAO_PROPOSALS_COL_NULLIFIERS_SMT_SNAPSHOT: &str = "nullifiers_smt_snapshot";
 pub const DAO_PROPOSALS_COL_TX_HASH: &str = "tx_hash";
 pub const DAO_PROPOSALS_COL_CALL_INDEX: &str = "call_index";
 
 // DAO_VOTES_TABLE
-pub const _DAO_VOTES_COL_VOTE_ID: &str = "vote_id";
 pub const DAO_VOTES_COL_PROPOSAL_BULLA: &str = "proposal_bulla";
 pub const DAO_VOTES_COL_VOTE_OPTION: &str = "vote_option";
 pub const DAO_VOTES_COL_YES_VOTE_BLIND: &str = "yes_vote_blind";
@@ -273,6 +270,8 @@ pub struct ProposalRecord {
     pub leaf_position: Option<bridgetree::Position>,
     /// Money merkle tree snapshot for reproducing the snapshot Merkle root
     pub money_snapshot_tree: Option<MerkleTree>,
+    /// Money nullifiers SMT snapshot for reproducing the snapshot Merkle root
+    pub nullifiers_smt_snapshot: Option<HashMap<BigUint, pallas::Base>>,
     /// The transaction hash where the proposal was deployed
     pub tx_hash: Option<TransactionHash>,
     /// The call index in the transaction where the proposal was deployed
@@ -320,6 +319,21 @@ impl fmt::Display for ProposalRecord {
     }
 }
 
+/// Auxiliary structure representing a parsed proposal information from
+/// a transaction call data.
+pub struct ParsedProposal {
+    /// Proposal parameters
+    pub params: DaoProposeParams,
+    /// Money merkle tree snapshot
+    pub money_tree: MerkleTree,
+    /// Money nullifiers SMT snapshot
+    pub nullifiers_smt: HashMap<BigUint, pallas::Base>,
+    /// The transaction hash where the proposal was found
+    pub tx_hash: TransactionHash,
+    /// The call index in the transaction where the proposal was found
+    pub call_idx: u8,
+}
+
 #[derive(Debug, Clone)]
 /// Parameters representing a vote we've made on a DAO proposal
 pub struct DaoVote {
@@ -528,7 +542,19 @@ impl Drk {
             }
         };
 
-        let tx_hash = match row[6] {
+        let nullifiers_smt_snapshot = match row[6] {
+            Value::Blob(ref nullifiers_smt_snapshot_bytes) => {
+                Some(deserialize_async(nullifiers_smt_snapshot_bytes).await?)
+            }
+            Value::Null => None,
+            _ => {
+                return Err(Error::ParseFailed(
+                    "[get_dao_proposals] Nullifiers SMT snapshot bytes parsing failed",
+                ))
+            }
+        };
+
+        let tx_hash = match row[7] {
             Value::Blob(ref tx_hash_bytes) => Some(deserialize_async(tx_hash_bytes).await?),
             Value::Null => None,
             _ => {
@@ -538,7 +564,7 @@ impl Drk {
             }
         };
 
-        let call_index = match row[7] {
+        let call_index = match row[8] {
             Value::Integer(call_index) => {
                 let Ok(call_index) = u8::try_from(call_index) else {
                     return Err(Error::ParseFailed("[get_dao_proposals] Call index parsing failed"))
@@ -554,6 +580,7 @@ impl Drk {
             data,
             leaf_position,
             money_snapshot_tree,
+            nullifiers_smt_snapshot,
             tx_hash,
             call_index,
         })
@@ -599,13 +626,13 @@ impl Drk {
         // DAOs that have been minted
         let mut new_dao_bullas: Vec<(DaoBulla, TransactionHash, u8)> = vec![];
         // DAO proposals that have been minted
-        let mut new_dao_proposals: Vec<(DaoProposeParams, MerkleTree, TransactionHash, u8)> =
-            vec![];
+        let mut new_dao_proposals: Vec<ParsedProposal> = vec![];
         // DAO votes that have been seen
         let mut new_dao_votes: Vec<(DaoVoteParams, TransactionHash, u8)> = vec![];
 
-        // We need to clone the tree here for reproducing the snapshot Merkle root
+        // We need to clone the trees here for reproducing the snapshot Merkle roots
         let money_tree = self.get_money_tree().await?;
+        let nullifiers_smt = self.get_nullifiers_smt().await?;
 
         // Run through the transaction and see what we got:
         match DaoFunction::try_from(data[0])? {
@@ -617,7 +644,13 @@ impl Drk {
             DaoFunction::Propose => {
                 println!("[apply_tx_dao_data] Found Dao::Propose call");
                 let params: DaoProposeParams = deserialize_async(&data[1..]).await?;
-                new_dao_proposals.push((params, money_tree.clone(), tx_hash, call_idx));
+                new_dao_proposals.push(ParsedProposal {
+                    params,
+                    money_tree: money_tree.clone(),
+                    nullifiers_smt: nullifiers_smt.clone(),
+                    tx_hash,
+                    call_idx,
+                });
             }
             DaoFunction::Vote => {
                 println!("[apply_tx_dao_data] Found Dao::Vote call");
@@ -657,35 +690,41 @@ impl Drk {
 
         let mut our_proposals: Vec<ProposalRecord> = vec![];
         for proposal in new_dao_proposals {
-            proposals_tree.append(MerkleNode::from(proposal.0.proposal_bulla.inner()));
+            proposals_tree.append(MerkleNode::from(proposal.params.proposal_bulla.inner()));
 
             // If we're able to decrypt this note, that's the way to link it
             // to a specific DAO.
             for dao in &daos {
-                if let Ok(note) = proposal.0.note.decrypt::<DaoProposal>(&dao.params.secret_key) {
+                if let Ok(note) =
+                    proposal.params.note.decrypt::<DaoProposal>(&dao.params.secret_key)
+                {
                     // We managed to decrypt it. Let's place this in a proper ProposalRecord object
                     println!("[apply_tx_dao_data] Managed to decrypt DAO proposal note");
 
                     // Check if we already got the record
-                    let our_proposal =
-                        match self.get_dao_proposal_by_bulla(&proposal.0.proposal_bulla).await {
-                            Ok(p) => {
-                                let mut our_proposal = p;
-                                our_proposal.leaf_position = proposals_tree.mark();
-                                our_proposal.money_snapshot_tree = Some(proposal.1);
-                                our_proposal.tx_hash = Some(proposal.2);
-                                our_proposal.call_index = Some(proposal.3);
-                                our_proposal
-                            }
-                            Err(_) => ProposalRecord {
-                                proposal: note,
-                                data: None,
-                                leaf_position: proposals_tree.mark(),
-                                money_snapshot_tree: Some(proposal.1),
-                                tx_hash: Some(proposal.2),
-                                call_index: Some(proposal.3),
-                            },
-                        };
+                    let our_proposal = match self
+                        .get_dao_proposal_by_bulla(&proposal.params.proposal_bulla)
+                        .await
+                    {
+                        Ok(p) => {
+                            let mut our_proposal = p;
+                            our_proposal.leaf_position = proposals_tree.mark();
+                            our_proposal.money_snapshot_tree = Some(proposal.money_tree);
+                            our_proposal.nullifiers_smt_snapshot = Some(proposal.nullifiers_smt);
+                            our_proposal.tx_hash = Some(proposal.tx_hash);
+                            our_proposal.call_index = Some(proposal.call_idx);
+                            our_proposal
+                        }
+                        Err(_) => ProposalRecord {
+                            proposal: note,
+                            data: None,
+                            leaf_position: proposals_tree.mark(),
+                            money_snapshot_tree: Some(proposal.money_tree),
+                            nullifiers_smt_snapshot: Some(proposal.nullifiers_smt),
+                            tx_hash: Some(proposal.tx_hash),
+                            call_index: Some(proposal.call_idx),
+                        },
+                    };
 
                     our_proposals.push(our_proposal);
                     break
@@ -851,7 +890,7 @@ impl Drk {
             }
 
             let query = format!(
-                "INSERT OR REPLACE INTO {} ({}, {}, {}, {}, {}, {}, {}, {}) VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8);",
+                "INSERT OR REPLACE INTO {} ({}, {}, {}, {}, {}, {}, {}, {}, {}) VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9);",
                 *DAO_PROPOSALS_TABLE,
                 DAO_PROPOSALS_COL_BULLA,
                 DAO_PROPOSALS_COL_DAO_BULLA,
@@ -859,6 +898,7 @@ impl Drk {
                 DAO_PROPOSALS_COL_DATA,
                 DAO_PROPOSALS_COL_LEAF_POSITION,
                 DAO_PROPOSALS_COL_MONEY_SNAPSHOT_TREE,
+                DAO_PROPOSALS_COL_NULLIFIERS_SMT_SNAPSHOT,
                 DAO_PROPOSALS_COL_TX_HASH,
                 DAO_PROPOSALS_COL_CALL_INDEX,
             );
@@ -878,6 +918,13 @@ impl Drk {
                 None => None,
             };
 
+            let nullifiers_smt_snapshot = match &proposal.nullifiers_smt_snapshot {
+                Some(nullifiers_smt_snapshot) => {
+                    Some(serialize_async(nullifiers_smt_snapshot).await)
+                }
+                None => None,
+            };
+
             let tx_hash = match &proposal.tx_hash {
                 Some(tx_hash) => Some(serialize_async(tx_hash).await),
                 None => None,
@@ -892,6 +939,7 @@ impl Drk {
                     data,
                     leaf_position,
                     money_snapshot_tree,
+                    nullifiers_smt_snapshot,
                     tx_hash,
                     proposal.call_index,
                 ],
@@ -909,10 +957,11 @@ impl Drk {
     pub async fn unconfirm_proposals(&self, proposals: &[ProposalRecord]) -> WalletDbResult<()> {
         for proposal in proposals {
             let query = format!(
-                "UPDATE {} SET {} = ?1, {} = ?2, {} = ?3 , {} = ?4 WHERE {} = ?5;",
+                "UPDATE {} SET {} = ?1, {} = ?2, {} = ?3, {} = ?4, {} = ?5 WHERE {} = ?6;",
                 *DAO_PROPOSALS_TABLE,
                 DAO_PROPOSALS_COL_LEAF_POSITION,
                 DAO_PROPOSALS_COL_MONEY_SNAPSHOT_TREE,
+                DAO_PROPOSALS_COL_NULLIFIERS_SMT_SNAPSHOT,
                 DAO_PROPOSALS_COL_TX_HASH,
                 DAO_PROPOSALS_COL_CALL_INDEX,
                 DAO_PROPOSALS_COL_BULLA
@@ -923,6 +972,7 @@ impl Drk {
                     None::<Vec<u8>>,
                     None::<Vec<u8>>,
                     None::<Vec<u8>>,
+                    None::<Vec<u8>>,
                     None::<u64>,
                     serialize_async(&proposal.bulla()).await
                 ],
@@ -1000,7 +1050,7 @@ impl Drk {
         let proposals = match self.get_proposals().await {
             Ok(p) => p,
             Err(e) => {
-                println!("[reset_dao_proposals] DAO proposalss retrieval failed: {e:?}");
+                println!("[reset_dao_proposals] DAO proposals retrieval failed: {e:?}");
                 return Err(WalletDbError::GenericError);
             }
         };
@@ -1436,6 +1486,7 @@ impl Drk {
             data: Some(serialize_async(&proposal_coinattrs).await),
             leaf_position: None,
             money_snapshot_tree: None,
+            nullifiers_smt_snapshot: None,
             tx_hash: None,
             call_index: None,
         };
@@ -1680,9 +1731,10 @@ impl Drk {
             return Err(Error::Custom(format!("[dao_vote] Proposal {} was not found", proposal)))
         };
         if proposal.leaf_position.is_none() ||
+            proposal.money_snapshot_tree.is_none() ||
+            proposal.nullifiers_smt_snapshot.is_none() ||
             proposal.tx_hash.is_none() ||
-            proposal.call_index.is_none() ||
-            proposal.money_snapshot_tree.is_none()
+            proposal.call_index.is_none()
         {
             return Err(Error::Custom(
                 "[dao_vote] Proposal seems to not have been deployed yet".to_string(),
@@ -1797,13 +1849,8 @@ impl Drk {
         let current_day = blockwindow(current_block_height);
 
         // 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);
+        let store = MemoryStorageFp { tree: proposal.nullifiers_smt_snapshot.unwrap() };
+        let money_null_smt = SmtMemoryFp::new(store, PoseidonFp::new(), &EMPTY_NODES_FP);
 
         // Create the vote call
         let call = DaoVoteCall {

+ 1 - 1
bin/drk/src/main.rs

@@ -1481,7 +1481,7 @@ async fn realmain(args: Args, ex: Arc<smol::Executor<'static>>) -> Result<()> {
                     }
                 };
 
-                println!("{}", bs58::encode(&serialize_async(&tx).await).into_string());
+                println!("{}", base64::encode(&serialize_async(&tx).await));
                 Ok(())
             }
 

+ 35 - 0
bin/drk/src/money.rs

@@ -19,6 +19,7 @@
 use std::{collections::HashMap, str::FromStr};
 
 use lazy_static::lazy_static;
+use num_bigint::BigUint;
 use rand::rngs::OsRng;
 use rusqlite::types::Value;
 
@@ -45,6 +46,7 @@ use darkfi_sdk::{
     bridgetree,
     crypto::{
         note::AeadEncryptedNote,
+        pasta_prelude::PrimeField,
         smt::{PoseidonFp, EMPTY_NODES_FP},
         BaseBlind, FuncId, Keypair, MerkleNode, MerkleTree, PublicKey, ScalarBlind, SecretKey,
         MONEY_CONTRACT_ID,
@@ -682,6 +684,39 @@ impl Drk {
         Ok(tree)
     }
 
+    /// Fetch the Money nullifiers SMT from the wallet, as a map.
+    pub async fn get_nullifiers_smt(&self) -> Result<HashMap<BigUint, pallas::Base>> {
+        let rows = match self.wallet.query_multiple(&MONEY_SMT_TABLE, &[], &[]) {
+            Ok(r) => r,
+            Err(e) => {
+                return Err(Error::RusqliteError(format!(
+                    "[get_nullifiers_smt] SMT records retrieval failed: {e:?}"
+                )))
+            }
+        };
+
+        let mut smt = HashMap::new();
+        for row in rows {
+            let Value::Blob(ref key_bytes) = row[0] else {
+                return Err(Error::ParseFailed("[get_nullifiers_smt] Key bytes parsing failed"))
+            };
+            let key = BigUint::from_bytes_be(key_bytes);
+
+            let Value::Blob(ref value_bytes) = row[1] else {
+                return Err(Error::ParseFailed("[get_nullifiers_smt] Value bytes parsing failed"))
+            };
+            let mut repr = [0; 32];
+            repr.copy_from_slice(value_bytes);
+            let Some(value) = pallas::Base::from_repr(repr).into() else {
+                return Err(Error::ParseFailed("[get_nullifiers_smt] Value conversion failed"))
+            };
+
+            smt.insert(key, value);
+        }
+
+        Ok(smt)
+    }
+
     /// Get the last scanned block height from the wallet.
     pub fn last_scanned_block(&self) -> WalletDbResult<u32> {
         let ret = self.wallet.query_single(

+ 1 - 1
contrib/localnet/darkfid-single-node/README.md

@@ -74,7 +74,7 @@ of the guide can be added for future regressions.
 | 29 | DAO proposal export       | dao proposal {PROPOSAL_BULLA} --export                   | Pass               |
 | 30 | DAO proposal import       | dao proposal-import                                      | Pass               |
 | 31 | DAO proposal mint         | dao proposal {PROPOSAL_BULLA} --mint-proposal            | Pass               |
-| 32 | DAO vote                  | dao vote {PROPOSAL_BULLA} {VOTE} {WEIGHT}                | Failure: needs #31 |
+| 32 | DAO vote                  | dao vote {PROPOSAL_BULLA} {VOTE} {WEIGHT}                | Pass               |
 | 33 | DAO proposal execution    | dao exec {PROPOSAL_BULLA}                                | Failure: needs #32 |
 | 34 | Coins unspend             | unspend {COIN}                                           | Pass               |
 | 35 | Transaction inspect       | inspect                                                  | Pass               |

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

@@ -104,7 +104,7 @@ pub trait StorageAdapter {
 /// An in-memory storage, useful for unit tests and smaller trees.
 #[derive(Default, Clone)]
 pub struct MemoryStorage<F: FieldElement> {
-    tree: HashMap<BigUint, F>,
+    pub tree: HashMap<BigUint, F>,
 }
 
 impl<F: FieldElement> MemoryStorage<F> {