Эх сурвалжийг харах

darkfid: More functions and graceful shutdown handlers.

parazyd 4 жил өмнө
parent
commit
78c78b1d84

+ 1 - 0
Cargo.lock

@@ -1725,6 +1725,7 @@ dependencies = [
  "async-executor",
  "async-std",
  "async-trait",
+ "ctrlc",
  "darkfi",
  "easy-parallel",
  "futures-lite",

+ 1 - 0
bin/darkfid2/Cargo.toml

@@ -13,6 +13,7 @@ async-channel = "1.6.1"
 async-executor = "1.4.1"
 async-std = "1.11.0"
 async-trait = "0.1.53"
+ctrlc = "3.2.1"
 darkfi = {path = "../../", features = ["blockchain2", "wallet", "rpc", "net"]}
 easy-parallel = "3.2.0"
 futures-lite = "1.12.0"

+ 26 - 3
bin/darkfid2/src/client.rs

@@ -1,14 +1,19 @@
+use async_std::sync::Mutex;
 use lazy_init::Lazy;
 use log::info;
 
 use darkfi::{
-    crypto::{address::Address, keypair::Keypair, proof::ProvingKey},
+    crypto::{
+        address::Address,
+        keypair::{Keypair, PublicKey},
+        proof::ProvingKey,
+    },
     wallet::walletdb::WalletPtr,
     Result,
 };
 
 pub struct Client {
-    main_keypair: Keypair,
+    main_keypair: Mutex<Keypair>,
     wallet: WalletPtr,
     mint_pk: Lazy<ProvingKey>,
     burn_pk: Lazy<ProvingKey>,
@@ -38,7 +43,12 @@ impl Client {
         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() })
+        Ok(Self {
+            main_keypair: Mutex::new(main_keypair),
+            wallet,
+            mint_pk: Lazy::new(),
+            burn_pk: Lazy::new(),
+        })
     }
 
     pub async fn keygen(&self) -> Result<Address> {
@@ -49,4 +59,17 @@ impl Client {
     pub async fn get_keypairs(&self) -> Result<Vec<Keypair>> {
         self.wallet.get_keypairs().await
     }
+
+    pub async fn put_keypair(&self, keypair: &Keypair) -> Result<()> {
+        self.wallet.put_keypair(keypair).await
+    }
+
+    pub async fn set_default_keypair(&self, public: &PublicKey) -> Result<()> {
+        self.wallet.set_default_keypair(public).await?;
+        let kp = self.wallet.get_default_keypair().await?;
+        let mut mk = self.main_keypair.lock().await;
+        *mk = kp;
+        drop(mk);
+        Ok(())
+    }
 }

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

@@ -9,6 +9,8 @@ const ERROR_KEYGEN: i64 = -32101;
 const ERROR_NAN: i64 = -32102;
 const ERROR_LT1: i64 = -32103;
 const ERROR_KP_FETCH: i64 = -32104;
+const ERROR_KP_NOT_FOUND: i64 = -32105;
+const ERROR_INVALID_KP: i64 = -32106;
 
 pub fn err_keygen(id: Value) -> JsonResult {
     jsonrpc::error(ServerError(ERROR_KEYGEN), Some("Failed generating keypair".to_string()), id)
@@ -32,3 +34,12 @@ pub fn err_kp_fetch(id: Value) -> JsonResult {
     )
     .into()
 }
+
+pub fn err_kp_not_found(id: Value) -> JsonResult {
+    jsonrpc::error(ServerError(ERROR_KP_NOT_FOUND), Some("Keypair not found".to_string()), id)
+        .into()
+}
+
+pub fn err_invalid_kp(id: Value) -> JsonResult {
+    jsonrpc::error(ServerError(ERROR_INVALID_KP), Some("Invalid keypair".to_string()), id).into()
+}

+ 143 - 11
bin/darkfid2/src/main.rs

@@ -1,9 +1,9 @@
 use async_executor::Executor;
-use async_std::sync::Arc;
+use async_std::sync::{Arc, Mutex};
 use async_trait::async_trait;
 use easy_parallel::Parallel;
 use futures_lite::future;
-use log::error;
+use log::{error, info};
 use serde_derive::Deserialize;
 use serde_json::{json, Value};
 use simplelog::{ColorChoice, TermLogger, TerminalMode};
@@ -13,10 +13,16 @@ use url::Url;
 
 use darkfi::{
     cli_desc,
-    crypto::address::Address,
+    crypto::{
+        address::Address,
+        keypair::{Keypair, PublicKey, SecretKey},
+    },
     rpc::{
         jsonrpc,
-        jsonrpc::{ErrorCode, JsonRequest, JsonResult},
+        jsonrpc::{
+            ErrorCode::{InternalError, InvalidParams, MethodNotFound},
+            JsonRequest, JsonResult,
+        },
         rpcserver2::{listen_and_serve, RequestHandler},
     },
     util::{
@@ -64,13 +70,14 @@ struct Args {
 
 pub struct Darkfid {
     client: Client,
+    synced: Mutex<bool>,
 }
 
 #[async_trait]
 impl RequestHandler for Darkfid {
     async fn handle_request(&self, req: JsonRequest) -> JsonResult {
         if !req.params.is_array() {
-            return jsonrpc::error(ErrorCode::InvalidParams, None, req.id).into()
+            return jsonrpc::error(InvalidParams, None, req.id).into()
         }
 
         let params = req.params.as_array().unwrap();
@@ -79,7 +86,10 @@ impl RequestHandler for Darkfid {
             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(),
+            Some("export_keypair") => return self.export_keypair(req.id, params).await,
+            Some("import_keypair") => return self.import_keypair(req.id, params).await,
+            Some("set_default_address") => return self.set_default_address(req.id, params).await,
+            Some(_) | None => return jsonrpc::error(MethodNotFound, None, req.id).into(),
         }
     }
 }
@@ -87,7 +97,7 @@ impl RequestHandler for Darkfid {
 impl Darkfid {
     pub async fn new(wallet: WalletPtr) -> Result<Self> {
         let client = Client::new(wallet).await?;
-        Ok(Self { client })
+        Ok(Self { client, synced: Mutex::new(false) })
     }
 
     // RPCAPI:
@@ -113,11 +123,15 @@ impl Darkfid {
     }
 
     // RPCAPI:
-    // Fetches a keypair by given indexes from the wallet and returns it in an
+    // 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": "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 {
+        if params.is_empty() {
+            return jsonrpc::error(InvalidParams, None, id).into()
+        }
+
         let mut fetch_all = false;
         for i in params {
             if !i.is_i64() {
@@ -148,7 +162,7 @@ impl Darkfid {
             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
+                // 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) {
@@ -161,6 +175,108 @@ impl Darkfid {
 
         jsonrpc::response(json!(ret), id).into()
     }
+
+    // RPCAPI:
+    // Exports the given keypair index.
+    // Returns the encoded secret key upon success.
+    // --> {"jsonrpc": "2.0", "method": "export_keypair", "params": [0], "id": 1}
+    // <-- {"jsonrpc": "2.0", "result": "foobar", "id": 1}
+    async fn export_keypair(&self, id: Value, params: &[Value]) -> JsonResult {
+        if params.len() != 1 || !params[0].is_u64() {
+            return jsonrpc::error(InvalidParams, None, id).into()
+        }
+
+        let keypairs = match self.client.get_keypairs().await {
+            Ok(v) => v,
+            Err(e) => {
+                error!("Failed fetching keypairs: {}", e);
+                return err_kp_fetch(id)
+            }
+        };
+
+        if let Some(kp) = keypairs.get(params[0].as_u64().unwrap() as usize) {
+            return jsonrpc::response(json!(kp.secret.to_bytes()), id).into()
+        }
+
+        err_kp_not_found(id)
+    }
+
+    // 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": "import_keypair", "params": ["foobar"], "id": 1}
+    // <-- {"jsonrpc": "2.0", "result": "pubfoobar", "id": 1}
+    async fn import_keypair(&self, id: Value, params: &[Value]) -> JsonResult {
+        if params.len() != 1 || !params[0].is_string() {
+            return jsonrpc::error(InvalidParams, None, id).into()
+        }
+
+        let bytes: [u8; 32] = match serde_json::from_str(params[0].as_str().unwrap()) {
+            Ok(v) => v,
+            Err(e) => {
+                error!("Failed parsing secret key from string: {}", e);
+                return err_invalid_kp(id)
+            }
+        };
+
+        let secret = match SecretKey::from_bytes(bytes) {
+            Ok(v) => v,
+            Err(e) => {
+                error!("Failed parsing secret key from string: {}", e);
+                return err_invalid_kp(id)
+            }
+        };
+
+        let public = PublicKey::from_secret(secret);
+        let keypair = Keypair { secret, public };
+        let address = Address::from(public).to_string();
+
+        match self.client.put_keypair(&keypair).await {
+            Ok(()) => {}
+            Err(e) => {
+                error!("Failed inserting keypair into wallet: {}", e);
+                return jsonrpc::error(InternalError, None, id).into()
+            }
+        };
+
+        jsonrpc::response(json!(address), id).into()
+    }
+
+    // RPCAPI:
+    // Sets the default wallet address to the given index.
+    // Returns `true` upon success.
+    // --> {"jsonrpc": "2.0", "method": "set_default_address", "params": [2], "id": 1}
+    // <-- {"jsonrpc": "2.0", "result": true, "id": 1}
+    async fn set_default_address(&self, id: Value, params: &[Value]) -> JsonResult {
+        if params.len() != 1 || !params[0].is_u64() {
+            return jsonrpc::error(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!("Failed fetching keypairs: {}", e);
+                return err_kp_fetch(id)
+            }
+        };
+
+        if keypairs.len() as u64 != idx - 1 {
+            return err_kp_not_found(id)
+        }
+
+        let kp = keypairs[idx as usize];
+        match self.client.set_default_keypair(&kp.public).await {
+            Ok(()) => {}
+            Err(e) => {
+                error!("Failed setting default keypair: {}", e);
+                return jsonrpc::error(InternalError, None, id).into()
+            }
+        };
+
+        jsonrpc::response(json!(true), id).into()
+    }
 }
 
 async fn init_wallet(wallet_path: &str, wallet_pass: &str) -> Result<WalletPtr> {
@@ -170,6 +286,22 @@ async fn init_wallet(wallet_path: &str, wallet_pass: &str) -> Result<WalletPtr>
     Ok(wallet)
 }
 
+async fn realmain(args: Args, darkfid: Arc<Darkfid>, ex: Arc<Executor<'_>>) -> Result<()> {
+    // We use this synchronous channel to block in this function, and
+    // to catch a shutdown signal, where we can clean up and exit gracefully.
+    let (signal, shutdown) = std::sync::mpsc::channel();
+    ctrlc::set_handler(move || signal.send(()).unwrap()).unwrap();
+
+    ex.spawn(listen_and_serve(args.rpc_listen, darkfid)).detach();
+
+    shutdown.recv().unwrap();
+    print!("\r");
+    info!("Caught ^C, cleaning up and exiting...");
+    // Flush dbs
+
+    Ok(())
+}
+
 fn main() -> Result<()> {
     let args = Args::from_args_with_toml("").unwrap();
     let cfg_path = get_config_path(args.config, CONFIG_FILE)?;
@@ -189,7 +321,7 @@ fn main() -> Result<()> {
     drop(ex);
 
     // https://docs.rs/smol/latest/smol/struct.Executor.html#examples
-    let ex = Executor::new();
+    let ex = Arc::new(Executor::new());
     let (signal, shutdown) = async_channel::unbounded::<()>();
     let (_, result) = Parallel::new()
         // Run four executor threads
@@ -197,7 +329,7 @@ fn main() -> Result<()> {
         // Run the main future on the current thread.
         .finish(|| {
             future::block_on(async {
-                listen_and_serve(args.rpc_listen, darkfid).await?;
+                realmain(args, darkfid, ex.clone()).await?;
                 drop(signal);
                 Ok::<(), darkfi::Error>(())
             })