Selaa lähdekoodia

Merge branch 'rpc-rewrite'

lunar-mining 4 vuotta sitten
vanhempi
sitoutus
56d24ff0bf

+ 5 - 2
Cargo.toml

@@ -14,6 +14,7 @@ bellman = { version = "0.8", default-features = false, features = ["groth16"] }
 bls12_381 = "0.3.1"
 jubjub = "0.5.1"
 bs58 = "0.4.0"
+hex = "0.4"
 
 zcash_primitives = "0.5.0"
 zcash_proofs = "0.5.0"
@@ -74,6 +75,9 @@ toml = "0.5.8"
 # serde
 serde = { version = "1.0.126", features = ["derive"]}
 
+# tokio async
+tokio = { version = "1.11.0", features = ["full"] }
+
 # zmq
 zeromq = { git="https://github.com/zeromq/zmq.rs", default-features = false, features = ["async-std-runtime", "all-transport"] }
 bytes = "1.0.1"
@@ -86,7 +90,6 @@ dirs = "3.0.2"
 solana-sdk = {version = "1.7.11", optional = true}
 solana-client = {version = "1.7.11", optional = true}
 tokio-tungstenite = {version = "0.15.0", optional = true} 
-tokio = {version = "1.11.0", features = ["full"], optional = true}
 
 ## Cashier Bitcoin Dependencies
 bitcoin = {version = "0.27.0", optional = true }
@@ -99,7 +102,7 @@ features = ["bundled", "sqlcipher"]
 
 [features]
 default = ["bitcoin", "secp256k1", "electrum-client"]
-sol = ["solana-sdk", "solana-client", "tokio-tungstenite", "tokio" ]
+sol = ["solana-sdk", "solana-client", "tokio-tungstenite"]
 
 [[bin]]
 name = "gatewayd"

+ 1 - 1
example/config/cashierd.toml

@@ -1,5 +1,5 @@
 accept_url = "127.0.0.1:7777"
-rpc_url = "http://127.0.0.1:8000"
+rpc_url = "127.0.0.1:8000"
 gateway_url = "127.0.0.1:3333"
 log_path = "/tmp/cashierd.log"
 password = "TEST_PASSWORD"

+ 1 - 1
example/config/drk.toml

@@ -1,2 +1,2 @@
-rpc_url = "http://127.0.0.1:8000"
+rpc_url = "127.0.0.1:8000"
 log_path = "/tmp/drk_cli.log"

+ 270 - 0
src/bin/cashierd2.rs

