소스 검색

drk2: initialize schemas

aggstam 2 년 전
부모
커밋
8af99afe71
7개의 변경된 파일272개의 추가작업 그리고 5개의 파일을 삭제
  1. 4 0
      bin/drk2/Cargo.toml
  2. 89 0
      bin/drk2/src/dao.rs
  3. 1 0
      bin/drk2/src/error.rs
  4. 38 2
      bin/drk2/src/main.rs
  5. 114 0
      bin/drk2/src/money.rs
  6. 17 3
      bin/drk2/src/walletdb.rs
  7. 9 0
      bin/drk2/wallet.sql

+ 4 - 0
bin/drk2/Cargo.toml

@@ -11,6 +11,10 @@ edition = "2021"
 [dependencies]
 # Darkfi
 darkfi = {path = "../../", features = ["async-daemonize", "rpc"]}
+darkfi_money_contract = {path = "../../src/contract/money", features = ["no-entrypoint", "client"]}
+darkfi_dao_contract = {path = "../../src/contract/dao", features = ["no-entrypoint", "client"]}
+darkfi-sdk = {path = "../../src/sdk", features = ["async"]}
+darkfi-serial = {path = "../../src/serial"}
 
 # Misc
 log = "0.4.20"

+ 89 - 0
bin/drk2/src/dao.rs

@@ -0,0 +1,89 @@
+/* This file is part of DarkFi (https://dark.fi)
+ *
+ * Copyright (C) 2020-2024 Dyne.org foundation
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <https://www.gnu.org/licenses/>.
+ */
+
+use std::process::exit;
+
+use darkfi::Result;
+use darkfi_dao_contract::client::{
+    DAO_TREES_COL_DAOS_TREE, DAO_TREES_COL_PROPOSALS_TREE, DAO_TREES_TABLE,
+};
+use darkfi_sdk::crypto::MerkleTree;
+use darkfi_serial::serialize;
+
+use crate::Drk;
+
+impl Drk {
+    /// Initialize wallet with tables for the DAO contract
+    pub async fn initialize_dao(&self) -> Result<()> {
+        // Initialize DAO wallet schema
+        let wallet_schema = include_str!("../../../src/contract/dao/wallet.sql");
+        if let Err(e) = self.wallet.exec_batch_sql(wallet_schema).await {
+            eprintln!("Error initializing DAO schema: {e:?}");
+            exit(2);
+        }
+
+        // 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, vec![DAO_TREES_COL_DAOS_TREE], &[])
+            .await
+            .is_err()
+        {
+            eprintln!("Initializing DAO Merkle trees");
+            let tree = MerkleTree::new(100);
+            self.put_dao_trees(&tree, &tree).await?;
+            eprintln!("Successfully initialized Merkle trees for the DAO contract");
+        }
+
+        Ok(())
+    }
+
+    /// Replace the DAO Merkle trees in the wallet.
+    pub async fn put_dao_trees(
+        &self,
+        daos_tree: &MerkleTree,
+        proposals_tree: &MerkleTree,
+    ) -> Result<()> {
+        // First we remove old records
+        let query = format!("DELETE FROM {};", DAO_TREES_TABLE);
+        if let Err(e) = self.wallet.exec_sql(&query, &[]).await {
+            eprintln!("Error removing DAO trees: {e:?}");
+            exit(2);
+        }
+
+        // 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,
+        );
+        if let Err(e) = self
+            .wallet
+            .exec_sql(&query, rusqlite::params![serialize(daos_tree), serialize(proposals_tree)])
+            .await
+        {
+            eprintln!("Error replacing DAO trees: {e:?}");
+            exit(2);
+        }
+
+        Ok(())
+    }
+}

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

@@ -34,4 +34,5 @@ pub enum WalletDbError {
     QueryExecutionFailed = -32121,
     QueryFinalizationFailed = -32122,
     ParseColumnValueError = -32123,
+    RowNotFound = -32124,
 }

+ 38 - 2
bin/drk2/src/main.rs

@@ -16,7 +16,7 @@
  * along with this program.  If not, see <https://www.gnu.org/licenses/>.
  */
 
-use std::{process::exit, sync::Arc, time::Instant};
+use std::{fs, process::exit, sync::Arc, time::Instant};
 
 use smol::stream::StreamExt;
 use structopt_toml::{serde::Deserialize, structopt::StructOpt, StructOptToml};
