Jelajahi Sumber

darkfid: More WIP for new RPC.

parazyd 4 tahun lalu
induk
melakukan
ecca1a26ce

+ 7 - 0
Cargo.lock

@@ -1728,6 +1728,7 @@ dependencies = [
  "darkfi",
  "easy-parallel",
  "futures-lite",
+ "lazy-init",
  "log",
  "serde",
  "serde_derive",
@@ -3198,6 +3199,12 @@ dependencies = [
  "log",
 ]
 
+[[package]]
+name = "lazy-init"
+version = "0.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "23517540be87a91d06324e6bf6286ba8214171123ee8862ae9a5e7d938d71815"
+
 [[package]]
 name = "lazy_static"
 version = "1.4.0"

+ 2 - 1
bin/darkfid2/Cargo.toml

@@ -13,9 +13,10 @@ async-channel = "1.6.1"
 async-executor = "1.4.1"
 async-std = "1.11.0"
 async-trait = "0.1.53"
-darkfi = {path = "../../", features = ["crypto", "rpc", "net"]}
+darkfi = {path = "../../", features = ["blockchain2", "wallet", "rpc", "net"]}
 easy-parallel = "3.2.0"
 futures-lite = "1.12.0"
+lazy-init = "0.5.0"
 log = "0.4.16"
 serde_json = "1.0.79"
 simplelog = "0.12.0-alpha1"

+ 3 - 0
bin/darkfid2/darkfid_config.toml

@@ -9,6 +9,9 @@
 # Path to the wallet database
 #wallet_path = "~/.config/darkfi/darkfid_wallet.db"
 
+# Wallet password
+wallet_pass = "changeme"
+
 # JSON-RPC listening url
 #rpc_listen = "tcp://127.0.0.1:5397"
 #rpc_listen = "tls://127.0.0.1:5397"

+ 52 - 0
bin/darkfid2/src/client.rs

@@ -0,0 +1,52 @@
+use lazy_init::Lazy;
+use log::info;
+
+use darkfi::{
+    crypto::{address::Address, keypair::Keypair, proof::ProvingKey},
+    wallet::walletdb::WalletPtr,
+    Result,
+};
+
+pub struct Client {
+    main_keypair: Keypair,
+    wallet: WalletPtr,
+    mint_pk: Lazy<ProvingKey>,
+    burn_pk: Lazy<ProvingKey>,
+}
+
+impl Client {
+    pub async fn new(wallet: WalletPtr) -> Result<Self> {
+        // Initialize or load the wallet
+        wallet.init_db().await?;
+
+        // Check if there is a default keypair and generate one in
+        // case we don't have any.
+        if wallet.get_default_keypair().await.is_err() {
+            // TODO: Clean this up with Option<T> to have less calls.
+            if wallet.get_keypairs().await?.is_empty() {
+                wallet.keygen().await?;
+            }
+
+            wallet.set_default_keypair(&wallet.get_keypairs().await?[0].public).await?;
+        }
+
+        // Generate Merkle Tree if we don't have one.
+        // if wallet.get_tree().await.is_err() {
+        // wallet.tree_gen().await?;
+        // }
+
+        let main_keypair = wallet.get_default_keypair().await?;
+        info!(target: "CLIENT", "Main keypair: {}", Address::from(main_keypair.public));
+
+        Ok(Self { main_keypair, wallet, mint_pk: Lazy::new(), burn_pk: Lazy::new() })
+    }
+
+    pub async fn keygen(&self) -> Result<Address> {
+        let kp = self.wallet.keygen().await?;
+        Ok(Address::from(kp.public))
+    }
+
+    pub async fn get_keypairs(&self) -> Result<Vec<Keypair>> {
+        self.wallet.get_keypairs().await
+    }
+}

+ 34 - 0
bin/darkfid2/src/error.rs

@@ -0,0 +1,34 @@
+use serde_json::Value;
+
+use darkfi::rpc::{
+    jsonrpc,
+    jsonrpc::{ErrorCode::ServerError, JsonResult},
+};
+
+const ERROR_KEYGEN: i64 = -32101;
+const ERROR_NAN: i64 = -32102;
+const ERROR_LT1: i64 = -32103;
+const ERROR_KP_FETCH: i64 = -32104;
+
+pub fn err_keygen(id: Value) -> JsonResult {
+    jsonrpc::error(ServerError(ERROR_KEYGEN), Some("Failed generating keypair".to_string()), id)
+        .into()
+}
+
+pub fn err_nan(id: Value) -> JsonResult {
+    jsonrpc::error(ServerError(ERROR_NAN), Some("Not a number".to_string()), id).into()
+}
+
+pub fn err_lt1(id: Value) -> JsonResult {
+    jsonrpc::error(ServerError(ERROR_LT1), Some("Number cannot be lower than -1".to_string()), id)
+        .into()
+}
+
+pub fn err_kp_fetch(id: Value) -> JsonResult {
+    jsonrpc::error(
+        ServerError(ERROR_KP_FETCH),
+        Some("Failed fetching keypairs from wallet".to_string()),
+        id,
+    )
+    .into()
+}

+ 127 - 31
bin/darkfid2/src/main.rs

@@ -3,6 +3,7 @@ use async_std::sync::Arc;
 use async_trait::async_trait;
 use easy_parallel::Parallel;
 use futures_lite::future;
+use log::error;
 use serde_derive::Deserialize;
 use serde_json::{json, Value};
 use simplelog::{ColorChoice, TermLogger, TerminalMode};
@@ -12,7 +13,7 @@ use url::Url;
 
 use darkfi::{
     cli_desc,
-    net::transport::{TcpTransport, TlsTransport},
+    crypto::address::Address,
     rpc::{
         jsonrpc,
         jsonrpc::{ErrorCode, JsonRequest, JsonResult},
@@ -20,11 +21,19 @@ use darkfi::{
     },
     util::{
         cli::{log_config, spawn_config},
+        expand_path,
         path::get_config_path,
     },
-    Error, Result,
+    wallet::walletdb::{WalletDb, WalletPtr},
+    Result,
 };
 
+mod client;
+use client::Client;
+
+mod error;
+use error::*;
+
 const CONFIG_FILE: &str = "darkfid_config.toml";
 const CONFIG_FILE_CONTENTS: &str = include_str!("../darkfid_config.toml");
 
@@ -36,76 +45,163 @@ struct Args {
     /// Configuration file to use
     config: Option<String>,
 
-    #[structopt(long, default_value = "~/.config/darkfi/darkfid_client.db")]
-    /// Path to the client database
-    database_path: String,
-
     #[structopt(long, default_value = "~/.config/darkfi/darkfid_wallet.db")]
     /// Path to the wallet database
     wallet_path: String,
 
+    #[structopt(long, default_value = "changeme")]
+    /// Password for the wallet database
+    wallet_pass: String,
+
     #[structopt(long, default_value = "tcp://127.0.0.1:5397")]
     /// JSON-RPC listen URL
-    rpc_listen: String,
+    rpc_listen: Url,
 
     #[structopt(short, parse(from_occurrences))]
     /// Increase verbosity (-vvv supported)
     verbose: u8,
 }
 
-pub struct Darkfid;
+pub struct Darkfid {
+    client: Client,
+}
 
 #[async_trait]
 impl RequestHandler for Darkfid {
     async fn handle_request(&self, req: JsonRequest) -> JsonResult {
-        if req.params.as_array().is_none() {
+        if !req.params.is_array() {
             return jsonrpc::error(ErrorCode::InvalidParams, None, req.id).into()
         }
 
+        let params = req.params.as_array().unwrap();
+
         match req.method.as_str() {
-            Some("ping") => return self.pong(req.id, req.params).await,
+            Some("ping") => return self.pong(req.id, params).await,
+            Some("keygen") => return self.keygen(req.id, params).await,
+            Some("get_key") => return self.get_key(req.id, params).await,
             Some(_) | None => return jsonrpc::error(ErrorCode::MethodNotFound, None, req.id).into(),
         }
     }
 }
 
 impl Darkfid {
+    pub async fn new(wallet: WalletPtr) -> Result<Self> {
+        let client = Client::new(wallet).await?;
+        Ok(Self { client })
+    }
+
     // RPCAPI:
     // Returns a `pong` to the `ping` request.
-    // --> {"jsonrpc":"2.0","method":"ping","params":[],"id":1}
-    // <-- {"jsonrpc":"2.0","result":"pong","id":1}
-    async fn pong(&self, id: Value, _params: Value) -> JsonResult {
+    // --> {"jsonrpc": "2.0", "method": "ping", "params": [], "id": 1}
+    // <-- {"jsonrpc": "2.0", "result": "pong", "id": 1}
+    async fn pong(&self, id: Value, _params: &[Value]) -> JsonResult {
         jsonrpc::response(json!("pong"), id).into()
     }
+
+    // RPCAPI:
+    // Attempts to generate a new keypair and returns its address upon success.
+    // --> {"jsonrpc": "2.0", "method": "keygen", "params": [], "id": 1}
+    // <-- {"jsonrpc": "2.0", "result": "1DarkFi...", "id": 1}
+    async fn keygen(&self, id: Value, _params: &[Value]) -> JsonResult {
+        match self.client.keygen().await {
+            Ok(a) => jsonrpc::response(json!(a.to_string()), id).into(),
+            Err(e) => {
+                error!("Failed creating keypair: {}", e);
+                err_keygen(id)
+            }
+        }
+    }
+
+    // RPCAPI:
+    // Fetches a keypair 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": "get_key", "params": [1, 2], "id": 1}
+    // <-- {"jsonrpc": "2.0", "result": ["foo", "bar"], "id": 1}
+    async fn get_key(&self, id: Value, params: &[Value]) -> JsonResult {
+        let mut fetch_all = false;
+        for i in params {
+            if !i.is_i64() {
+                return err_nan(id)
+            }
+
+            if i.as_i64() == Some(-1) {
+                fetch_all = true;
+                break
+            }
+
+            if i.as_i64() < Some(-1) {
+                return err_lt1(id)
+            }
+        }
+
+        let keypairs = match self.client.get_keypairs().await {
+            Ok(v) => v,
+            Err(e) => {
+                error!("Failed fetching keypairs: {}", e);
+                return err_kp_fetch(id)
+            }
+        };
+
+        let mut ret = vec![];
+
+        if fetch_all {
+            ret = keypairs.iter().map(|x| Some(Address::from(x.public).to_string())).collect()
+        } else {
+            for i in params {
+                // This cast is safe 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)
+                }
+            }
+        }
+
+        jsonrpc::response(json!(ret), id).into()
+    }
+}
+
+async fn init_wallet(wallet_path: &str, wallet_pass: &str) -> Result<WalletPtr> {
+    let expanded = expand_path(wallet_path)?;
+    let wallet_path = format!("sqlite://{}", expanded.to_str().unwrap());
+    let wallet = WalletDb::new(&wallet_path, wallet_pass).await?;
+    Ok(wallet)
 }
 
 fn main() -> Result<()> {
     let args = Args::from_args_with_toml("").unwrap();
-    let cfg_path = get_config_path(args.config.clone(), CONFIG_FILE)?;
+    let cfg_path = get_config_path(args.config, CONFIG_FILE)?;
     spawn_config(&cfg_path, CONFIG_FILE_CONTENTS.as_bytes())?;
     let args = Args::from_args_with_toml(&std::fs::read_to_string(cfg_path)?).unwrap();
 
     let (lvl, conf) = log_config(args.verbose.into())?;
     TermLogger::init(lvl, conf, TerminalMode::Mixed, ColorChoice::Auto)?;
 
-    // Validate args
+    // Initialize or load wallet
+    let ex = Executor::new();
+    let wallet = future::block_on(ex.run(init_wallet(&args.wallet_path, &args.wallet_pass)))?;
+
+    // Initialize state
+    let darkfid = future::block_on(ex.run(Darkfid::new(wallet)))?;
+    let darkfid = Arc::new(darkfid);
+    drop(ex);
 
+    // https://docs.rs/smol/latest/smol/struct.Executor.html#examples
     let ex = Executor::new();
     let (signal, shutdown) = async_channel::unbounded::<()>();
-
-    // // https://docs.rs/smol/latest/smol/struct.Executor.html#examples
-    // let (_, result) = Parallel::new()
-    // // Run four executor threads
-    // .each(0..4, |_| future::block_on(ex.run(shutdown.recv())))
-    // // Run the main future on the current thread.
-    // .finish(|| {
-    // future::block_on(async {
-    // realmain(args).await?;
-    // drop(signal);
-    // Ok::<(), darkfi::Error>(())
-    // })
-    // });
-
-    // result
-    Ok(())
+    let (_, result) = Parallel::new()
+        // Run four executor threads
+        .each(0..4, |_| future::block_on(ex.run(shutdown.recv())))
+        // Run the main future on the current thread.
+        .finish(|| {
+            future::block_on(async {
+                listen_and_serve(args.rpc_listen, darkfid).await?;
+                drop(signal);
+                Ok::<(), darkfi::Error>(())
+            })
+        });
+
+    result
 }

+ 2 - 2
src/wallet/walletdb.rs

@@ -94,11 +94,11 @@ impl WalletDb {
         Ok(())
     }
 
-    pub async fn key_gen(&self) -> Result<()> {
+    pub async fn keygen(&self) -> Result<Keypair> {
         debug!("Attempting to generate keypairs");
         let keypair = Keypair::random(&mut OsRng);
         self.put_keypair(&keypair).await?;
-        Ok(())
+        Ok(keypair)
     }
 
     pub async fn put_keypair(&self, keypair: &Keypair) -> Result<()> {