@@ -0,0 +1,270 @@
+use async_std::sync::Arc;
+use log::*;
+use std::path::PathBuf;
+
+use clap::clap_app;
+use serde_json::{json, Value};
+use simplelog::{
+    CombinedLogger, Config as SimLogConfig, ConfigBuilder, LevelFilter, TermLogger, TerminalMode,
+    WriteLogger,
+};
+use std::net::SocketAddr;
+use tokio::io::{AsyncReadExt, AsyncWriteExt};
+use tokio::net::TcpListener;
+
+use async_executor::Executor;
+use easy_parallel::Parallel;
+
+use drk::{
+    cli::{CashierdConfig, Config},
+    rpc::{
+        jsonrpc::{error as jsonerr, response as jsonresp},
+        jsonrpc::{ErrorCode::*, JsonRequest, JsonResult},
+    },
+    serial::{deserialize, serialize},
+    service::{bridge, CashierService},
+    util::join_config_path,
+    wallet::{CashierDb, WalletDb},
+    Error, Result,
+};
+
+use ff::PrimeField;
+
+#[derive(Clone)]
+struct Cashierd {
+    verbose: bool,
+    config: CashierdConfig,
+    client_wallet: Arc<WalletDb>,
+    cashier_wallet: Arc<CashierDb>,
+    // clientdb:
+    // mint_params:
+    // spend_params:
+}
+
+impl Cashierd {
+    fn new(verbose: bool, config_path: PathBuf) -> Result<Self> {
+        let config: CashierdConfig = Config::<CashierdConfig>::load(config_path)?;
+        let cashier_wallet = CashierDb::new(
+            &PathBuf::from(config.cashierdb_path.clone()),
+            config.password.clone(),
+        )?;
+        let client_wallet = WalletDb::new(
+            &PathBuf::from(config.cashierdb_path.clone()),
+            config.password.clone(),
+        )?;
+
+        Ok(Self {
+            verbose,
+            config,
+            cashier_wallet,
+            client_wallet,
+        })
+    }
+
+    async fn start(self, executor: Arc<Executor<'_>>, config: CashierdConfig) -> Result<()> {
+        let ex = executor.clone();
+        let accept_addr: SocketAddr = config.accept_url.parse()?;
+
+        let gateway_addr: SocketAddr = config.gateway_url.parse()?;
+
+        let database_path = PathBuf::from(config.cashierdb_path);
+
+        //let database_path = join_config_path(&PathBuf::from("cashier_client_database.db"))?;
+
+        //let cashierdb = join_config_path(&PathBuf::from("cashier.db"))?;
+        //let client_wallet = join_config_path(&PathBuf::from("cashier_client_walletdb.db"))?;
+
+        let mint_params_path = join_config_path(&PathBuf::from("cashier_mint.params"))?;
+        let spend_params_path = join_config_path(&PathBuf::from("cashier_spend.params"))?;
+
+        let mut cashier = CashierService::new(
+            accept_addr,
+            self.cashier_wallet.clone(),
+            self.client_wallet.clone(),
+            database_path,
+            (gateway_addr, "127.0.0.1:4444".parse()?),
+            (mint_params_path, spend_params_path),
+        )
+        .await?;
+
+        //// TODO: make this a vector of accepted assets
+        //let asset = Asset::new("btc".to_string());
+        //// TODO: this should be done by the user
+        //let asset_id = deserialize(&asset.id)?;
+
+        //// TODO: pass vector of assets into cashier.start()
+        //cashier.start(ex.clone(), asset_id).await?;
+
+        Ok(())
+    }
+
+    async fn handle_request(self, req: JsonRequest) -> JsonResult {
+        if req.params.as_array().is_none() {
+            return JsonResult::Err(jsonerr(InvalidParams, None, req.id));
+        }
+
+        debug!(target: "RPC", "--> {:#?}", serde_json::to_string(&req).unwrap());
+
+        // TODO: "features"
+        match req.method.as_str() {
+            //Some("say_hello") => return self.say_hello(req.id, req.params).await,
+            //Some("create_wallet") => return self.create_wallet(req.id, req.params).await,
+            //Some("key_gen") => return self.key_gen(req.id, req.params).await,
+            //Some("get_key") => return self.get_key(req.id, req.params).await,
+            Some("deposit") => return self.deposit(req.id, req.params).await,
+            //Some("withdraw") => return self.withdraw(req.id, req.params).await,
+            //Some("transfer") => return self.transfer(req.id, req.params).await,
+            Some(_) => {}
+            None => {}
+        };
+
+        return JsonResult::Err(jsonerr(MethodNotFound, None, req.id));
+    }
+
+    async fn deposit(self, id: Value, params: Value) -> JsonResult {
+        debug!(target: "CASHIER", "RECEIVED DEPOSIT REQUEST");
+
+        if params.as_array().is_none() {
+            return JsonResult::Err(jsonerr(InvalidParams, None, id));
+        }
+
+        let args = params.as_array().unwrap();
+
+        let _network = &args[0];
+        let token = &args[1];
+        let pubkey = &args[2];
+
+        debug!(target: "CASHIER", "PROCESSING INPUT");
+        if token.as_str().is_none() {
+            return JsonResult::Err(jsonerr(InvalidParams, None, id));
+        }
+
+        let token_str = token.as_str().unwrap();
+        let token_fr = jubjub::Fr::from_str(token_str);
+        if token_fr.is_none() {
+            return JsonResult::Err(jsonerr(InvalidParams, None, id));
+        };
+
+        let token = token_fr.unwrap();
+
+        let pubkey = pubkey.as_str().unwrap();
+        let hex = hex::decode(pubkey).unwrap();
+
+        let pubkey: jubjub::SubgroupPoint = deserialize(&hex).unwrap();
+
+        //// TODO: Sanity check.
+        debug!(target: "CASHIER", "GET DEPOSIT COIN KEYS");
+        let _check = self
+            .cashier_wallet
+            .get_deposit_coin_keys_by_dkey_public(&pubkey, &serialize(&1));
+
+        // TODO: implement bridge communication
+        // NOTE: this just returns the user public key
+        debug!(target: "CASHIER", "ATTEMPING REPLY");
+        JsonResult::Resp(jsonresp(json!(pubkey), json!(id)))
+    }
+}
+
+#[tokio::main]
+async fn main() -> Result<()> {
+    let args = clap_app!(cashierd =>
+        (@arg CONFIG: -c --config +takes_value "Sets a custom config file")
+        (@arg verbose: -v --verbose "Increase verbosity")
+    )
+    .get_matches();
+
+    let config_path: PathBuf;
+
+    if args.is_present("CONFIG") {
+        config_path = PathBuf::from(args.value_of("CONFIG").unwrap());
+    } else {
+        config_path = join_config_path(&PathBuf::from("cashierd.toml"))?;
+    }
+
+    let cashierd = Cashierd::new(args.clone().is_present("verbose"), config_path)?;
+
+    let listener = TcpListener::bind(cashierd.clone().config.rpc_url).await?;
+    debug!(target: "RPC SERVER", "Listening on {}", cashierd.clone().config.rpc_url);
+
+    let logger_config = ConfigBuilder::new().set_time_format_str("%T%.6f").build();
+    let debug_level = if args.is_present("verbose") {
+        LevelFilter::Debug
+    } else {
+        LevelFilter::Off
+    };
+
+    let log_path = cashierd.clone().config.log_path;
+    CombinedLogger::init(vec![
+        TermLogger::new(debug_level, logger_config, TerminalMode::Mixed).unwrap(),
+        WriteLogger::new(
+            LevelFilter::Debug,
+            SimLogConfig::default(),
+            std::fs::File::create(log_path).unwrap(),
+        ),
+    ])
+    .unwrap();
+
+    let ex = Arc::new(Executor::new());
+    let ex2 = ex.clone();
+    let (signal, shutdown) = async_channel::unbounded::<()>();
+
+    let cashierd2 = cashierd.clone();
+    let cashierd3 = cashierd.clone();
+    let (_, _result) = Parallel::new()
+        // Run four executor threads.
+        .each(0..3, |_| smol::future::block_on(ex.run(shutdown.recv())))
+        // Run the main future on the current thread.
+        .finish(|| {
+            smol::future::block_on(async move {
+                cashierd2.start(ex2, cashierd3.clone().config).await?;
+                drop(signal);
+                Ok::<(), Error>(())
+            })
+        });
+
+    loop {
+        debug!(target: "RPC SERVER", "waiting for client");
+
+        let (mut socket, _) = listener.accept().await?;
+
+        debug!(target: "RPC SERVER", "accepted client");
+
+        let cashierd = cashierd.clone();
+        tokio::spawn(async move {
+            let mut buf = [0; 2048];
+
+            loop {
+                let n = match socket.read(&mut buf).await {
+                    Ok(n) if n == 0 => {
+                        debug!(target: "RPC SERVER", "closed connection");
+                        return;
+                    }
+                    Ok(n) => n,
+                    Err(e) => {
+                        debug!(target: "RPC SERVER", "failed to read from socket; err = {:?}", e);
+                        return;
+                    }
+                };
+
+                let r: JsonRequest = match serde_json::from_slice(&buf[0..n]) {
+                    Ok(r) => r,
+                    Err(e) => {
+                        debug!(target: "RPC SERVER", "received invalid json; err = {:?}", e);
+                        return;
+                    }
+                };
+
+                let reply = cashierd.clone().handle_request(r).await;
+                let j = serde_json::to_string(&reply).unwrap();
+
+                debug!(target: "RPC", "<-- {:#?}", j);
+
+                // Write the data back
+                if let Err(e) = socket.write_all(j.as_bytes()).await {
+                    debug!(target: "RPC SERVER", "failed to write to socket; err = {:?}", e);
+                    return;
+                }
+            }
+        });
+    }
+}

