Ver código fonte

darkfid: Support writing arbitrary data to wallet over JSON-RPC.

parazyd 3 anos atrás
pai
commit
ad674cba05

+ 1 - 0
bin/darkfid/Cargo.toml

@@ -29,6 +29,7 @@ serde_json = "1.0.87"
 simplelog = "0.12.0"
 sled = "0.34.7"
 smol = "1.2.5"
+libsqlite3-sys = {version = "0.24.2", features = ["bundled-sqlcipher"]}
 sqlx = {version = "0.6.2", features = ["runtime-async-std-native-tls", "sqlite"]}
 url = "2.3.1"
 

+ 6 - 1
bin/darkfid/src/error.rs

@@ -24,13 +24,15 @@ use darkfi::rpc::jsonrpc::{ErrorCode::ServerError, JsonError, JsonResult};
 /// Please sort them sensefully.
 pub enum RpcError {
     // Wallet/Key-related errors
+    NoRowsFoundInWallet = -32101,
+    /*
     Keygen = -32101,
     KeypairFetch = -32102,
     KeypairNotFound = -32103,
     InvalidKeypair = -32104,
     InvalidAddressParam = -32105,
     DecryptionFailed = -32106,
-
+    */
     // Transaction-related errors
     TxBuildFail = -32110,
     TxBroadcastFail = -32111,
@@ -49,12 +51,15 @@ pub enum RpcError {
 fn to_tuple(e: RpcError) -> (i64, String) {
     let msg = match e {
         // Wallet/Key-related errors
+        RpcError::NoRowsFoundInWallet => "No queried rows found in wallet",
+        /*
         RpcError::Keygen => "Failed generating keypair",
         RpcError::KeypairFetch => "Failed fetching keypairs from wallet",
         RpcError::KeypairNotFound => "Keypair not found",
         RpcError::InvalidKeypair => "Invalid keypair",
         RpcError::InvalidAddressParam => "Invalid address parameter",
         RpcError::DecryptionFailed => "Decryption failed",
+        */
         // Transaction-related errors
         RpcError::TxBuildFail => "Failed building transaction",
         RpcError::TxBroadcastFail => "Failed broadcasting transaction",

+ 8 - 22
bin/darkfid/src/main.rs

@@ -219,31 +219,13 @@ impl RequestHandler for Darkfid {
             Some("tx.transfer") => return self.tx_transfer(req.id, params).await,
             Some("tx.broadcast") => return self.tx_broadcast(req.id, params).await,
             */
-
             // ==============
             // Wallet methods
             // ==============
-            /*
-            Some("wallet.keygen") => return self.wallet_keygen(req.id, params).await,
-            Some("wallet.get_addrs") => return self.wallet_get_addrs(req.id, params).await,
-            Some("wallet.export_keypair") => {
-                return self.wallet_export_keypair(req.id, params).await
-            }
-            Some("wallet.import_keypair") => {
-                return self.wallet_import_keypair(req.id, params).await
-            }
-            Some("wallet.set_default_address") => {
-                return self.wallet_set_default_address(req.id, params).await
+            Some("wallet.exec_sql") => return self.wallet_exec_sql(req.id, params).await,
+            Some("wallet.query_row_single") => {
+                return self.wallet_query_row_single(req.id, params).await
             }
-            Some("wallet.get_balances") => return self.wallet_get_balances(req.id, params).await,
-            Some("wallet.get_coins_valtok") => {
-                return self.wallet_get_coins_valtok(req.id, params).await
-            }
-            Some("wallet.get_merkle_path") => {
-                return self.wallet_get_merkle_path(req.id, params).await
-            }
-            Some("wallet.decrypt_note") => return self.wallet_decrypt_note(req.id, params).await,
-            */
             // ==============
             // Invalid method
             // ==============
@@ -480,9 +462,13 @@ async fn realmain(args: Args, ex: Arc<smol::Executor<'_>>) -> Result<()> {
     print!("\r");
     info!("Caught termination signal, cleaning up and exiting...");
 
-    info!("Flushing database...");
+    info!("Flushing sled database...");
     let flushed_bytes = sled_db.flush_async().await?;
     info!("Flushed {} bytes", flushed_bytes);
 
+    info!("Closing wallet connection...");
+    wallet.conn.close().await;
+    info!("Closed wallet connection");
+
     Ok(())
 }

+ 54 - 8
bin/darkfid/src/rpc_wallet.rs

@@ -28,7 +28,7 @@ use darkfi::{
     wallet::walletdb::QueryType,
 };
 
-use super::Darkfid;
+use super::{error::RpcError, server_error, Darkfid};
 
 impl Darkfid {
     // RPCAPI:
@@ -51,8 +51,6 @@ impl Darkfid {
     // --> {"jsonrpc": "2.0", "method": "wallet.query_row_single", "params": [...], "id": 1}
     // <-- {"jsonrpc": "2.0", "result": ["va", "lu", "es", ...], "id": 1}
     pub async fn wallet_query_row_single(&self, id: Value, params: &[Value]) -> JsonResult {
-        // TODO: Better errors
-
         // We need at least 3 params for something we want to fetch, and we want them in pairs.
         // Also the first param should be a String
         if params.len() < 3 || params[1..].len() % 2 != 0 || !params[0].is_string() {
@@ -90,7 +88,7 @@ impl Darkfid {
             Ok(v) => v,
             Err(e) => {
                 error!("[RPC] wallet.query_row_single: Failed to execute SQL query: {}", e);
-                return JsonError::new(InternalError, None, id).into()
+                return server_error(RpcError::NoRowsFoundInWallet, id, None)
             }
         };
 
@@ -132,22 +130,70 @@ impl Darkfid {
 
     // RPCAPI:
     // Executes an arbitrary SQL query on the wallet, and returns `true` on success.
+    // `params[1..]` can optionally be provided in pairs like in `wallet.query_row_single`.
     //
     // --> {"jsonrpc": "2.0", "method": "wallet.exec_sql", "params": ["CREATE TABLE ..."], "id": 1}
     // <-- {"jsonrpc": "2.0", "result": true, "id": 1}
     pub async fn wallet_exec_sql(&self, id: Value, params: &[Value]) -> JsonResult {
-        if params.len() != 1 || !params[0].is_string() {
+        if params.is_empty() || !params[0].is_string() {
+            return JsonError::new(InvalidParams, None, id).into()
+        }
+
+        if params.len() > 1 && params[1..].len() % 2 != 0 {
             return JsonError::new(InvalidParams, None, id).into()
         }
 
         let query = params[0].as_str().unwrap();
         debug!("Executing SQL query: {}", query);
+        let mut query = sqlx::query(query);
 
-        if let Err(e) = self.wallet.exec_sql(query).await {
-            error!("[RPC] wallet.exec_sql: Error executing query: {}", e);
-            return JsonError::new(InternalError, None, id).into()
+        for pair in params[1..].chunks(2) {
+            if !pair[0].is_u64() || pair[0].as_u64().unwrap() >= QueryType::Last as u64 {
+                return JsonError::new(InvalidParams, None, id).into()
+            }
+
+            let typ = (pair[0].as_u64().unwrap() as u8).into();
+            match typ {
+                QueryType::Integer => {
+                    let val: i32 = match serde_json::from_value(pair[1].clone()) {
+                        Ok(v) => v,
+                        Err(e) => {
+                            error!("[RPC] wallet.exec_sql: Failed casting value to i32: {}", e);
+                            return JsonError::new(ParseError, None, id).into()
+                        }
+                    };
+
+                    query = query.bind(val);
+                }
+                QueryType::Blob => {
+                    let val: Vec<u8> = match serde_json::from_value(pair[1].clone()) {
+                        Ok(v) => v,
+                        Err(e) => {
+                            error!("[RPC] wallet.exec_sql: Failed casting value to Vec<u8>: {}", e);
+                            return JsonError::new(ParseError, None, id).into()
+                        }
+                    };
+
+                    query = query.bind(val);
+                }
+                _ => return JsonError::new(InvalidParams, None, id).into(),
+            }
         }
 
+        // Get a wallet connection
+        let mut conn = match self.wallet.conn.acquire().await {
+            Ok(v) => v,
+            Err(e) => {
+                error!("[RPC] wallet.exec_sql: Failed to acquire wallet connection: {}", e);
+                return JsonError::new(InternalError, None, id).into()
+            }
+        };
+
+        if let Err(e) = query.execute(&mut conn).await {
+            error!("[RPC] wallet.exec_sql: Failed to execute sql query: {}", e);
+            return JsonError::new(InternalError, None, id).into()
+        };
+
         JsonResponse::new(json!(true), id).into()
     }
 }

+ 362 - 0
bin/darkfid/src/rpc_wallet_old.rs

@@ -0,0 +1,362 @@
+/* This file is part of DarkFi (https://dark.fi)
+ *
+ * Copyright (C) 2020-2022 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 darkfi_sdk::crypto::{Address, Keypair, PublicKey, SecretKey, TokenId};
+use darkfi_serial::{deserialize, serialize};
+use fxhash::FxHashMap;
+use incrementalmerkletree::Tree;
+use log::error;
+use serde_json::{json, Value};
+
+use darkfi::{
+    node::State,
+    rpc::jsonrpc::{
+        ErrorCode::{InternalError, InvalidParams, ParseError},
+        JsonError, JsonResponse, JsonResult,
+    },
+};
+
+use super::Darkfid;
+use crate::{server_error, RpcError};
+
+impl Darkfid {
+    // RPCAPI:
+    // Attempts to generate a new keypair and returns its address upon success.
+    //
+    // --> {"jsonrpc": "2.0", "method": "wallet.keygen", "params": [], "id": 1}
+    // <-- {"jsonrpc": "2.0", "result": "1DarkFi...", "id": 1}
+    pub async fn wallet_keygen(&self, id: Value, params: &[Value]) -> JsonResult {
+        if !params.is_empty() {
+            return JsonError::new(InvalidParams, None, id).into()
+        }
+
+        match self.client.keygen().await {
+            Ok(a) => JsonResponse::new(json!(a.to_string()), id).into(),
+            Err(e) => {
+                error!("[RPC] wallet.keygen: Failed creating keypair: {}", e);
+                server_error(RpcError::Keygen, id, None)
+            }
+        }
+    }
+
+    // RPCAPI:
+    // Fetches public keys by given indexes from the wallet and returns it in an
+    // encoded format. `-1` is supported to fetch all available keys.
+    //
+    // --> {"jsonrpc": "2.0", "method": "wallet.get_addrs", "params": [1, 2], "id": 1}
+    // <-- {"jsonrpc": "2.0", "result": ["foo", "bar"], "id": 1}
+    pub async fn wallet_get_addrs(&self, id: Value, params: &[Value]) -> JsonResult {
+        if params.is_empty() {
+            return JsonError::new(InvalidParams, None, id).into()
+        }
+
+        let mut fetch_all = false;
+
+        for (i, elem) in params.iter().enumerate() {
+            if !elem.is_i64() {
+                error!("[RPC] wallet.get_addrs: Param {} is not i64", i);
+                return server_error(RpcError::NaN, id, Some(&format!("Param {} is not i64", i)))
+            }
+
+            if elem.as_i64() == Some(-1) {
+                if params.len() != 1 {
+                    return server_error(
+                        RpcError::ParseError,
+                        id,
+                        Some("-1 can only be used as a single param"),
+                    )
+                }
+
+                fetch_all = true;
+                break
+            }
+
+            if elem.as_i64() < Some(-1) {
+                return server_error(RpcError::LessThanNegOne, id, None)
+            }
+        }
+
+        let keypairs = match self.client.get_keypairs().await {
+            Ok(v) => v,
+            Err(e) => {
+                error!("[RPC] wallet.get_addrs: Failed fetching keypairs: {}", e);
+                return server_error(RpcError::KeypairFetch, id, None)
+            }
+        };
+
+        if fetch_all {
+            let ret: Vec<String> =
+                keypairs.iter().map(|x| Address::from(x.public).to_string()).collect();
+            return JsonResponse::new(json!(ret), id).into()
+        }
+
+        let mut ret = vec![];
+        for i in params {
+            // This cast is safe on 64bit since we've already sorted out
+            // all negative cases above.
+            let idx = i.as_i64().unwrap() as usize;
+            if let Some(kp) = keypairs.get(idx) {
+                ret.push(Some(Address::from(kp.public).to_string()));
+            } else {
+                ret.push(None)
+            }
+        }
+
+        JsonResponse::new(json!(ret), id).into()
+    }
+
+    // RPCAPI:
+    // Exports the given keypair index.
+    // Returns the encoded secret key upon success.
+    //
+    // --> {"jsonrpc": "2.0", "method": "wallet.export_keypair", "params": [0], "id": 1}
+    // <-- {"jsonrpc": "2.0", "result": "foobar", "id": 1}
+    pub async fn wallet_export_keypair(&self, id: Value, params: &[Value]) -> JsonResult {
+        if params.len() != 1 || !params[0].is_u64() {
+            return JsonError::new(InvalidParams, None, id).into()
+        }
+
+        let keypairs = match self.client.get_keypairs().await {
+            Ok(v) => v,
+            Err(e) => {
+                error!("[RPC] wallet.export_keypair: Failed fetching keypairs: {}", e);
+                return server_error(RpcError::KeypairFetch, id, None)
+            }
+        };
+
+        if let Some(kp) = keypairs.get(params[0].as_u64().unwrap() as usize) {
+            return JsonResponse::new(json!(serialize(&kp.secret)), id).into()
+        }
+
+        server_error(RpcError::KeypairNotFound, id, None)
+    }
+
+    // RPCAPI:
+    // Imports a given secret key into the wallet as a keypair.
+    // Returns the public counterpart as the result upon success.
+    //
+    // --> {"jsonrpc": "2.0", "method": "wallet.import_keypair", "params": ["foobar"], "id": 1}
+    // <-- {"jsonrpc": "2.0", "result": "pubfoobar", "id": 1}
+    pub async fn wallet_import_keypair(&self, id: Value, params: &[Value]) -> JsonResult {
+        if params.len() != 1 || !params[0].is_string() {
+            return JsonError::new(InvalidParams, None, id).into()
+        }
+
+        let bytes: [u8; 32] = match serde_json::from_str(params[0].as_str().unwrap()) {
+            Ok(v) => v,
+            Err(e) => {
+                error!("[RPC] wallet.import_keypair: Failed parsing secret key from string: {}", e);
+                return server_error(RpcError::InvalidKeypair, id, None)
+            }
+        };
+
+        let secret = match SecretKey::from_bytes(bytes) {
+            Ok(v) => v,
+            Err(e) => {
+                error!("[RPC] wallet.import_keypair: Failed parsing secret key from string: {}", e);
+                return server_error(RpcError::InvalidKeypair, id, None)
+            }
+        };
+
+        let public = PublicKey::from_secret(secret);
+        let keypair = Keypair { secret, public };
+        let address = Address::from(public).to_string();
+
+        if let Err(e) = self.client.put_keypair(&keypair).await {
+            error!("[RPC] wallet.import_keypair: Failed inserting keypair into wallet: {}", e);
+            return JsonError::new(InternalError, None, id).into()
+        }
+
+        JsonResponse::new(json!(address), id).into()
+    }
+
+    // RPCAPI:
+    // Sets the default wallet address to the given index.
+    // Returns `true` upon success.
+    //
+    // --> {"jsonrpc": "2.0", "method": "wallet.set_default_address", "params": [2], "id": 1}
+    // <-- {"jsonrpc": "2.0", "result": true, "id": 1}
+    pub async fn wallet_set_default_address(&self, id: Value, params: &[Value]) -> JsonResult {
+        if params.len() != 1 || !params[0].is_u64() {
+            return JsonError::new(InvalidParams, None, id).into()
+        }
+
+        let idx = params[0].as_u64().unwrap();
+
+        let keypairs = match self.client.get_keypairs().await {
+            Ok(v) => v,
+            Err(e) => {
+                error!("[RPC] wallet.set_default_address: Failed fetching keypairs: {}", e);
+                return server_error(RpcError::KeypairFetch, id, None)
+            }
+        };
+
+        if keypairs.len() as u64 != idx - 1 {
+            return server_error(RpcError::KeypairNotFound, id, None)
+        }
+
+        let kp = keypairs[idx as usize];
+
+        if let Err(e) = self.client.set_default_keypair(&kp.public).await {
+            error!("[RPC] wallet.set_default_address: Failed setting default keypair: {}", e);
+            return JsonError::new(InternalError, None, id).into()
+        }
+
+        JsonResponse::new(json!(true), id).into()
+    }
+
+    // RPCAPI:
+    // Queries the wallet for known tokens with active balances.
+    // Returns a map of balances, indexed by the token ID.
+    //
+    // --> {"jsonrpc": "2.0", "method": "wallet.get_balances", "params": [], "id": 1}
+    // <-- {"jsonrpc": "2.0", "result": [{"1Foobar...": 100}, {...}]", "id": 1}
+    pub async fn wallet_get_balances(&self, id: Value, _params: &[Value]) -> JsonResult {
+        let balances = match self.client.get_balances().await {
+            Ok(v) => v,
+            Err(e) => {
+                error!("[RPC] wallet.get_balances: Failed fetching balances from wallet: {}", e);
+                return JsonError::new(InternalError, None, id).into()
+            }
+        };
+
+        // k: token_id, v: [amount]
+        let mut ret: FxHashMap<String, u64> = FxHashMap::default();
+
+        for balance in balances.list {
+            let token_id = format!("{}", TokenId::from(balance.token_id));
+            let mut amount = balance.value;
+
+            if let Some(prev) = ret.get(&token_id) {
+                amount += prev;
+            }
+
+            ret.insert(token_id, amount);
+        }
+
+        JsonResponse::new(json!(ret), id).into()
+    }
+
+    // RPCAPI:
+    // Queries the wallet for a coin containing given parameters (value, token_id, unspent),
+    // and returns the entire row with the coin's data:
+    //
+    // --> {"jsonrpc": "2.0", "method": "wallet.get_coins_valtok", "params": [1234, "F00b4r...", true], "id": 1}
+    // <-- {"jsonrpc": "2.0", "result": ["coin", "data", ...], "id": 1}
+    pub async fn wallet_get_coins_valtok(&self, id: Value, params: &[Value]) -> JsonResult {
+        if params.len() != 3 ||
+            !params[0].is_u64() ||
+            !params[1].is_string() ||
+            !params[2].is_boolean()
+        {
+            return JsonError::new(InvalidParams, None, id).into()
+        }
+
+        let value = params[0].as_u64().unwrap();
+        let unspent = params[2].as_bool().unwrap();
+        let token_id = match TokenId::try_from(params[1].as_str().unwrap()) {
+            Ok(v) => v,
+            Err(e) => {
+                error!("[RPC] wallet.get_coins_valtok: Failed parsing token_id from base58: {}", e);
+                return JsonError::new(ParseError, None, id).into()
+            }
+        };
+
+        let coins = match self.client.get_coins_valtok(value, token_id, unspent).await {
+            Ok(v) => v,
+            Err(e) => {
+                error!("[RPC] wallet.get_coins_valtok: Failed fetching from wallet: {}", e);
+                return JsonError::new(InternalError, None, id).into()
+            }
+        };
+
+        let ret: Vec<String> =
+            coins.iter().map(|x| bs58::encode(serialize(x)).into_string()).collect();
+        JsonResponse::new(json!(ret), id).into()
+    }
+
+    // RPCAPI:
+    // Query the state merkle tree for the merkle path of a given leaf position.
+    //
+    // --> {"jsonrpc": "2.0", "method": "wallet.get_merkle_path", "params": [3], "id": 1}
+    // <-- {"jsonrpc": "2.0", "result": ["f091uf1...", "081ff0h10w1h0...", ...], "id": 1}
+    pub async fn wallet_get_merkle_path(&self, id: Value, params: &[Value]) -> JsonResult {
+        if params.len() != 1 || !params[0].is_u64() {
+            return JsonError::new(InvalidParams, None, id).into()
+        }
+
+        let leaf_pos: incrementalmerkletree::Position =
+            ((params[0].as_u64().unwrap() as u64) as usize).into();
+
+        let validator_state = self.validator_state.read().await;
+        let state = validator_state.state_machine.lock().await;
+        let root = state.tree.root(0).unwrap();
+        let merkle_path = state.tree.authentication_path(leaf_pos, &root).unwrap();
+        drop(state);
+        drop(validator_state);
+
+        let ret: Vec<String> =
+            merkle_path.iter().map(|x| bs58::encode(serialize(x)).into_string()).collect();
+        JsonResponse::new(json!(ret), id).into()
+    }
+
+    // RPCAPI:
+    // Try to decrypt a given encrypted note with the secret keys
+    // found in the wallet.
+    //
+    // --> {"jsonrpc": "2.0", "method": "wallet.decrypt_note", params": [ciphertext], "id": 1}
+    // <-- {"jsonrpc": "2.0", "result": "base58_encoded_plain_note", "id": 1}
+    pub async fn wallet_decrypt_note(&self, id: Value, params: &[Value]) -> JsonResult {
+        if params.len() != 1 || !params[0].is_string() {
+            return JsonError::new(InvalidParams, None, id).into()
+        }
+
+        let bytes = match bs58::decode(params[0].as_str().unwrap()).into_vec() {
+            Ok(v) => v,
+            Err(e) => {
+                error!("[RPC] wallet.decrypt_note: Failed decoding base58 string: {}", e);
+                return JsonError::new(ParseError, None, id).into()
+            }
+        };
+
+        let enc_note = match deserialize(&bytes) {
+            Ok(v) => v,
+            Err(e) => {
+                error!("[RPC] wallet.decrypt_note: Failed deserializing into EncryptedNote: {}", e);
+                return JsonError::new(InternalError, None, id).into()
+            }
+        };
+
+        let keypairs = match self.client.get_keypairs().await {
+            Ok(v) => v,
+            Err(e) => {
+                error!("[RPC] wallet.decrypt_note: Failed fetching keypairs: {}", e);
+                return JsonError::new(InternalError, None, id).into()
+            }
+        };
+
+        for kp in keypairs {
+            if let Some(note) = State::try_decrypt_note(&enc_note, kp.secret) {
+                let s = bs58::encode(&serialize(&note)).into_string();
+                return JsonResponse::new(json!(s), id).into()
+            }
+        }
+
+        server_error(RpcError::DecryptionFailed, id, None)
+    }
+}