Browse Source

drk: Initial block scans for DAO

parazyd 3 years ago
parent
commit
faf649cff8
5 changed files with 135 additions and 32 deletions
  1. 18 0
      bin/drk/build.rs
  2. 30 20
      bin/drk/src/main.rs
  3. 51 8
      bin/drk/src/rpc_blockchain.rs
  4. 14 3
      bin/drk/src/rpc_dao.rs
  5. 22 1
      bin/drk/src/rpc_wallet.rs

+ 18 - 0
bin/drk/build.rs

@@ -1,3 +1,21 @@
+/* This file is part of DarkFi (https://dark.fi)
+ *
+ * Copyright (C) 2020-2023 Dyne.org foundation
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <https://www.gnu.org/licenses/>.
+ */
+
 use pkg_config::probe_library;
 
 fn main() {

+ 30 - 20
bin/drk/src/main.rs

@@ -164,7 +164,7 @@ enum Subcmd {
     },
 
     /// OTC atomic swap
-    #[command(subcommand)]
+    #[command(subcommand, about = cli_desc!())]
     Otc(OtcSubcmd),
 
     /// Inspect a transaction from stdin
@@ -182,7 +182,7 @@ enum Subcmd {
     Subscribe,
 
     /// DAO functionalities
-    #[command(subcommand)]
+    #[command(subcommand, about = cli_desc!())]
     Dao(DaoSubcmd),
 
     /// Scan the blockchain and parse relevant transactions
@@ -255,14 +255,14 @@ enum DaoSubcmd {
 
     /// Mint an imported DAO on-chain
     Mint {
-        /// Named identifier for the DAO
-        dao_name: String,
+        /// Numeric identifier for the DAO
+        dao_id: u64,
     },
 
     /// Create a proposal for a DAO
     Propose {
-        /// Named identifier for the DAO
-        dao_name: String,
+        /// Numeric identifier for the DAO
+        dao_id: u64,
 
         /// Pubkey to send tokens to with proposal success
         recv_pubkey: String,
@@ -278,14 +278,14 @@ enum DaoSubcmd {
 
     /// List DAO proposals
     Proposals {
-        /// Named identifier for the DAO
-        dao_name: String,
+        /// Numeric identifier for the DAO
+        dao_id: u64,
     },
 
     /// View a DAO proposal data
     Proposal {
-        /// Named identifier for the DAO
-        dao_name: String,
+        /// Numeric identifier for the DAO
+        dao_id: u64,
 
         /// Proposal identifier
         proposal: String,
@@ -293,8 +293,8 @@ enum DaoSubcmd {
 
     /// Vote on a given proposal
     Vote {
-        /// Named identifier for the DAO
-        dao_name: String,
+        /// Numeric identifier for the DAO
+        dao_id: u64,
 
         /// Proposal identifier
         proposal: String,
@@ -305,8 +305,8 @@ enum DaoSubcmd {
 
     /// Execute a DAO proposal
     Exec {
-        /// Named identifier for the DAO
-        dao_name: String,
+        /// Numeric identifier for the DAO
+        dao_id: u64,
 
         /// Proposal identifier
         proposal: String,
@@ -774,17 +774,27 @@ async fn main() -> Result<()> {
                 Ok(())
             }
 
-            DaoSubcmd::Mint { dao_name } => todo!(),
+            DaoSubcmd::Mint { dao_id } => {
+                let rpc_client = RpcClient::new(args.endpoint.clone())
+                    .await
+                    .with_context(|| "Could not connect to darkfid RPC endpoint")?;
+
+                let drk = Drk { rpc_client };
+
+                drk.dao_mint(dao_id).await.with_context(|| "Failed to mint DAO")?;
+
+                Ok(())
+            }
 
-            DaoSubcmd::Propose { dao_name, recv_pubkey, amount, token_id, serial } => todo!(),
+            DaoSubcmd::Propose { dao_id, recv_pubkey, amount, token_id, serial } => todo!(),
 
-            DaoSubcmd::Proposals { dao_name } => todo!(),
+            DaoSubcmd::Proposals { dao_id } => todo!(),
 
-            DaoSubcmd::Proposal { dao_name, proposal } => todo!(),
+            DaoSubcmd::Proposal { dao_id, proposal } => todo!(),
 
-            DaoSubcmd::Vote { dao_name, proposal, vote } => todo!(),
+            DaoSubcmd::Vote { dao_id, proposal, vote } => todo!(),
 
-            DaoSubcmd::Exec { dao_name, proposal } => todo!(),
+            DaoSubcmd::Exec { dao_id, proposal } => todo!(),
         },
     }
 }

+ 51 - 8
bin/drk/src/rpc_blockchain.rs

@@ -28,6 +28,10 @@ use darkfi::{
     tx::Transaction,
     wallet::walletdb::QueryType,
 };
+use darkfi_dao_contract::{
+    dao_model::{DaoBulla, DaoMintParams},
+    DaoFunction,
+};
 use darkfi_money_contract::{
     client::{
         Coin, EncryptedNote, OwnCoin, MONEY_COINS_COL_COIN, MONEY_COINS_COL_COIN_BLIND,
@@ -106,7 +110,8 @@ impl Drk {
                     eprintln!("=======================================");
 
                     eprintln!("Deserialized successfully. Scanning block...");
-                    self.scan_block(&block_data).await?;
+                    self.scan_block_money(&block_data).await?;
+                    self.scan_block_dao(&block_data).await?;
                 }
 
                 JsonResult::Error(e) => {
@@ -124,21 +129,56 @@ impl Drk {
         Err(e)
     }
 
-    /// `scan_block` will go over transactions in a block and fetch the ones dealing
+    /// `scan_block_dao` will go over transactions in a block and fetch the ones dealing
+    /// with the dao contract. Then over all of them, try to see if any are related
+    /// to us. If any are found, the metadata is extracted and placed into the wallet
+    /// for future use.
+    async fn scan_block_dao(&self, block: &BlockInfo) -> Result<()> {
+        eprintln!("Iterating over {} transactions", block.txs.len());
+
+        let mut minted_dao_bullas: Vec<(DaoBulla, blake3::Hash, u32)> = vec![];
+
+        for (i, tx) in block.txs.iter().enumerate() {
+            for (j, call) in tx.calls.iter().enumerate() {
+                if call.contract_id == *MONEY_CONTRACT_ID && call.data[0] == DaoFunction::Mint as u8
+                {
+                    eprintln!("Found Dao::Mint in call {} in tx {}", j, i);
+                    let params: DaoMintParams = deserialize(&call.data[1..])?;
+                    minted_dao_bullas.push((
+                        params.dao_bulla,
+                        blake3::hash(&serialize(tx)),
+                        j as u32,
+                    ));
+                    continue
+                }
+            }
+        }
+
+        let (mut daos_tree, mut proposals_tree) = self.wallet_dao_trees().await?;
+        // We assume that the state transitions are correct and won't allow a DAO being
+        // minted twice. So here we just blindly put stuff in.
+        for bulla in minted_dao_bullas {
+            daos_tree.append(&MerkleNode::from(bulla.0.inner()));
+            // TODO: If our wallet has this DAO, add the info in the row, and witness the tree
+        }
+
+        Ok(())
+    }
+
+    /// `scan_block_money` will go over transactions in a block and fetch the ones dealing
     /// with the money contract. Then over all of them, try to see if any are related
     /// to us. If any are found, the metadata is extracted and placed into the wallet
     /// for future use.
-    async fn scan_block(&self, block: &BlockInfo) -> Result<()> {
+    async fn scan_block_money(&self, block: &BlockInfo) -> Result<()> {
         eprintln!("Iterating over {} transactions", block.txs.len());
 
         let mut nullifiers: Vec<Nullifier> = vec![];
         let mut outputs: Vec<Output> = vec![];
 
-        let contract_id = *MONEY_CONTRACT_ID;
-
         for (i, tx) in block.txs.iter().enumerate() {
             for (j, call) in tx.calls.iter().enumerate() {
-                if call.contract_id == contract_id && call.data[0] == MoneyFunction::Transfer as u8
+                if call.contract_id == *MONEY_CONTRACT_ID &&
+                    call.data[0] == MoneyFunction::Transfer as u8
                 {
                     eprintln!("Found Money::Transfer in call {} in tx {}", j, i);
                     let params: MoneyTransferParams = deserialize(&call.data[1..])?;
@@ -151,7 +191,9 @@ impl Drk {
                     continue
                 }
 
-                if call.contract_id == contract_id && call.data[0] == MoneyFunction::OtcSwap as u8 {
+                if call.contract_id == *MONEY_CONTRACT_ID &&
+                    call.data[0] == MoneyFunction::OtcSwap as u8
+                {
                     eprintln!("Found Money::OtcSwap in call {} in tx {}", j, i);
                     let params: MoneyTransferParams = deserialize(&call.data[1..])?;
                     for input in params.inputs {
@@ -380,7 +422,8 @@ impl Drk {
             eprint!("Requesting slot {}... ", sl);
             if let Some(block) = self.get_block_by_slot(sl).await? {
                 eprintln!("Found");
-                self.scan_block(&block).await?;
+                self.scan_block_money(&block).await?;
+                self.scan_block_dao(&block).await?;
             } else {
                 eprintln!("Not found");
                 // Write down the slot number into back to the wallet

+ 14 - 3
bin/drk/src/rpc_dao.rs

@@ -90,7 +90,7 @@ impl Drk {
         Ok(())
     }
 
-    async fn dao_list_single(&self, dao_id: u64) -> Result<()> {
+    async fn dao_get_by_id(&self, dao_id: u64) -> Result<Dao> {
         let query =
             format!("SELECT * FROM {} WHERE {} = {}", DAO_DAOS_TABLE, DAO_DAOS_COL_DAO_ID, dao_id);
 
@@ -157,7 +157,7 @@ impl Drk {
         let tx_hash =
             if tx_hash_bytes.is_empty() { None } else { Some(deserialize(&tx_hash_bytes)?) };
 
-        let dao = Dao {
+        Ok(Dao {
             name,
             proposer_limit,
             quorum,
@@ -169,7 +169,11 @@ impl Drk {
             leaf_position,
             tx_hash,
             call_index,
-        };
+        })
+    }
+
+    async fn dao_list_single(&self, dao_id: u64) -> Result<()> {
+        let dao = self.dao_get_by_id(dao_id).await?;
 
         println!("DAO Parameters:");
         println!("Name: {}", dao.name);
@@ -224,4 +228,11 @@ impl Drk {
 
         Ok(())
     }
+
+    /// Mint a DAO on-chain
+    pub async fn dao_mint(&self, dao_id: u64) -> Result<()> {
+        let dao = self.dao_get_by_id(dao_id).await?;
+
+        Ok(())
+    }
 }

+ 22 - 1
bin/drk/src/rpc_wallet.rs

@@ -475,7 +475,7 @@ impl Drk {
         Ok(ret)
     }
 
-    /// Get the Merkle tree from the wallet
+    /// Get the Money Merkle tree from the wallet
     pub async fn wallet_tree(&self) -> Result<BridgeTree<MerkleNode, MERKLE_DEPTH>> {
         let query = format!("SELECT * FROM {}", MONEY_TREE_TABLE);
         let params = json!([query, QueryType::Blob as u8, MONEY_TREE_COL_TREE]);
@@ -487,6 +487,27 @@ impl Drk {
         Ok(tree)
     }
 
+    pub async fn wallet_dao_trees(&self) -> Result<(MerkleTree, MerkleTree)> {
+        let query = format!("SELECT * FROM {}", DAO_TREES_TABLE);
+        let params = json!([
+            query,
+            QueryType::Blob as u8,
+            DAO_TREES_COL_DAOS_TREE,
+            QueryType::Blob as u8,
+            DAO_TREES_COL_PROPOSALS_TREE
+        ]);
+        let req = JsonRequest::new("wallet.query_row_single", params);
+        let rep = self.rpc_client.request(req).await?;
+
+        let daos_tree_bytes: Vec<u8> = serde_json::from_value(rep[0].clone())?;
+        let proposals_tree_bytes: Vec<u8> = serde_json::from_value(rep[1].clone())?;
+
+        let daos_tree = deserialize(&daos_tree_bytes)?;
+        let proposals_tree = deserialize(&proposals_tree_bytes)?;
+
+        Ok((daos_tree, proposals_tree))
+    }
+
     /// Get the last scanned slot from the wallet
     pub async fn wallet_last_scanned_slot(&self) -> Result<u64> {
         let query =