+ 323 - 0
src/bin/darkfid2.rs

@@ -0,0 +1,323 @@
+use log::*;
+use std::fs;
+use std::path::PathBuf;
+
+use clap::clap_app;
+use serde_json::{json, Value};
+use simplelog::{
+    CombinedLogger, Config as SimLogConfig, ConfigBuilder, LevelFilter, TermLogger, TerminalMode,
+    WriteLogger,
+};
+
+use async_std::sync::Arc;
+use tokio::io::{AsyncReadExt, AsyncWriteExt};
+use tokio::net::TcpListener;
+
+use drk::{
+    cli::{Config, DarkfidConfig},
+    rpc::{
+        jsonrpc::{error as jsonerr, request as jsonreq, response as jsonresp, send_request},
+        jsonrpc::{ErrorCode::*, JsonRequest, JsonResult},
+    },
+    serial::serialize,
+    util::join_config_path,
+    wallet::WalletDb,
+    Error,
+};
+
+#[derive(Clone)]
+struct Darkfid {
+    verbose: bool,
+    config: DarkfidConfig,
+    wallet: Arc<WalletDb>,
+    // clientdb:
+    // mint_params:
+    // spend_params:
+}
+
+impl Darkfid {
+    fn new(verbose: bool, config_path: PathBuf) -> Result<Self, Error> {
+        let config: DarkfidConfig = Config::<DarkfidConfig>::load(config_path)?;
+        let wallet = WalletDb::new(
+            &PathBuf::from(config.walletdb_path.clone()),
+            config.password.clone(),
+        )?;
+
+        Ok(Self {
+            verbose,
+            config,
+            wallet,
+        })
+    }
+
+    // TODO: ServerError codes should be part of the lib.
+    async fn handle_request(self, req: JsonRequest) -> JsonResult {
+        if req.params.as_array().is_none() {
+            return JsonResult::Err(jsonerr(InvalidParams, None, req.id));
+        }
+
+        debug!(target: "RPC", "--> {:#?}", serde_json::to_string(&req).unwrap());
+
+        match req.method.as_str() {
+            Some("say_hello") => return self.say_hello(req.id, req.params).await,
+            Some("create_wallet") => return self.create_wallet(req.id, req.params).await,
+            Some("key_gen") => return self.key_gen(req.id, req.params).await,
+            Some("get_key") => return self.get_key(req.id, req.params).await,
+            Some("get_token_id") => return self.get_token_id(req.id, req.params).await,
+            Some("deposit") => return self.deposit(req.id, req.params).await,
+            Some("withdraw") => return self.withdraw(req.id, req.params).await,
+            Some("transfer") => return self.transfer(req.id, req.params).await,
+            Some(_) => {}
+            None => {}
+        };
+
+        return JsonResult::Err(jsonerr(MethodNotFound, None, req.id));
+    }
+
+    // --> {"method": "say_hello", "params": []}
+    // <-- {"result": "hello world"}
+    async fn say_hello(self, id: Value, _params: Value) -> JsonResult {
+        JsonResult::Resp(jsonresp(json!("hello world"), id))
+    }
+
+    // --> {"method": "create_wallet", "params": []}
+    // <-- {"result": true}
+    async fn create_wallet(self, id: Value, _params: Value) -> JsonResult {
+        match self.wallet.init_db() {
+            Ok(()) => return JsonResult::Resp(jsonresp(json!(true), id)),
+            Err(e) => {
+                return JsonResult::Err(jsonerr(ServerError(-32001), Some(e.to_string()), id))
+            }
+        }
+    }
+
+    // --> {"method": "key_gen", "params": []}
+    // <-- {"result": true}
+    async fn key_gen(self, id: Value, _params: Value) -> JsonResult {
+        match self.wallet.key_gen() {
+            Ok((_, _)) => return JsonResult::Resp(jsonresp(json!(true), id)),
+            Err(e) => {
+                return JsonResult::Err(jsonerr(ServerError(-32002), Some(e.to_string()), id))
+            }
+        }
+    }
+
+    // --> {"method": "get_key", "params": []}
+    // <-- {"result": "vdNS7oBj7KvsMWWmo9r96SV4SqATLrGsH2a3PGpCfJC"}
+    async fn get_key(self, id: Value, _params: Value) -> JsonResult {
+        match self.wallet.get_keypairs() {
+            Ok(v) => {
+                let pk = v[0].public;
+                let b58 = bs58::encode(serialize(&pk)).into_string();
+                return JsonResult::Resp(jsonresp(json!(b58), id));
+            }
+            Err(e) => {
+                return JsonResult::Err(jsonerr(ServerError(-32003), Some(e.to_string()), id))
+            }
+        }
+    }
+
+    // --> {"jsonrpc": "2.0", "method": "get_token_id",
+    //      "params": [token],
+    //      "id": 42}
+    // <-- {"result": "Ht5G1RhkcKnpLVLMhqJc5aqZ4wYUEbxbtZwGCVbgU7DL"}
+    async fn get_token_id(self, id: Value, params: Value) -> JsonResult {
+        let args = params.as_array().unwrap();
+        let symbol = &args[0];
+
+        if symbol.as_str().is_none() {
+            return JsonResult::Err(jsonerr(InvalidParams, None, id));
+        };
+
+        let symbol = symbol.as_str().unwrap().to_uppercase();
+
+        let file_contents =
+            fs::read_to_string("token/solanatokenlist.json").expect("Can't find tokenlist file");
+        let root: Value = serde_json::from_str(&file_contents).unwrap();
+        let tokens = root["tokens"].as_array().unwrap();
+
+        for item in tokens {
+            if item["symbol"] == symbol {
+                let address = &item["address"];
+                return JsonResult::Resp(jsonresp(json!(address), id));
+            }
+        }
+        return JsonResult::Err(jsonerr(InvalidParams, None, id));
+    }
+
+    // --> {"jsonrpc": "2.0", "method": "deposit",
+    //      "params": [network, token, publickey],
+    //      "id": 42}
+    // The publickey sent here is used so the cashier can know where to send
+    // assets once the deposit is received.
+    // <-- {"result": "Ht5G1RhkcKnpLVLMhqJc5aqZ4wYUEbxbtZwGCVbgU7DL"}
+    async fn deposit(self, id: Value, params: Value) -> JsonResult {
+        let args = params.as_array().unwrap();
+        if args.len() != 2 {
+            return JsonResult::Err(jsonerr(InvalidParams, None, id));
+        }
+
+        let network = &args[0];
+        let token = &args[1];
+
+        if token.as_str().is_none() {
+            return JsonResult::Err(jsonerr(InvalidParams, None, id));
+        };
+
+        // TODO: Optional sanity checking here, but cashier *must* do so too.
+
+        let pubkey: String;
+        match self.wallet.get_keypairs() {
+            Ok(v) => {
+                let pk = v[0].public;
+                pubkey = bs58::encode(serialize(&pk)).into_string();
+            }
+            Err(e) => {
+                return JsonResult::Err(jsonerr(ServerError(-32003), Some(e.to_string()), id))
+            }
+        }
+
+        // Send request to cashier. If the cashier supports the requested network
+        // (and token), it shall return a valid address where assets can be deposited.
+        // If not, an error is returned, and forwarded to the method caller.
+        let req = jsonreq(json!("deposit"), json!([network, token, pubkey]));
+        let rep: JsonResult;
+        match send_request(self.config.cashier_url, json!(req)).await {
+            Ok(v) => rep = v,
+            Err(e) => {
+                return JsonResult::Err(jsonerr(ServerError(-32004), Some(e.to_string()), id))
+            }
+        }
+
+        match rep {
+            JsonResult::Resp(r) => return JsonResult::Resp(r),
+            JsonResult::Err(e) => return JsonResult::Err(e),
+            JsonResult::Notif(_n) => return JsonResult::Err(jsonerr(InternalError, None, id)),
+        }
+    }
+
+    // --> {"method": "withdraw", "params": [network, token, publickey, amount]}
+    // The publickey sent here is the address where the caller wants to receive
+    // the tokens they plan to withdraw.
+    // On request, send request to cashier to get deposit address, and then transfer
+    // dark assets to the cashier's wallet. Following that, the cashier should return
+    // a transaction ID of them sending the funds that are requested for withdrawal.
+    // <-- {"result": "txID"}
+    async fn withdraw(self, id: Value, params: Value) -> JsonResult {
+        let args = params.as_array().unwrap();
+        if args.len() != 4 {
+            return JsonResult::Err(jsonerr(InvalidParams, None, id));
+        }
+
+        let network = &args[0];
+        let token = &args[1];
+        let address = &args[2];
+        let amount = &args[3];
+
+        // 1. Send request to cashier.
+        // 2. Cashier checks if they support the network, and if so,
+        //    return adeposit address.
+        // 3. We issue a transfer of $amount to the given address.
+
+        return JsonResult::Err(jsonerr(
+            ServerError(-32005),
+            Some("failed to withdraw".to_string()),
+            id,
+        ));
+    }
+
+    // --> {"method": "transfer", [dToken, address, amount]}
+    // <-- {"result": "txID"}
+    async fn transfer(self, id: Value, _params: Value) -> JsonResult {
+        return JsonResult::Err(jsonerr(
+            ServerError(-32006),
+            Some("failed to transfer".to_string()),
+            id,
+        ));
+    }
+}
+
+#[tokio::main]
+async fn main() -> Result<(), Box<dyn std::error::Error>> {
+    let args = clap_app!(darkfid =>
+        (@arg CONFIG: -c --config +takes_value "Sets a custom config file")
+        (@arg verbose: -v --verbose "Increase verbosity")
+    )
+    .get_matches();
+
+    let config_path: PathBuf;
+    if args.is_present("CONFIG") {
+        config_path = PathBuf::from(args.value_of("CONFIG").unwrap());
+    } else {
+        config_path = join_config_path(&PathBuf::from("darkfid.toml"))?;
+    }
+
+    let darkfid = Darkfid::new(args.clone().is_present("verbose"), config_path)?;
+    // TODO: TLS
+    let listener = TcpListener::bind(darkfid.clone().config.rpc_url).await?;
+    debug!(target: "RPC SERVER", "Listening on {}", darkfid.clone().config.rpc_url);
+
+    let logger_config = ConfigBuilder::new().set_time_format_str("%T%.6f").build();
+    let debug_level = if args.is_present("verbose") {
+        LevelFilter::Debug
+    } else {
+        LevelFilter::Off
+    };
+
+    let log_path = darkfid.clone().config.log_path;
+    CombinedLogger::init(vec![
+        TermLogger::new(debug_level, logger_config, TerminalMode::Mixed).unwrap(),
+        WriteLogger::new(
+            LevelFilter::Debug,
+            SimLogConfig::default(),
+            std::fs::File::create(log_path).unwrap(),
+        ),
+    ])
+    .unwrap();
+
+    loop {
+        debug!(target: "RPC SERVER", "waiting for client");
+
+        let (mut socket, _) = listener.accept().await?;
+        let darkfid = darkfid.clone();
+
+        debug!(target: "RPC SERVER", "accepted client");
+
+        tokio::spawn(async move {
+            let mut buf = [0; 2048];
+
+            loop {
+                let n = match socket.read(&mut buf).await {
+                    Ok(n) if n == 0 => {
+                        debug!(target: "RPC SERVER", "closed connection");
+                        return;
+                    }
+                    Ok(n) => n,
+                    Err(e) => {
+                        debug!(target: "RPC SERVER", "failed to read from socket; err = {:?}", e);
+                        return;
+                    }
+                };
+
+                let r: JsonRequest = match serde_json::from_slice(&buf[0..n]) {
+                    Ok(r) => r,
+                    Err(e) => {
+                        debug!(target: "RPC SERVER", "received invalid json; err = {:?}", e);
+                        return;
+                    }
+                };
+
+                let reply = darkfid.clone().handle_request(r).await;
+                let j = serde_json::to_string(&reply).unwrap();
+
+                debug!(target: "RPC", "<-- {:#?}", j);
+
+                // Write the data back
+                if let Err(e) = socket.write_all(j.as_bytes()).await {
+                    debug!(target: "RPC SERVER", "failed to write to socket; err = {:?}", e);
+                    return;
+                }
+            }
+        });
+    }
+}