@@ -36,6 +36,12 @@ mod error;
 mod cli_util;
 use cli_util::kaching;
 
+/// Wallet functionality related to Money
+mod money;
+
+/// Wallet functionality related to Dao
+mod dao;
+
 /// Wallet database operations handler
 mod walletdb;
 use walletdb::{WalletDb, WalletPtr};
@@ -136,17 +142,38 @@ impl Drk {
         endpoint: Url,
         ex: Arc<smol::Executor<'static>>,
     ) -> Result<Self> {
-        let wallet = match WalletDb::new(Some(expand_path(&wallet_path)?), Some(&wallet_pass)) {
+        // Initialize wallet
+        let wallet_path = expand_path(&wallet_path)?;
+        if !wallet_path.exists() {
+            if let Some(parent) = wallet_path.parent() {
+                fs::create_dir_all(parent)?;
+            }
+        }
+        let wallet = match WalletDb::new(Some(wallet_path), Some(&wallet_pass)) {
             Ok(w) => w,
             Err(e) => {
                 eprintln!("Error initializing wallet: {e:?}");
                 exit(2);
             }
         };
+
+        // Initialize rpc client
         let rpc_client = RpcClient::new(endpoint, ex).await?;
+
         Ok(Self { wallet, rpc_client })
     }
 
+    /// Initialize wallet with tables for drk
+    async fn initialize_wallet(&self) -> Result<()> {
+        let wallet_schema = include_str!("../wallet.sql");
+        if let Err(e) = self.wallet.exec_batch_sql(wallet_schema).await {
+            eprintln!("Error initializing wallet: {e:?}");
+            exit(2);
+        }
+
+        Ok(())
+    }
+
     /// Auxilliary function to ping configured darkfid daemon for liveness.
     async fn ping(&self) -> Result<()> {
         eprintln!("Executing ping request to darkfid...");
@@ -197,6 +224,15 @@ async fn realmain(args: Args, ex: Arc<smol::Executor<'static>>) -> Result<()> {
                 exit(2);
             }
 
+            let drk = Drk::new(args.wallet_path, args.wallet_pass, args.endpoint, ex).await?;
+
+            if initialize {
+                drk.initialize_wallet().await?;
+                drk.initialize_money().await?;
+                drk.initialize_dao().await?;
+                return Ok(())
+            }
+
             // TODO
 
             Ok(())

+ 114 - 0
bin/drk2/src/money.rs

@@ -0,0 +1,114 @@
+/* This file is part of DarkFi (https://dark.fi)
+ *
+ * Copyright (C) 2020-2024 Dyne.org foundation
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <https://www.gnu.org/licenses/>.
+ */
+
+use std::process::exit;
+
+use rusqlite::types::Value;
+
+use darkfi::{zk::halo2::Field, Result};
+use darkfi_money_contract::client::{
+    MONEY_INFO_COL_LAST_SCANNED_SLOT, MONEY_INFO_TABLE, MONEY_TREE_COL_TREE, MONEY_TREE_TABLE,
+};
+use darkfi_sdk::{
+    crypto::{MerkleNode, MerkleTree},
+    pasta::pallas,
+};
+use darkfi_serial::serialize;
+
+use crate::{
+    error::{WalletDbError, WalletDbResult},
+    Drk,
+};
+
+impl Drk {
+    /// Initialize wallet with tables for the Money contract
+    pub async fn initialize_money(&self) -> Result<()> {
+        // Initialize Money wallet schema
+        let wallet_schema = include_str!("../../../src/contract/money/wallet.sql");
+        if let Err(e) = self.wallet.exec_batch_sql(wallet_schema).await {
+            eprintln!("Error initializing Money schema: {e:?}");
+            exit(2);
+        }
+
+        // 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
+        // new tree and push it into the table.
+        // 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(MONEY_TREE_TABLE, vec![MONEY_TREE_COL_TREE], &[]).await.is_err()
+        {
+            eprintln!("Initializing Money Merkle tree");
+            let mut tree = MerkleTree::new(100);
+            tree.append(MerkleNode::from(pallas::Base::ZERO));
+            let _ = tree.mark().unwrap();
+            self.put_money_tree(&tree).await?;
+            eprintln!("Successfully initialized Merkle tree for the Money contract");
+        }
+
+        // We maintain the last scanned slot as part of the Money contract,
+        // but at this moment it is also somewhat applicable to DAO scans.
+        if self.last_scanned_slot().await.is_err() {
+            let query = format!(
+                "INSERT INTO {} ({}) VALUES (?1);",
+                MONEY_INFO_TABLE, MONEY_INFO_COL_LAST_SCANNED_SLOT
+            );
+            if let Err(e) = self.wallet.exec_sql(&query, rusqlite::params![0]).await {
+                eprintln!("Error inserting last scanned slot: {e:?}");
+                exit(2);
+            }
+        }
+
+        Ok(())
+    }
+
+    /// Replace the Money Merkle tree in the wallet.
+    pub async fn put_money_tree(&self, tree: &MerkleTree) -> Result<()> {
+        // First we remove old record
+        let query = format!("DELETE FROM {};", MONEY_TREE_TABLE);
+        if let Err(e) = self.wallet.exec_sql(&query, &[]).await {
+            eprintln!("Error removing Money tree: {e:?}");
+            exit(2);
+        }
+
+        // then we insert the new one
+        let query =
+            format!("INSERT INTO {} ({}) VALUES (?1);", MONEY_TREE_TABLE, MONEY_TREE_COL_TREE,);
+        if let Err(e) = self.wallet.exec_sql(&query, rusqlite::params![serialize(tree)]).await {
+            eprintln!("Error replacing Money tree: {e:?}");
+            exit(2);
+        }
+
+        Ok(())
+    }
+
+    /// Get the last scanned slot from the wallet
+    pub async fn last_scanned_slot(&self) -> WalletDbResult<u64> {
+        let ret = self
+            .wallet
+            .query_single(MONEY_INFO_TABLE, vec![MONEY_INFO_COL_LAST_SCANNED_SLOT], &[])
+            .await?;
+        let Value::Integer(slot) = ret[0] else {
+            return Err(WalletDbError::ParseColumnValueError);
+        };
+        let Ok(slot) = u64::try_from(slot) else {
+            return Err(WalletDbError::ParseColumnValueError);
+        };
+
+        Ok(slot)
+    }
+}

+ 17 - 3
bin/drk2/src/walletdb.rs

@@ -18,7 +18,7 @@
 
 use std::{path::PathBuf, sync::Arc};
 
-use log::{debug, error, info};
+use log::{debug, error};
 use rusqlite::{
     types::{ToSql, Value},
     Connection,
@@ -56,10 +56,23 @@ impl WalletDb {
             return Err(WalletDbError::PragmaUpdateError);
         };
 
-        info!(target: "walletdb::new", "[WalletDb] Opened Sqlite connection at \"{path:?}\"");
+        debug!(target: "walletdb::new", "[WalletDb] Opened Sqlite connection at \"{path:?}\"");
         Ok(Arc::new(Self { conn: Mutex::new(conn) }))
     }
 
+    /// 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<()> {
+        debug!(target: "walletdb::exec_batch_sql", "[WalletDb] Executing batch SQL query:\n{query}");
+        // If no params are provided, execute directly
+        if let Err(e) = self.conn.lock().await.execute_batch(query) {
+            error!(target: "walletdb::exec_batch_sql", "[WalletDb] Query failed: {e}");
+            return Err(WalletDbError::QueryExecutionFailed)
+        };
+
+        Ok(())
+    }
+
     /// 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<()> {
@@ -76,6 +89,7 @@ impl WalletDb {
         // First we prepare the query
         let conn = self.conn.lock().await;
         let Ok(mut stmt) = conn.prepare(query) else {
+            eprintln!("Error: {:?}", conn.prepare(query));
             return Err(WalletDbError::QueryPreparationFailed)
         };
 
@@ -131,7 +145,7 @@ impl WalletDb {
         let Ok(next) = rows.next() else { return Err(WalletDbError::QueryExecutionFailed) };
         let row = match next {
             Some(row_result) => row_result,
-            None => return Ok(vec![]),
+            None => return Err(WalletDbError::RowNotFound),
         };
 
         // Grab returned values

+ 9 - 0
bin/drk2/wallet.sql

@@ -0,0 +1,9 @@
+-- Wallet definitions for drk.
+-- We store data that is needed for wallet operations.
+
+-- Broadcasted transactions history
+CREATE TABLE IF NOT EXISTS transactions_history (
+    transaction_hash TEXT PRIMARY KEY NOT NULL,
+    status TEXT NOT NULL,
+	tx BLOB NOT NULL
+);