+ 43 - 24
src/bin/drk2.rs

@@ -1,16 +1,16 @@
-#[macro_use]
-extern crate clap;
-use clap::ArgMatches;
-use drk::cli::{Config, DrkConfig};
-use drk::util::join_config_path;
-use drk::{rpc::jsonrpc, rpc::jsonrpc::JsonResult, Error, Result};
 use log::debug;
+use std::path::PathBuf;
+
+use clap::{clap_app, ArgMatches};
 use serde_json::{json, Value};
 use simplelog::{
     CombinedLogger, Config as SimplelogConfig, ConfigBuilder, LevelFilter, TermLogger,
     TerminalMode, WriteLogger,
 };
-use std::path::PathBuf;
+
+use drk::cli::{Config, DrkConfig};
+use drk::util::join_config_path;
+use drk::{rpc::jsonrpc, rpc::jsonrpc::JsonResult, Error, Result};
 
 struct Drk {
     url: String,
@@ -22,32 +22,29 @@ impl Drk {
     }
 
     async fn request(&self, r: jsonrpc::JsonRequest) -> Result<Value> {
-        let data = surf::Body::from_json(&r)?;
-        debug!(target: "DRK", "--> {:?}", r);
-        let mut req = surf::post(&self.url).body(data).await?;
-
-        let resp = req.take_body();
-        let json = resp.into_string().await?;
+        let reply: JsonResult;
+        debug!(target: "DRK", "--> {:#?}", serde_json::to_string(&r)?);
+        match jsonrpc::send_request(self.url.clone(), json!(r)).await {
+            Ok(v) => reply = v,
+            Err(e) => return Err(e),
+        }
 
-        let v: JsonResult = serde_json::from_str(&json)?;
-        match v {
+        match reply {
             JsonResult::Resp(r) => {
-                debug!(target: "DRK", "<-- {:?}", r);
+                debug!(target: "DRK", "<-- {:#?}", serde_json::to_string(&r)?);
                 return Ok(r.result);
             }
 
             JsonResult::Err(e) => {
-                debug!(target: "DRK", "<-- {:?}", e);
+                debug!(target: "DRK", "<-- {:#?}", serde_json::to_string(&e)?);
                 return Err(Error::JsonRpcError(e.error.message.to_string()));
             }
 
             JsonResult::Notif(n) => {
-                debug!(target: "DRK", "<-- {:?}", n);
-                return Err(Error::JsonRpcError(
-                    "Unexpected reply from server".to_string(),
-                ));
+                debug!(target: "DRK", "<-- {:#?}", serde_json::to_string(&n)?);
+                return Err(Error::JsonRpcError("Unexpected reply".to_string()));
             }
-        };
+        }
     }
 
     // --> {"jsonrpc": "2.0", "method": "say_hello", "params": [], "id": 42}
@@ -78,6 +75,13 @@ impl Drk {
         Ok(self.request(req).await?)
     }
 
+    // --> {"jsonrpc": "2.0", "method": "get_key", "params": ["usdc"], "id": 42}
+    // <-- {"jsonrpc": "2.0", "result": "vdNS7oBj7KvsMWWmo9r96SV4SqATLrGsH2a3PGpCfJC", "id": 42}
+    async fn get_token_id(&self, token: &str) -> Result<Value> {
+        let req = jsonrpc::request(json!("get_token_id"), json!([token]));
+        Ok(self.request(req).await?)
+    }
+
     // --> {"jsonrpc": "2.0", "method": "deposit", "params": ["solana", "usdc"], "id": 42}
     // <-- {"jsonrpc": "2.0", "result": "Ht5G1RhkcKnpLVLMhqJc5aqZ4wYUEbxbtZwGCVbgU7DL", "id": 42}
     async fn deposit(&self, network: &str, asset: &str) -> Result<Value> {
@@ -137,6 +141,15 @@ async fn start(config: &DrkConfig, options: ArgMatches<'_>) -> Result<()> {
         }
     }
 
+    if let Some(matches) = options.subcommand_matches("id") {
+        let token = matches.value_of("TOKEN").unwrap();
+
+        let reply = client.get_token_id(&token).await?;
+
+        println!("Server replied: {}", &reply.to_string());
+        return Ok(());
+    }
+
     if let Some(matches) = options.subcommand_matches("deposit") {
         let network = matches.value_of("network").unwrap().to_lowercase();
         let token = matches.value_of("TOKEN").unwrap();
@@ -186,7 +199,8 @@ async fn start(config: &DrkConfig, options: ArgMatches<'_>) -> Result<()> {
     Err(Error::MissingParams)
 }
 
-fn main() -> Result<()> {
+#[tokio::main]
+async fn main() -> Result<()> {
     let args = clap_app!(drk =>
         (@arg CONFIG: -c --config +takes_value "Sets a custom config file")
         (@arg verbose: -v --verbose "Increase verbosity")
@@ -199,6 +213,11 @@ fn main() -> Result<()> {
             (@arg keygen: --keygen "Generate wallet keypair")
             (@arg address: --address "Get wallet address")
         )
+        (@subcommand id =>
+            (about: "Get hexidecimal ID for token symbol")
+            (@arg TOKEN: +required
+                    "Which token to query (BTC/SOL/USDC/...)")
+        )
         (@subcommand deposit =>
             (about: "Deposit clear assets for Dark assets")
             (@arg network: +required +takes_value --network
@@ -252,5 +271,5 @@ fn main() -> Result<()> {
     ])
     .unwrap();
 
-    futures::executor::block_on(start(&config, args))
+    start(&config, args).await
 }

+ 15 - 3
src/cli/cli_config.rs

@@ -9,7 +9,7 @@ use std::{
     str,
 };
 
-#[derive(Default)]
+#[derive(Clone, Default)]
 pub struct Config<T> {
     config: PhantomData<T>,
 }
@@ -34,7 +34,7 @@ pub struct DrkConfig {
     pub log_path: String,
 }
 
-#[derive(Serialize, Deserialize, Debug)]
+#[derive(Clone, Serialize, Deserialize, Debug)]
 pub struct DarkfidConfig {
     #[serde(rename = "connect_url")]
     pub connect_url: String,
@@ -48,6 +48,12 @@ pub struct DarkfidConfig {
     #[serde(rename = "rpc_url")]
     pub rpc_url: String,
 
+    #[serde(rename = "database_path")]
+    pub database_path: String,
+
+    #[serde(rename = "walletdb_path")]
+    pub walletdb_path: String,
+
     #[serde(rename = "log_path")]
     pub log_path: String,
 
@@ -67,7 +73,7 @@ pub struct GatewaydConfig {
     pub log_path: String,
 }
 
-#[derive(Serialize, Deserialize, Debug)]
+#[derive(Clone, Serialize, Deserialize, Debug)]
 pub struct CashierdConfig {
     #[serde(rename = "accept_url")]
     pub accept_url: String,
@@ -81,6 +87,12 @@ pub struct CashierdConfig {
     #[serde(rename = "log_path")]
     pub log_path: String,
 
+    #[serde(rename = "database_path")]
+    pub database_path: String,
+
+    #[serde(rename = "cashierdb_path")]
+    pub cashierdb_path: String,
+
     #[serde(rename = "password")]
     pub password: String,
 

+ 60 - 4
src/rpc/jsonrpc.rs

@@ -3,9 +3,48 @@ use std::str;
 use rand::Rng;
 use serde::{Deserialize, Serialize};
 use serde_json::{json, Value};
+use tokio::io::{AsyncReadExt, AsyncWriteExt};
+use tokio::net::TcpStream;
+
+use crate::Error;
+
+#[derive(Debug, Clone)]
+pub enum ErrorCode {
+    ParseError,
+    InvalidRequest,
+    MethodNotFound,
+    InvalidParams,
+    InternalError,
+    ServerError(i64),
+}
+
+impl ErrorCode {
+    pub fn code(&self) -> i64 {
+        match *self {
+            ErrorCode::ParseError => -32700,
+            ErrorCode::InvalidRequest => -32600,
+            ErrorCode::MethodNotFound => -32601,
+            ErrorCode::InvalidParams => -32602,
+            ErrorCode::InternalError => -32603,
+            ErrorCode::ServerError(c) => c,
+        }
+    }
+
+    pub fn description(&self) -> String {
+        let desc = match *self {
+            ErrorCode::ParseError => "Parse error",
+            ErrorCode::InvalidRequest => "Invalid request",
+            ErrorCode::MethodNotFound => "Method not found",
+            ErrorCode::InvalidParams => "Invalid params",
+            ErrorCode::InternalError => "Internal error",
+            ErrorCode::ServerError(_) => "Server error",
+        };
+        desc.to_string()
+    }
+}
 
-#[derive(Serialize, Deserialize, Debug)]
 #[serde(untagged)]
+#[derive(Serialize, Deserialize, Debug)]
 pub enum JsonResult {
     Resp(JsonResponse),
     Err(JsonError),
@@ -66,10 +105,14 @@ pub fn response(r: Value, i: Value) -> JsonResponse {
     }
 }
 
-pub fn error(c: i64, m: String, i: Value) -> JsonError {
+pub fn error(c: ErrorCode, m: Option<String>, i: Value) -> JsonError {
     let ev = JsonErrorVal {
-        code: json!(c),
-        message: json!(m),
+        code: json!(c.code()),
+        message: if m.is_none() {
+            json!(c.description())
+        } else {
+            json!(Some(m))
+        },
     };
 
     JsonError {
@@ -86,3 +129,16 @@ pub fn notification(m: Value, p: Value) -> JsonNotification {
         params: p,
     }
 }
+
+pub async fn send_request(url: String, data: Value) -> Result<JsonResult, Error> {
+    // TODO: TLS
+    let mut buf = [0; 2048];
+    let mut stream = TcpStream::connect(url).await?;
+    let data_str = serde_json::to_string(&data)?;
+
+    stream.write_all(&data_str.as_bytes()).await?;
+    let bytes_read = stream.read(&mut buf[..]).await?;
+
+    let reply: JsonResult = serde_json::from_slice(&buf[0..bytes_read])?;
+    Ok(reply)
+}

+ 1 - 0
src/wallet/walletdb.rs

@@ -22,6 +22,7 @@ pub struct Keypair {
     pub private: jubjub::Fr,
 }
 
+#[derive(Clone)]
 pub struct WalletDb {
     pub path: PathBuf,
     pub password: String,