Просмотр исходного кода

rpc: Replace serde_json with tinyjson.

parazyd 3 лет назад
Родитель
Сommit
21dd80ae99
13 измененных файлов с 719 добавлено и 437 удалено
  1. 9 13
      Cargo.toml
  2. 1 1
      src/consensus/task/consensus_sync.rs
  3. 8 8
      src/consensus/validator.rs
  4. 24 2
      src/error.rs
  5. 8 4
      src/event_graph/model.rs
  6. 107 127
      src/rpc/client.rs
  7. 106 0
      src/rpc/common.rs
  8. 362 120
      src/rpc/jsonrpc.rs
  9. 3 0
      src/rpc/mod.rs
  10. 56 110
      src/rpc/server.rs
  11. 8 11
      src/util/file.rs
  12. 2 25
      src/util/time.rs
  13. 25 16
      tests/jsonrpc.rs

+ 9 - 13
Cargo.toml

@@ -79,8 +79,8 @@ x509-parser = {version = "0.15.1", features = ["validate", "verify"], optional =
 
 # Encoding
 bs58 = {version = "0.5.0", optional = true}
-serde_json = {version = "1.0.105", optional = true}
 serde = {version = "1.0.183", features = ["derive"], optional = true}
+tinyjson = {version = "2.5.1", optional = true}
 semver = {version = "1.0.18", optional = true}
 structopt = {version= "0.3.26", optional = true}
 structopt-toml = {version= "0.5.1", optional = true}
@@ -175,6 +175,7 @@ geode = [
 event-graph = [
     "blake3",
     "rand",
+    "tinyjson",
 
     "async-runtime",
     "darkfi-serial",
@@ -184,19 +185,17 @@ event-graph = [
 ]
 
 net = [
-    "ed25519-compact",
     "async-rustls",
-    "structopt",
-    "structopt-toml",
+    "ed25519-compact",
     "rand",
     "rcgen",
     "rustls-pemfile",
-    "x509-parser",
     "semver",
-    "serde",
-    "serde_json",
     "socket2",
+    "structopt",
+    "structopt-toml",
     "url",
+    "x509-parser",
 
     "async-runtime",
     "darkfi-serial",
@@ -212,14 +211,11 @@ net = [
 ]
 
 rpc = [
-    "bs58",
     "rand",
-    "serde",
-    "serde_json",
+    "tinyjson",
     "url",
 
     "async-runtime",
-    "darkfi-serial",
     "net",
 ]
 
@@ -240,9 +236,9 @@ tx = [
 
 util = [
     "rand",
-    "simplelog",
     "serde",
-    "serde_json",
+    "simplelog",
+    "tinyjson",
     "toml",
 
     "darkfi-serial",

+ 1 - 1
src/consensus/task/consensus_sync.rs

@@ -96,7 +96,7 @@ pub async fn consensus_sync_task(p2p: P2pPtr, state: ValidatorStatePtr) -> Resul
     // Listen for next finalization
     info!(target: "consensus::consensus_sync", "Waiting for next finalization...");
     let subscriber = state.read().await.subscribers.get("blocks").unwrap().clone();
-    let subscription = subscriber.subscriber.subscribe().await;
+    let subscription = subscriber.sub.subscribe().await;
     subscription.receive().await;
     subscription.unsubscribe().await;
 

+ 8 - 8
src/consensus/validator.rs

@@ -35,7 +35,7 @@ use rand::rngs::OsRng;
 
 use crate::{
     blockchain::{BlockInfo, Blockchain, BlockchainOverlay, BlockchainOverlayPtr},
-    rpc::jsonrpc::MethodSubscriber,
+    rpc::jsonrpc::JsonSubscriber,
     runtime::vm_runtime::Runtime,
     tx::Transaction,
     util::time::{TimeKeeper, Timestamp},
@@ -70,7 +70,7 @@ pub struct ValidatorState {
     /// Canonical (finalized) blockchain
     pub blockchain: Blockchain,
     /// A map of various subscribers exporting live info from the blockchain
-    pub subscribers: HashMap<&'static str, MethodSubscriber>,
+    pub subscribers: HashMap<&'static str, JsonSubscriber>,
     /// Wallet interface
     pub wallet: WalletPtr,
     /// Flag signalling node has finished initial sync
@@ -190,8 +190,8 @@ impl ValidatorState {
 
         // Here we initialize various subscribers that can export live consensus/blockchain data.
         let mut subscribers = HashMap::new();
-        let block_subscriber = MethodSubscriber::new("blockchain.subscribe_blocks".into());
-        let err_txs_subscriber = MethodSubscriber::new("blockchain.subscribe_err_txs".into());
+        let block_subscriber = JsonSubscriber::new("blockchain.subscribe_blocks");
+        let err_txs_subscriber = JsonSubscriber::new("blockchain.subscribe_err_txs");
         subscribers.insert("blocks", block_subscriber);
         subscribers.insert("err_txs", err_txs_subscriber);
 
@@ -339,7 +339,7 @@ impl ValidatorState {
         for err_tx in erroneous_txs {
             let tx_hash = blake3::hash(&serialize(&err_tx)).to_hex().as_str().to_string();
             info!(target: "consensus::validator", "purge_pending_txs(): Sending notification about erroneous transaction");
-            err_txs_subscriber.notify(&tx_hash).await;
+            err_txs_subscriber.notify(&[tx_hash]).await;
         }
 
         Ok(())
@@ -809,7 +809,7 @@ impl ValidatorState {
             }
 
             info!(target: "consensus::validator", "consensus: Sending notification about finalized block");
-            blocks_subscriber.notify(proposal).await;
+            blocks_subscriber.notify(&[proposal.clone()]).await;
         }
 
         // Setting leaders history to last proposal leaders count
@@ -916,7 +916,7 @@ impl ValidatorState {
 
         let blocks_subscriber = self.subscribers.get("blocks").unwrap();
         info!(target: "consensus::validator", "consensus: Sending notification about finalized block");
-        blocks_subscriber.notify(&block).await;
+        blocks_subscriber.notify(&[block.clone()]).await;
 
         info!(target: "consensus::validator", "receive_finalized_block(): Removing block transactions from pending txs store");
         self.blockchain.remove_pending_txs(&block.txs)?;
@@ -964,7 +964,7 @@ impl ValidatorState {
         let blocks_subscriber = self.subscribers.get("blocks").unwrap();
         for block in new_blocks {
             info!(target: "consensus::validator", "consensus: Sending notification about finalized block");
-            blocks_subscriber.notify(&block).await;
+            blocks_subscriber.notify(&[block]).await;
         }
 
         Ok(())

+ 24 - 2
src/error.rs

@@ -90,6 +90,14 @@ pub enum Error {
     #[error("serde_json error: {0}")]
     SerdeJsonError(String),
 
+    #[cfg(feature = "tinyjson")]
+    #[error("JSON parse error: {0}")]
+    JsonParseError(String),
+
+    #[cfg(feature = "tinyjson")]
+    #[error("JSON generate error: {0}")]
+    JsonGenerateError(String),
+
     #[cfg(feature = "toml")]
     #[error(transparent)]
     TomlDeserializeError(#[from] toml::de::Error),
@@ -224,8 +232,8 @@ pub enum Error {
     #[error("Unsupported chain")]
     UnsupportedChain,
 
-    #[error("JSON-RPC error: {0}")]
-    JsonRpcError(String),
+    #[error("JSON-RPC error: {0:?}")]
+    JsonRpcError((i32, String)),
 
     #[cfg(feature = "rpc")]
     #[error(transparent)]
@@ -698,6 +706,20 @@ impl From<serde_json::Error> for Error {
     }
 }
 
+#[cfg(feature = "tinyjson")]
+impl From<tinyjson::JsonParseError> for Error {
+    fn from(err: tinyjson::JsonParseError) -> Self {
+        Self::JsonParseError(err.to_string())
+    }
+}
+
+#[cfg(feature = "tinyjson")]
+impl From<tinyjson::JsonGenerateError> for Error {
+    fn from(err: tinyjson::JsonGenerateError) -> Self {
+        Self::JsonGenerateError(err.to_string())
+    }
+}
+
 #[cfg(feature = "fast-socks5")]
 impl From<fast_socks5::SocksError> for Error {
     fn from(err: fast_socks5::SocksError) -> Self {

+ 8 - 4
src/event_graph/model.rs

@@ -24,10 +24,12 @@ use darkfi_serial::{
     deserialize, serialize, Decodable, Encodable, SerialDecodable, SerialEncodable,
 };
 use log::{error, info};
+use tinyjson::JsonValue;
 
 use crate::{
     event_graph::events_queue::EventsQueuePtr,
     util::{
+        encoding::base64,
         file::{load_json_file, save_json_file},
         time::Timestamp,
     },
@@ -100,9 +102,9 @@ where
     pub fn save_tree(&self, path: &Path) -> crate::Result<()> {
         let path = path.join("tree");
         let tree = self.event_map.clone();
-        let ser_tree = serialize(&tree);
+        let ser_tree = base64::encode(&serialize(&tree));
 
-        save_json_file(&path, &ser_tree, false)?;
+        save_json_file(&path, &JsonValue::String(ser_tree), false)?;
 
         info!("Tree is saved to disk");
 
@@ -115,8 +117,10 @@ where
             return Ok(())
         }
 
-        let loaded_tree = load_json_file::<Vec<u8>>(&path)?;
-        let dser_tree: HashMap<blake3::Hash, EventNode<T>> = deserialize(&loaded_tree)?;
+        let loaded_tree_obj = load_json_file(&path)?;
+        let loaded_tree_obj: &String = loaded_tree_obj.get::<String>().unwrap();
+        let loaded_tree_bytes = base64::decode(loaded_tree_obj.as_str()).unwrap();
+        let dser_tree: HashMap<blake3::Hash, EventNode<T>> = deserialize(&loaded_tree_bytes)?;
         self.event_map = dser_tree;
 
         info!("Tree is loaded from disk");

+ 107 - 127
src/rpc/client.rs

@@ -16,62 +16,55 @@
  * along with this program.  If not, see <https://www.gnu.org/licenses/>.
  */
 
-//! JSON-RPC client-side implementation.
-use std::time::Duration;
-
-use async_std::{
-    io::{timeout, ReadExt, WriteExt},
-    sync::Arc,
-};
+use async_std::sync::Arc;
 use futures::{select, FutureExt};
 use log::{debug, error};
-use serde_json::{json, Value};
+use smol::channel::{Receiver, Sender};
+use tinyjson::JsonValue;
 use url::Url;
 
-use super::jsonrpc::{ErrorCode, JsonError, JsonRequest, JsonResult};
+use super::{
+    common::{read_from_stream, write_to_stream, INIT_BUF_SIZE},
+    jsonrpc::*,
+};
 use crate::{
-    error::RpcError,
     net::transport::{Dialer, PtStream},
     system::SubscriberPtr,
     Error, Result,
 };
 
-const INIT_BUF_SIZE: usize = 1024; // 1K
-const MAX_BUF_SIZE: usize = 1024 * 8192; // 1M
-const READ_TIMEOUT: Duration = Duration::from_secs(60);
-
 /// JSON-RPC client implementation using asynchronous channels.
 pub struct RpcClient {
-    send: smol::channel::Sender<(Value, bool)>,
-    recv: smol::channel::Receiver<JsonResult>,
-    stop_signal: smol::channel::Sender<()>,
+    sender: Sender<(JsonRequest, bool)>,
+    receiver: Receiver<JsonResult>,
+    stop_signal: Sender<()>,
     endpoint: Url,
 }
 
 impl RpcClient {
     /// Instantiate a new JSON-RPC client that will connect to the given endpoint
-    pub async fn new(endpoint: Url, executor: Option<Arc<smol::Executor<'_>>>) -> Result<Self> {
-        let (send, recv, stop_signal) = Self::open_channels(&endpoint, executor.clone()).await?;
-        Ok(Self { send, recv, stop_signal, endpoint })
+    pub async fn new(endpoint: Url, ex: Option<Arc<smol::Executor<'_>>>) -> Result<Self> {
+        let (sender, receiver, stop_signal) = Self::open_channels(endpoint.clone(), ex).await?;
+        Ok(Self { sender, receiver, stop_signal, endpoint })
     }
 
-    /// Instantiate channels for a new [`RpcClient`]
+    /// Instantiate async channels for a new [`RpcClient`]
     async fn open_channels(
-        endpoint: &Url,
-        executor: Option<Arc<smol::Executor<'_>>>,
-    ) -> Result<(
-        smol::channel::Sender<(Value, bool)>,
-        smol::channel::Receiver<JsonResult>,
-        smol::channel::Sender<()>,
-    )> {
+        endpoint: Url,
+        ex: Option<Arc<smol::Executor<'_>>>,
+    ) -> Result<(Sender<(JsonRequest, bool)>, Receiver<JsonResult>, Sender<()>)> {
         let (data_send, data_recv) = smol::channel::unbounded();
         let (result_send, result_recv) = smol::channel::unbounded();
-        let (stop_send, stop_recv) = smol::channel::unbounded();
+        let (stop_send, stop_recv) = smol::channel::bounded(1);
 
-        let dialer = Dialer::new(endpoint.clone()).await?;
+        let dialer = Dialer::new(endpoint).await?;
+        // TODO: Could add a timeout here:
         let stream = dialer.dial(None).await?;
 
-        if let Some(ex) = executor {
+        // By passing in an executor we can avoid the global executor provided
+        // by these crates. Production usage should actually give an exexutor
+        // to `RpcClient::new()`.
+        if let Some(ex) = ex {
             ex.spawn(Self::reqrep_loop(stream, result_send, data_recv, stop_recv)).detach();
         } else {
             smol::spawn(Self::reqrep_loop(stream, result_send, data_recv, stop_recv)).detach();
@@ -86,141 +79,112 @@ impl RpcClient {
         Ok(())
     }
 
-    /// Internal read function called from `reqrep_loop` that reads from the
-    /// active stream.
-    async fn read_from_stream(stream: &mut Box<dyn PtStream>, buf: &mut Vec<u8>) -> Result<usize> {
-        debug!(target: "rpc::client", "Reading from stream...");
-        let mut total_read = 0;
-
-        while total_read < MAX_BUF_SIZE {
-            buf.resize(total_read + INIT_BUF_SIZE, 0);
-
-            match timeout(READ_TIMEOUT, stream.read(&mut buf[total_read..])).await {
-                Ok(0) if total_read == 0 => {
-                    return Err(RpcError::ConnectionClosed("Connection closed".to_string()).into())
-                }
-                Ok(0) => break, // Finished reading
-                Ok(n) => {
-                    total_read += n;
-                    if buf[total_read - 1] == b'\n' {
-                        break
-                    }
-                }
-                Err(e) => return Err(RpcError::IoError(e.kind()).into()),
-            }
-        }
-
-        // Truncate buffer to actual data size
-        buf.truncate(total_read);
-        debug!(target: "rpc::client", "Finished reading {} bytes", total_read);
-        Ok(total_read)
-    }
-
     /// Internal function that loops on a given stream and multiplexes the data.
     async fn reqrep_loop(
         mut stream: Box<dyn PtStream>,
-        result_send: smol::channel::Sender<JsonResult>,
-        data_recv: smol::channel::Receiver<(Value, bool)>,
-        stop_recv: smol::channel::Receiver<()>,
+        result_send: Sender<JsonResult>,
+        data_recv: Receiver<(JsonRequest, bool)>,
+        stop_recv: Receiver<()>,
     ) -> Result<()> {
-        let mut buf = Vec::with_capacity(INIT_BUF_SIZE);
-
         loop {
-            buf.clear();
+            let mut buf = Vec::with_capacity(INIT_BUF_SIZE);
 
             select! {
                 tuple = data_recv.recv().fuse() => {
-                    let (data, _with_timeout) = tuple?;
-                    let data_bytes = serde_json::to_vec(&data)?;
-                    stream.write_all(&data_bytes).await?;
-                    stream.write_all(&[b'\n']).await?;
-
-                    let _ = Self::read_from_stream(&mut stream, &mut buf).await?;
-
-                    let r: JsonResult = serde_json::from_slice(&buf).map_err(
-                        |e| RpcError::InvalidJson(e.to_string())
-                    )?;
-
-                    result_send.send(r).await?;
+                    let (request, with_timeout) = tuple?;
+                    let request = JsonResult::Request(request);
+                    write_to_stream(&mut stream, &request).await?;
+
+                    let _ = read_from_stream(&mut stream, &mut buf, with_timeout).await?;
+                    let val: JsonValue = String::from_utf8(buf)?.parse()?;
+                    let rep = JsonResult::try_from_value(&val)?;
+                    result_send.send(rep).await?;
                 }
 
-                _ = stop_recv.recv().fuse() => break
+                _ = stop_recv.recv().fuse() => break,
             }
         }
 
         Ok(())
     }
 
-    /// Send a given JSON-RPC request over the instantiated client.
-    pub async fn request(&self, value: JsonRequest) -> Result<Value> {
-        let req_id = value.id.clone().as_u64().unwrap();
-
-        debug!(target: "rpc::client", "--> {}", serde_json::to_string(&value)?);
+    /// Send a given JSON-RPC request over the instantiated client and
+    /// return a possible result. If the response is an error, returns
+    /// a `JsonRpcError`.
+    pub async fn request(&self, req: JsonRequest) -> Result<JsonValue> {
+        let req_id = req.id;
+        debug!(target: "rpc::client", "--> {}", req.stringify()?);
 
         // If the connection is closed, the sender will get an error
         // for sending to a closed channel.
-        if let Err(e) = self.send.send((json!(value), true)).await {
+        if let Err(e) = self.sender.send((req, true)).await {
             error!(
                 target: "rpc::client", "[RPC] Client unable to send to {}: {}",
-                self.endpoint, e
+                self.endpoint, e,
             );
             return Err(Error::NetworkOperationFailed)
         }
 
         // If the connection is closed, the receiver will get an error
         // for waiting on a closed channel.
-        let reply = self.recv.recv().await;
+        let reply = self.receiver.recv().await;
         if let Err(e) = reply {
             error!(
                 target: "rpc::client", "[RPC] Client unable to recv from {}: {}",
-                self.endpoint, e
+                self.endpoint, e,
             );
             return Err(Error::NetworkOperationFailed)
         }
 
         match reply.unwrap() {
-            JsonResult::Response(r) => {
-                debug!(target: "rpc::client", "<-- {}", serde_json::to_string(&r)?);
+            JsonResult::Response(rep) => {
+                debug!(target: "rpc::client", "<-- {}", rep.stringify()?);
 
                 // Check if the IDs match
-                match r.id.as_u64() {
-                    Some(id) => {
-                        if id != req_id {
-                            let e = JsonError::new(ErrorCode::InvalidId, None, r.id);
-                            return Err(Error::JsonRpcError(e.error.message.to_string()))
-                        }
-                    }
-
-                    None => {
-                        let e = JsonError::new(ErrorCode::InvalidId, None, r.id);
-                        return Err(Error::JsonRpcError(e.error.message.to_string()))
-                    }
+                if req_id != rep.id {
+                    let e = JsonError::new(ErrorCode::IdMismatch, None, rep.id);
+                    return Err(Error::JsonRpcError((e.error.code, e.error.message)))
                 }
 
-                Ok(r.result)
+                Ok(rep.result)
             }
 
             JsonResult::Error(e) => {
-                debug!(target: "rpc::client", "<-- {}", serde_json::to_string(&e)?);
-                Err(Error::JsonRpcError(e.error.message.to_string()))
+                debug!(target: "rpc::client", "<-- {}", e.stringify()?);
+                Err(Error::JsonRpcError((e.error.code, e.error.message)))
             }
 
             JsonResult::Notification(n) => {
-                debug!(target: "rpc::client", "<-- {}", serde_json::to_string(&n)?);
-                Err(Error::JsonRpcError("Unexpected reply".to_string()))
+                debug!(target: "rpc::client", "<-- {}", n.stringify()?);
+                let e = JsonError::new(ErrorCode::InvalidReply, None, req_id);
+                Err(Error::JsonRpcError((e.error.code, e.error.message)))
+            }
+
+            JsonResult::Request(r) => {
+                debug!(target: "rpc::client", "<-- {}", r.stringify()?);
+                let e = JsonError::new(ErrorCode::InvalidReply, None, req_id);
+                Err(Error::JsonRpcError((e.error.code, e.error.message)))
             }
 
             JsonResult::Subscriber(_) => {
                 // When?
-                Err(Error::JsonRpcError("Unexpected reply".to_string()))
+                let e = JsonError::new(ErrorCode::InvalidReply, None, req_id);
+                Err(Error::JsonRpcError((e.error.code, e.error.message)))
             }
         }
     }
 
     /// Oneshot send a given JSON-RPC request over the instantiated client
     /// and immediately close the channels upon receiving a reply.
-    pub async fn oneshot_request(&self, value: JsonRequest) -> Result<Value> {
-        let rep = self.request(value).await?;
+    pub async fn oneshot_request(&self, req: JsonRequest) -> Result<JsonValue> {
+        let rep = match self.request(req).await {
+            Ok(v) => v,
+            Err(e) => {
+                self.stop_signal.send(()).await?;
+                return Err(e)
+            }
+        };
+
         self.stop_signal.send(()).await?;
         Ok(rep)
     }
@@ -229,11 +193,12 @@ impl RpcClient {
     /// NOTE: Subscriber listeners must perform response handling.
     pub async fn subscribe(&self, req: JsonRequest, sub: SubscriberPtr<JsonResult>) -> Result<()> {
         // Perform initial request.
-        debug!(target: "rpc::client", "--> {}", serde_json::to_string(&req)?);
+        let req_id = req.id;
+        debug!(target: "rpc::client", "--> {}", req.stringify()?);
 
         // If the connection is closed, the sender will get an error for
         // sending to a closed channel.
-        if let Err(e) = self.send.send((json!(req), false)).await {
+        if let Err(e) = self.sender.send((req, false)).await {
             error!(target: "rpc::client", "[RPC] Client unable to send to {}: {}", self.endpoint, e);
             return Err(Error::NetworkOperationFailed)
         }
@@ -241,32 +206,47 @@ impl RpcClient {
         loop {
             // If the connection is closed, the receiver will get an error
             // for waiting on a closed channel.
-            let notification = self.recv.recv().await;
+            let notification = self.receiver.recv().await;
             if let Err(e) = notification {
                 error!(target: "rpc::client", "[RPC] Client unable to recv from {}: {}", self.endpoint, e);
+                self.stop_signal.send(()).await?;
                 break
             }
 
             // Notify subscribed channels
             let notification = notification.unwrap();
-            debug!(target: "rpc::client", "<-- {}", serde_json::to_string(&notification)?);
-            sub.notify(notification.clone()).await;
-
-            // Stop listening on error
             match notification {
-                JsonResult::Notification(_) => {}
-                _ => break,
-            }
+                JsonResult::Notification(ref n) => {
+                    debug!(target: "rpc::client", "<-- {}", n.stringify()?);
+                    sub.notify(notification.clone()).await;
+                }
 
-            // Triggering next consume
-            // TODO: FIXME: This should not be required
-            if let Err(e) = self.send.send((json!(req), false)).await {
-                error!(target: "rpc::client", "[RPC] Client unable to send to {}: {}", self.endpoint, e);
-                break
+                JsonResult::Error(e) => {
+                    debug!(target: "rpc::client", "<-- {}", e.stringify()?);
+                    return Err(Error::JsonRpcError((e.error.code, e.error.message)))
+                }
+
+                JsonResult::Response(r) => {
+                    debug!(target: "rpc::client", "<-- {}", r.stringify()?);
+                    let e = JsonError::new(ErrorCode::InvalidReply, None, req_id);
+                    return Err(Error::JsonRpcError((e.error.code, e.error.message)))
+                }
+
+                JsonResult::Request(r) => {
+                    debug!(target: "rpc::client", "<-- {}", r.stringify()?);
+                    let e = JsonError::new(ErrorCode::InvalidReply, None, req_id);
+                    return Err(Error::JsonRpcError((e.error.code, e.error.message)))
+                }
+
+                JsonResult::Subscriber(_) => {
+                    // When?
+                    let e = JsonError::new(ErrorCode::InvalidReply, None, req_id);
+                    return Err(Error::JsonRpcError((e.error.code, e.error.message)))
+                }
             }
         }
 
-        sub.notify(JsonError::new(ErrorCode::InternalError, None, req.id).into()).await;
+        sub.notify(JsonError::new(ErrorCode::InternalError, None, req_id).into()).await;
         Err(Error::NetworkOperationFailed)
     }
 }

+ 106 - 0
src/rpc/common.rs

@@ -0,0 +1,106 @@
+/* This file is part of DarkFi (https://dark.fi)
+ *
+ * Copyright (C) 2020-2023 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::time::Duration;
+
+use async_std::io::{timeout, ReadExt, WriteExt};
+
+use super::jsonrpc::*;
+use crate::{error::RpcError, net::transport::PtStream, Result};
+
+pub(super) const INIT_BUF_SIZE: usize = 4096; // 4K
+pub(super) const MAX_BUF_SIZE: usize = 1024 * 8192; // 8M
+pub(super) const READ_TIMEOUT: Duration = Duration::from_secs(30);
+
+/// Internal read function that reads from the active stream into a buffer.
+pub(super) async fn read_from_stream(
+    stream: &mut Box<dyn PtStream>,
+    buf: &mut Vec<u8>,
+    with_timeout: bool,
+) -> Result<usize> {
+    let mut total_read = 0;
+
+    while total_read < MAX_BUF_SIZE {
+        buf.resize(total_read + INIT_BUF_SIZE, 0);
+
+        // Lame we have to duplicate this code, but it is what it is.
+        if with_timeout {
+            match timeout(READ_TIMEOUT, stream.read(&mut buf[total_read..])).await {
+                Ok(0) if total_read == 0 => {
+                    return Err(
+                        RpcError::ConnectionClosed("Connection closed cleanly".to_string()).into()
+                    )
+                }
+                Ok(0) => break, // Finished reading
+                Ok(n) => {
+                    total_read += n;
+                    if buf[total_read - 1] == b'\n' {
+                        break
+                    }
+                }
+
+                Err(e) => return Err(RpcError::IoError(e.kind()).into()),
+            }
+        } else {
+            match stream.read(&mut buf[total_read..]).await {
+                Ok(0) if total_read == 0 => {
+                    return Err(
+                        RpcError::ConnectionClosed("Connection closed cleanly".to_string()).into()
+                    )
+                }
+                Ok(0) => break, // Finished reading
+                Ok(n) => {
+                    total_read += n;
+                    if buf[total_read - 1] == b'\n' {
+                        break
+                    }
+                }
+
+                Err(e) => return Err(RpcError::IoError(e.kind()).into()),
+            }
+        }
+    }
+
+    // Truncate buffer to actual data size
+    buf.truncate(total_read);
+    Ok(total_read)
+}
+
+/// Internal write function that writes a JSON-RPC object to the active stream.
+pub(super) async fn write_to_stream(
+    stream: &mut Box<dyn PtStream>,
+    object: &JsonResult,
+) -> Result<()> {
+    let object_str = match object {
+        JsonResult::Notification(v) => v.stringify()?,
+        JsonResult::Response(v) => v.stringify()?,
+        JsonResult::Error(v) => v.stringify()?,
+        JsonResult::Request(v) => v.stringify()?,
+        _ => unreachable!(),
+    };
+
+    // As we're a line-based protocol, we append the '\n' char at
+    // the end of the JSON string.
+    for i in [object_str.as_bytes(), &[b'\n']] {
+        if let Err(e) = stream.write_all(i).await {
+            return Err(e.into())
+        }
+    }
+
+    Ok(())
+}

+ 362 - 120
src/rpc/jsonrpc.rs

@@ -16,70 +16,104 @@
  * along with this program.  If not, see <https://www.gnu.org/licenses/>.
  */
 
-//! JSON-RPC 2.0 primitives
-use std::fmt;
+//! JSON-RPC 2.0 object definitions
+use std::collections::HashMap;
 
-use async_std::sync::Arc;
 use darkfi_serial::{serialize, Encodable};
 use rand::{rngs::OsRng, Rng};
-use serde::{Deserialize, Deserializer, Serialize, Serializer};
-use serde_json::{json, Value};
+use tinyjson::JsonValue;
 
-use crate::system::{Subscriber, SubscriberPtr};
+use crate::{
+    error::RpcError,
+    system::{Subscriber, SubscriberPtr},
+    util::encoding::base64,
+    Result,
+};
 
 /// JSON-RPC error codes.
-/// The error codes from and including -32768 to -32000 are reserved for pre-defined errors.
-#[derive(Debug, Clone)]
+/// The error codes `[-32768, -32000]` are reserved for predefined errors.
+#[derive(Copy, Clone, Debug)]
 pub enum ErrorCode {
+    /// Invalid JSON was received by the server.
+    /// An error occurred on the server while parsing the JSON text.
     ParseError,
+    /// The JSON sent is not a valid Request object.
     InvalidRequest,
+    /// The method does not exist / is not available.
     MethodNotFound,
+    /// Invalid method parameter(s).
     InvalidParams,
+    /// Internal JSON-RPC error.
     InternalError,
-    ServerError(i64),
-    InvalidId,
+    /// ID mismatch
+    IdMismatch,
+    /// Invalid/Unexpected reply
+    InvalidReply,
+    /// Reserved for implementation-defined server-errors.
+    ServerError(i32),
 }
 
 impl ErrorCode {
-    pub fn code(&self) -> i64 {
+    pub fn code(&self) -> i32 {
         match *self {
             Self::ParseError => -32700,
             Self::InvalidRequest => -32600,
             Self::MethodNotFound => -32601,
             Self::InvalidParams => -32602,
             Self::InternalError => -32603,
-            // -32000 to -32099
+            Self::IdMismatch => -32360,
+            Self::InvalidReply => -32361,
             Self::ServerError(c) => c,
-            Self::InvalidId => -32001,
         }
     }
 
-    pub fn desc(&self) -> String {
-        let desc = match *self {
-            Self::ParseError => "Parse error",
-            Self::InvalidRequest => "Invalid request",
-            Self::MethodNotFound => "Method not found",
-            Self::InvalidParams => "Invalid params",
-            Self::InternalError => "Internal error",
-            Self::ServerError(_) => "",
-            Self::InvalidId => "Request ID mismatch",
-        };
-
-        desc.to_string()
+    pub fn message(&self) -> String {
+        match *self {
+            Self::ParseError => "parse error".to_string(),
+            Self::InvalidRequest => "invalid request".to_string(),
+            Self::MethodNotFound => "method not found".to_string(),
+            Self::InvalidParams => "invalid params".to_string(),
+            Self::InternalError => "internal error".to_string(),
+            Self::IdMismatch => "id mismatch".to_string(),
+            Self::InvalidReply => "invalid reply".to_string(),
+            Self::ServerError(_) => "server error".to_string(),
+        }
+    }
+
+    pub fn desc(&self) -> JsonValue {
+        JsonValue::String(self.message())
     }
 }
 
-/// Wrapping enum around the possible JSON-RPC object types.
 // ANCHOR: jsonresult
-#[derive(Clone, Debug, Serialize, Deserialize)]
-#[serde(untagged)]
+/// Wrapping enum around the available JSON-RPC object types
+#[derive(Clone, Debug)]
 pub enum JsonResult {
     Response(JsonResponse),
     Error(JsonError),
     Notification(JsonNotification),
+    /// Subscriber is a special object that yields a channel
     Subscriber(JsonSubscriber),
+    Request(JsonRequest),
+}
+
+impl JsonResult {
+    pub fn try_from_value(value: &JsonValue) -> Result<Self> {
+        if let Ok(response) = JsonResponse::try_from(value) {
+            return Ok(Self::Response(response))
+        }
+
+        if let Ok(error) = JsonError::try_from(value) {
+            return Ok(Self::Error(error))
+        }
+
+        if let Ok(notification) = JsonNotification::try_from(value) {
+            return Ok(Self::Notification(notification))
+        }
+
+        Err(RpcError::InvalidJson("Invalid JSON Result".to_string()).into())
+    }
 }
-// ANCHOR_END: jsonresult
 
 impl From<JsonResponse> for JsonResult {
     fn from(resp: JsonResponse) -> Self {
@@ -105,158 +139,366 @@ impl From<JsonSubscriber> for JsonResult {
     }
 }
 
-/// A JSON-RPC request object.
 // ANCHOR: jsonrequest
-#[derive(Clone, Debug, Serialize, Deserialize)]
+/// A JSON-RPC request object
+#[derive(Clone, Debug)]
 pub struct JsonRequest {
     /// JSON-RPC version
-    pub jsonrpc: Value,
+    pub jsonrpc: &'static str,
     /// Request ID
-    pub id: Value,
+    pub id: u16,
     /// Request method
-    pub method: Value,
+    pub method: String,
     /// Request parameters
-    pub params: Value,
+    pub params: JsonValue,
 }
 // ANCHOR_END: jsonrequest
 
 impl JsonRequest {
-    pub fn new(method: &str, parameters: Value) -> Self {
-        Self {
-            jsonrpc: json!("2.0"),
-            id: json!(OsRng.gen::<u64>()),
-            method: json!(method),
-            params: parameters,
+    /// Create a new [`JsonRequest`] object with the given method and parameters.
+    /// The request ID is chosen randomly.
+    pub fn new(method: &str, params: JsonValue) -> Self {
+        assert!(params.is_array());
+        Self { jsonrpc: "2.0", id: OsRng::gen(&mut OsRng), method: method.to_string(), params }
+    }
+
+    /// Convert the object into a JSON string
+    pub fn stringify(&self) -> Result<String> {
+        let v: JsonValue = self.into();
+        Ok(v.stringify()?)
+    }
+}
+
+impl From<&JsonRequest> for JsonValue {
+    fn from(req: &JsonRequest) -> JsonValue {
+        JsonValue::Object(HashMap::from([
+            ("jsonrpc".to_string(), JsonValue::String(req.jsonrpc.to_string())),
+            ("id".to_string(), JsonValue::Number(req.id.into())),
+            ("method".to_string(), JsonValue::String(req.method.clone())),
+            ("params".to_string(), req.params.clone()),
+        ]))
+    }
+}
+
+impl TryFrom<&JsonValue> for JsonRequest {
+    type Error = RpcError;
+
+    fn try_from(value: &JsonValue) -> std::result::Result<Self, Self::Error> {
+        if !value.is_object() {
+            return Err(RpcError::InvalidJson("JSON is not an Object".to_string()))
+        }
+
+        let map: &HashMap<String, JsonValue> = value.get().unwrap();
+
+        if !map.contains_key("jsonrpc") ||
+            !map["jsonrpc"].is_string() ||
+            map["jsonrpc"] != JsonValue::String("2.0".to_string())
+        {
+            return Err(RpcError::InvalidJson(
+                "Request does not contain valid \"jsonrpc\" field".to_string(),
+            ))
+        }
+
+        if !map.contains_key("id") || !map["id"].is_number() {
+            return Err(RpcError::InvalidJson(
+                "Request does not contain valid \"id\" field".to_string(),
+            ))
+        }
+
+        if !map.contains_key("method") || !map["method"].is_string() {
+            return Err(RpcError::InvalidJson(
+                "Request does not contain valid \"method\" field".to_string(),
+            ))
         }
+
+        if !map.contains_key("params") || !map["params"].is_array() {
+            return Err(RpcError::InvalidJson(
+                "Request does not contain valid \"params\" field".to_string(),
+            ))
+        }
+
+        Ok(Self {
+            jsonrpc: "2.0",
+            id: *map["id"].get::<f64>().unwrap() as u16,
+            method: map["method"].get::<String>().unwrap().clone(),
+            params: map["params"].clone(),
+        })
     }
 }
 
-/// A JSON-RPC notification object.
-#[derive(Clone, Debug, Serialize, Deserialize)]
+/// A JSON-RPC notification object
+#[derive(Clone, Debug)]
 pub struct JsonNotification {
     /// JSON-RPC version
-    pub jsonrpc: Value,
+    pub jsonrpc: &'static str,
     /// Notification method
-    pub method: Value,
+    pub method: String,
     /// Notification parameters
-    pub params: Value,
+    pub params: JsonValue,
 }
 
 impl JsonNotification {
-    pub fn new(method: Value, params: Value) -> Self {
-        Self { jsonrpc: json!("2.0"), method, params }
+    /// Create a new [`JsonNotification`] object with the given method and parameters.
+    pub fn new(method: &str, params: JsonValue) -> Self {
+        assert!(params.is_array());
+        Self { jsonrpc: "2.0", method: method.to_string(), params }
     }
-}
 
-/// A method specific JSON-RPC subscriber for notifications
-#[derive(Clone)]
-pub struct MethodSubscriber {
-    /// Notification method
-    pub method: Value,
-    /// Notification subscriber
-    pub subscriber: SubscriberPtr<JsonNotification>,
-}
-
-impl MethodSubscriber {
-    pub fn new(method: Value) -> Self {
-        let subscriber = Subscriber::new();
-        Self { method, subscriber }
+    /// Convert the object into a JSON string
+    pub fn stringify(&self) -> Result<String> {
+        let v: JsonValue = self.into();
+        Ok(v.stringify()?)
     }
+}
 
-    /// Auxiliary function to format provided message and notify the subscriber.
-    pub async fn notify<T: Encodable>(&self, message: &T) {
-        let params = json!([bs58::encode(&serialize(message)).into_string()]);
-        let notif = JsonNotification::new(self.method.clone(), params);
-        self.subscriber.notify(notif).await;
+impl From<&JsonNotification> for JsonValue {
+    fn from(notif: &JsonNotification) -> JsonValue {
+        JsonValue::Object(HashMap::from([
+            ("jsonrpc".to_string(), JsonValue::String(notif.jsonrpc.to_string())),
+            ("method".to_string(), JsonValue::String(notif.method.clone())),
+            ("params".to_string(), notif.params.clone()),
+        ]))
     }
 }
 
-impl fmt::Debug for MethodSubscriber {
-    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
-        f.debug_struct("MethodSubscriber")
-            .field("method", &self.method)
-            .field("pointer", &Arc::as_ptr(&self.subscriber))
-            .finish()
+impl TryFrom<&JsonValue> for JsonNotification {
+    type Error = RpcError;
+
+    fn try_from(value: &JsonValue) -> std::result::Result<Self, Self::Error> {
+        if !value.is_object() {
+            return Err(RpcError::InvalidJson("JSON is not an Object".to_string()))
+        }
+
+        let map: &HashMap<String, JsonValue> = value.get().unwrap();
+
+        if !map.contains_key("jsonrpc") ||
+            !map["jsonrpc"].is_string() ||
+            map["jsonrpc"] != JsonValue::String("2.0".to_string())
+        {
+            return Err(RpcError::InvalidJson(
+                "Notification does not contain valid \"jsonrpc\" field".to_string(),
+            ))
+        }
+
+        if !map.contains_key("method") || !map["method"].is_string() {
+            return Err(RpcError::InvalidJson(
+                "Notification does not contain valid \"method\" field".to_string(),
+            ))
+        }
+
+        if !map.contains_key("params") || !map["params"].is_array() {
+            return Err(RpcError::InvalidJson(
+                "Notification does not contain valid \"params\" field".to_string(),
+            ))
+        }
+
+        Ok(Self {
+            jsonrpc: "2.0",
+            method: map["method"].get::<String>().unwrap().clone(),
+            params: map["params"].clone(),
+        })
     }
 }
 
-/// A JSON-RPC subscriber for notifications
+/// A JSON-RPC response object
 #[derive(Clone, Debug)]
-pub struct JsonSubscriber {
+pub struct JsonResponse {
     /// JSON-RPC version
-    pub jsonrpc: Value,
-    /// Method subscriber
-    pub subscriber: MethodSubscriber,
+    pub jsonrpc: &'static str,
+    /// Request ID
+    pub id: u16,
+    /// Response result
+    pub result: JsonValue,
 }
 
-impl JsonSubscriber {
-    pub fn new(subscriber: MethodSubscriber) -> Self {
-        Self { jsonrpc: json!("2.0"), subscriber }
+impl JsonResponse {
+    /// Create a new [`JsonResponse`] object with the given ID and result value.
+    /// Creating a `JsonResponse` implies that the method call was successful.
+    pub fn new(result: JsonValue, id: u16) -> Self {
+        Self { jsonrpc: "2.0", id, result }
     }
-}
 
-impl Serialize for JsonSubscriber {
-    fn serialize<S>(&self, _serializer: S) -> Result<S::Ok, S::Error>
-    where
-        S: Serializer,
-    {
-        unimplemented!();
+    /// Convert the object into a JSON string
+    pub fn stringify(&self) -> Result<String> {
+        let v: JsonValue = self.into();
+        Ok(v.stringify()?)
     }
 }
 
-impl<'de> Deserialize<'de> for JsonSubscriber {
-    fn deserialize<D>(_deserializer: D) -> Result<JsonSubscriber, D::Error>
-    where
-        D: Deserializer<'de>,
-    {
-        unimplemented!();
+impl From<&JsonResponse> for JsonValue {
+    fn from(rep: &JsonResponse) -> JsonValue {
+        JsonValue::Object(HashMap::from([
+            ("jsonrpc".to_string(), JsonValue::String(rep.jsonrpc.to_string())),
+            ("id".to_string(), JsonValue::Number(rep.id.into())),
+            ("result".to_string(), rep.result.clone()),
+        ]))
     }
 }
 
-/// A JSON-RPC response object.
-#[derive(Clone, Debug, Serialize, Deserialize)]
-pub struct JsonResponse {
-    /// JSON-RPC version
-    pub jsonrpc: Value,
-    /// Request ID
-    pub id: Value,
-    /// Response result
-    pub result: Value,
-}
+impl TryFrom<&JsonValue> for JsonResponse {
+    type Error = RpcError;
 
-impl JsonResponse {
-    pub fn new(result: Value, id: Value) -> Self {
-        Self { jsonrpc: json!("2.0"), id, result }
+    fn try_from(value: &JsonValue) -> std::result::Result<Self, Self::Error> {
+        if !value.is_object() {
+            return Err(RpcError::InvalidJson("Json is not an Object".to_string()))
+        }
+
+        let map: &HashMap<String, JsonValue> = value.get().unwrap();
+
+        if !map.contains_key("jsonrpc") ||
+            !map["jsonrpc"].is_string() ||
+            map["jsonrpc"] != JsonValue::String("2.0".to_string())
+        {
+            return Err(RpcError::InvalidJson(
+                "Response does not contain valid \"jsonrpc\" field".to_string(),
+            ))
+        }
+
+        if !map.contains_key("id") || !map["id"].is_number() {
+            return Err(RpcError::InvalidJson(
+                "Response does not contain valid \"id\" field".to_string(),
+            ))
+        }
+
+        Ok(Self {
+            jsonrpc: "2.0",
+            id: *map["id"].get::<f64>().unwrap() as u16,
+            result: map["result"].clone(),
+        })
     }
 }
 
-/// A JSON-RPC error object.
-#[derive(Clone, Debug, Serialize, Deserialize)]
+/// A JSON-RPC error object
+#[derive(Clone, Debug)]
 pub struct JsonError {
     /// JSON-RPC version
-    pub jsonrpc: Value,
+    pub jsonrpc: &'static str,
     /// Request ID
-    pub id: Value,
+    pub id: u16,
     /// JSON-RPC error (code and message)
     pub error: JsonErrorVal,
 }
 
 /// A JSON-RPC error value (code and message)
-#[derive(Clone, Debug, Serialize, Deserialize)]
+#[derive(Clone, Debug)]
 pub struct JsonErrorVal {
     /// Error code
-    pub code: Value,
+    pub code: i32,
     /// Error message
-    pub message: Value,
+    pub message: String,
 }
 
 impl JsonError {
-    pub fn new(c: ErrorCode, m: Option<String>, id: Value) -> Self {
-        let error = JsonErrorVal {
-            code: json!(c.code()),
-            message: if m.is_none() { json!(c.desc()) } else { json!(m.unwrap()) },
-        };
+    /// Create a new [`JsonError`] object with the given error code, optional
+    /// message, and a response ID.
+    /// Creating a `JsonError` implies that the method call was unsuccessful.
+    pub fn new(c: ErrorCode, message: Option<String>, id: u16) -> Self {
+        let error = JsonErrorVal { code: c.code(), message: message.unwrap_or(c.message()) };
+        Self { jsonrpc: "2.0", id, error }
+    }
+
+    /// Convert the object into a JSON string
+    pub fn stringify(&self) -> Result<String> {
+        let v: JsonValue = self.into();
+        Ok(v.stringify()?)
+    }
+}
+
+impl From<&JsonError> for JsonValue {
+    fn from(err: &JsonError) -> JsonValue {
+        let errmap = JsonValue::Object(HashMap::from([
+            ("code".to_string(), JsonValue::Number(err.error.code.into())),
+            ("message".to_string(), JsonValue::String(err.error.message.clone())),
+        ]));
+
+        JsonValue::Object(HashMap::from([
+            ("jsonrpc".to_string(), JsonValue::String(err.jsonrpc.to_string())),
+            ("id".to_string(), JsonValue::Number(err.id.into())),
+            ("error".to_string(), errmap),
+        ]))
+    }
+}
+
+impl TryFrom<&JsonValue> for JsonError {
+    type Error = RpcError;
+
+    fn try_from(value: &JsonValue) -> std::result::Result<Self, Self::Error> {
+        if !value.is_object() {
+            return Err(RpcError::InvalidJson("JSON is not an Object".to_string()))
+        }
+
+        let map: &HashMap<String, JsonValue> = value.get().unwrap();
+
+        if !map.contains_key("jsonrpc") ||
+            !map["jsonrpc"].is_string() ||
+            map["jsonrpc"] != JsonValue::String("2.0".to_string())
+        {
+            return Err(RpcError::InvalidJson(
+                "Error does not contain valid \"jsonrpc\" field".to_string(),
+            ))
+        }
+
+        if !map.contains_key("id") || !map["id"].is_number() {
+            return Err(RpcError::InvalidJson(
+                "Error does not contain valid \"id\" field".to_string(),
+            ))
+        }
+
+        if !map.contains_key("error") || !map["error"].is_object() {
+            return Err(RpcError::InvalidJson(
+                "Error does not contain valid \"error\" field".to_string(),
+            ))
+        }
+
+        if !map["error"]["code"].is_number() {
+            return Err(RpcError::InvalidJson(
+                "Error does not contain valid \"error.code\" field".to_string(),
+            ))
+        }
+
+        if !map["error"]["message"].is_string() {
+            return Err(RpcError::InvalidJson(
+                "Error does not contain valid \"error.message\" field".to_string(),
+            ))
+        }
+
+        Ok(Self {
+            jsonrpc: "2.0",
+            id: *map["id"].get::<f64>().unwrap() as u16,
+            error: JsonErrorVal {
+                code: *map["error"]["code"].get::<f64>().unwrap() as i32,
+                message: map["error"]["message"].get::<String>().unwrap().to_string(),
+            },
+        })
+    }
+}
+
+/// A JSON-RPC subscriber for notifications
+#[derive(Clone, Debug)]
+pub struct JsonSubscriber {
+    /// Notification method
+    pub method: &'static str,
+    /// Notification subscriber
+    pub sub: SubscriberPtr<JsonNotification>,
+}
+
+impl JsonSubscriber {
+    pub fn new(method: &'static str) -> Self {
+        let sub = Subscriber::new();
+        Self { method, sub }
+    }
+
+    /// Send a notification to the subscriber with the given params.
+    /// All the params will be serialized and then encoded with base64 encoding.
+    pub async fn notify<T: Encodable>(&self, raw_params: &[T]) {
+        let mut params = vec![];
+
+        // Serialize and encode all params
+        for raw_param in raw_params {
+            params.push(JsonValue::String(base64::encode(&serialize(raw_param))));
+        }
 
-        Self { jsonrpc: json!("2.0"), error, id }
+        let notification = JsonNotification::new(self.method, JsonValue::Array(params));
+        self.sub.notify(notification).await;
     }
 }

+ 3 - 0
src/rpc/mod.rs

@@ -16,6 +16,9 @@
  * along with this program.  If not, see <https://www.gnu.org/licenses/>.
  */
 
+/// Common internal functions
+mod common;
+
 /// JSON-RPC primitives
 pub mod jsonrpc;
 

+ 56 - 110
src/rpc/server.rs

@@ -16,61 +16,29 @@
  * along with this program.  If not, see <https://www.gnu.org/licenses/>.
  */
 
-//! JSON-RPC server-side implementation.
-use std::time::Duration;
-
-use async_std::{io::timeout, sync::Arc};
+use async_std::sync::Arc;
 use async_trait::async_trait;
-use futures::{AsyncReadExt, AsyncWriteExt};
 use log::{debug, error, info};
+use tinyjson::JsonValue;
 use url::Url;
 
-use super::jsonrpc::{JsonRequest, JsonResult};
+use super::{
+    common::{read_from_stream, write_to_stream, INIT_BUF_SIZE},
+    jsonrpc::*,
+};
 use crate::{
-    error::RpcError,
     net::transport::{Listener, PtListener, PtStream},
     Result,
 };
 
 /// Asynchronous trait implementing a handler for incoming JSON-RPC requests.
-/// Can be used by matching on methods and branching out to functions that
-/// handle respective methods.
 #[async_trait]
 pub trait RequestHandler: Sync + Send {
     async fn handle_request(&self, req: JsonRequest) -> JsonResult;
-}
-
-const INIT_BUF_SIZE: usize = 1024; // 1K
-const MAX_BUF_SIZE: usize = 1024 * 8192; // 8M
-const READ_TIMEOUT: Duration = Duration::from_secs(30);
 
-/// Internal read function called from `accept` that reads from the active stream.
-async fn read_from_stream(stream: &mut Box<dyn PtStream>, buf: &mut Vec<u8>) -> Result<usize> {
-    debug!(target: "rpc::server", "Reading from stream...");
-    let mut total_read = 0;
-
-    while total_read < MAX_BUF_SIZE {
-        buf.resize(total_read + INIT_BUF_SIZE, 0);
-
-        match timeout(READ_TIMEOUT, stream.read(&mut buf[total_read..])).await {
-            Ok(0) if total_read == 0 => {
-                return Err(RpcError::ConnectionClosed("Connection closed".to_string()).into())
-            }
-            Ok(0) => break, // Finished reading
-            Ok(n) => {
-                total_read += n;
-                if buf[total_read - 1] == b'\n' {
-                    break
-                }
-            }
-            Err(e) => return Err(RpcError::IoError(e.kind()).into()),
-        }
+    async fn pong(&self, id: u16, _params: JsonValue) -> JsonResult {
+        JsonResponse::new(JsonValue::String("pong".to_string()), id).into()
     }
-
-    // Truncate buffer to actual data size
-    buf.truncate(total_read);
-    debug!(target: "rpc::server", "Finished reading {} bytes", total_read);
-    Ok(total_read)
 }
 
 /// Accept function that should run inside a loop for accepting incoming
@@ -80,83 +48,51 @@ pub async fn accept(
     addr: Url,
     rh: Arc<impl RequestHandler + 'static>,
 ) -> Result<()> {
-    let mut buf = Vec::with_capacity(INIT_BUF_SIZE);
     loop {
-        buf.clear();
-
-        let _ = read_from_stream(&mut stream, &mut buf).await?;
+        let mut buf = Vec::with_capacity(INIT_BUF_SIZE);
+        let _ = read_from_stream(&mut stream, &mut buf, false).await?;
+        let val: JsonValue = String::from_utf8(buf)?.parse()?;
+        let req = JsonRequest::try_from(&val)?;
 
-        let r: JsonRequest =
-            serde_json::from_slice(&buf).map_err(|e| RpcError::InvalidJson(e.to_string()))?;
+        debug!(target: "rpc::server", "{} --> {}", addr, val.stringify()?);
 
-        debug!(target: "rpc::server", "{} --> {}", addr, String::from_utf8_lossy(&buf));
+        let rep = rh.handle_request(req).await;
 
-        let reply = rh.handle_request(r).await;
-
-        match reply {
-            JsonResult::Subscriber(sub) => {
+        match rep {
+            JsonResult::Subscriber(subscriber) => {
                 // Subscribe to the inner method subscriber
-                let subscription = sub.subscriber.subscriber.subscribe().await;
+                let subscription = subscriber.sub.subscribe().await;
                 loop {
-                    // Listen subscription for notifications
+                    // Listen for notifications
                     let notification = subscription.receive().await;
 
                     // Push notification
-                    let j = serde_json::to_string(&notification).unwrap();
-                    debug!(target: "rpc::server", "{} <-- {}", addr, j);
-
-                    if let Err(e) = stream.write_all(j.as_bytes()).await {
-                        error!(target: "rpc::server", "[RPC] Server failed writing to {} socket: {}", addr, e);
-                        debug!(target: "rpc::server", "Closed connection for {}", addr);
-                        break
-                    }
-
-                    if let Err(e) = stream.write_all(&[b'\n']).await {
-                        error!(target: "rpc::server", "[RPC] Server failed writing to {} socket: {}", addr, e);
-                        debug!(target: "rpc::server", "Closed connection for {}", addr);
-                        break
+                    debug!(target: "rpc::server", "{} <-- {}", addr, notification.stringify()?);
+                    let notification = JsonResult::Notification(notification);
+                    if let Err(e) = write_to_stream(&mut stream, &notification).await {
+                        subscription.unsubscribe().await;
+                        return Err(e)
                     }
                 }
-                subscription.unsubscribe().await;
             }
-            _ => {
-                let j = serde_json::to_string(&reply)
-                    .map_err(|e| RpcError::InvalidJson(e.to_string()))?;
 
-                debug!(target: "rpc::server", "{} <-- {}", addr, j);
+            JsonResult::Request(_) | JsonResult::Notification(_) => {
+                unreachable!("Should never happen")
+            }
 
-                if let Err(e) = stream.write_all(j.as_bytes()).await {
-                    error!(
-                        target: "rpc::server", "[RPC] Server failed writing to {} socket: {}",
-                        addr, e
-                    );
-                    return close_conn(
-                        &addr,
-                        RpcError::ConnectionClosed("Socket write error".to_string()),
-                    )
-                }
+            JsonResult::Response(ref v) => {
+                debug!(target: "rpc::server", "{} <-- {}", addr, v.stringify()?);
+                write_to_stream(&mut stream, &rep).await?;
+            }
 
-                if let Err(e) = stream.write_all(&[b'\n']).await {
-                    error!(
-                        target: "rpc::server", "[RPC] Server failed writing to {} socket: {}",
-                        addr, e
-                    );
-                    return close_conn(
-                        &addr,
-                        RpcError::ConnectionClosed("Socket write error".to_string()),
-                    )
-                }
+            JsonResult::Error(ref v) => {
+                debug!(target: "rpc::server", "{} <-- {}", addr, v.stringify()?);
+                write_to_stream(&mut stream, &rep).await?;
             }
         }
     }
 }
 
-/// Helper function for connection closing
-fn close_conn(peer_addr: &Url, reason: RpcError) -> Result<()> {
-    debug!(target: "rpc::server", "Closed connection for {}", peer_addr);
-    Err(reason.into())
-}
-
 /// Wrapper function around [`accept()`] to take the incoming connection and
 /// pass it forward.
 async fn run_accept_loop(
@@ -165,30 +101,40 @@ async fn run_accept_loop(
     ex: Arc<smol::Executor<'_>>,
 ) -> Result<()> {
     while let Ok((stream, peer_addr)) = listener.next().await {
-        info!(target: "rpc::server", "[RPC] Server accepted connection from {}", peer_addr);
+        info!(target: "rpc::server", "[RPC] Server accepted conn from {}", peer_addr);
         // Detaching requests handling
-        let _rh = rh.clone();
+        let rh_ = rh.clone();
         ex.spawn(async move {
-            if let Err(e) = accept(stream, peer_addr.clone(), _rh).await {
-                error!(target: "rpc::server", "[RPC] Server error on handling request of {}: {}", peer_addr, e);
+            if let Err(e) = accept(stream, peer_addr.clone(), rh_).await {
+                if e.to_string().as_str() == "Connection closed cleanly" {
+                    info!(
+                        target: "rpc::server",
+                        "[RPC] Closed connection from {}",
+                        peer_addr,
+                    );
+                } else {
+                    error!(
+                        target: "rpc::server",
+                        "[RPC] Server error on handling request from {}: {}",
+                        peer_addr, e,
+                    );
+                }
             }
-        }).detach();
+        })
+        .detach();
     }
 
-    Ok(())
+    // NOTE: This is here now to catch some code path. Will be handled properly.
+    panic!("RPC server listener stopped/crashed");
 }
 
-/// Start a JSON-RPC server bound to the given accept URL and use the given
-/// [`RequestHandler`] to handle incoming requests.
+/// Start a JSON-RPC server bound to the given accept URL and use the
+/// given [`RequestHandler`] to handle incoming requests.
 pub async fn listen_and_serve(
     accept_url: Url,
     rh: Arc<impl RequestHandler + 'static>,
     ex: Arc<smol::Executor<'_>>,
 ) -> Result<()> {
-    debug!(target: "rpc::server", "Trying to bind listener on {}", accept_url);
-
     let listener = Listener::new(accept_url).await?.listen().await?;
-    run_accept_loop(listener, rh, ex.clone()).await?;
-
-    Ok(())
+    run_accept_loop(listener, rh, ex.clone()).await
 }

+ 8 - 11
src/util/file.rs

@@ -22,7 +22,7 @@ use std::{
     path::Path,
 };
 
-use serde::{de::DeserializeOwned, Serialize};
+use tinyjson::JsonValue;
 
 use crate::Result;
 
@@ -40,21 +40,18 @@ pub fn save_file(path: &Path, st: &str) -> Result<()> {
     Ok(())
 }
 
-pub fn load_json_file<T: DeserializeOwned>(path: &Path) -> Result<T> {
-    let file = File::open(path)?;
-    let reader = BufReader::new(file);
-
-    let value: T = serde_json::from_reader(reader)?;
-    Ok(value)
+pub fn load_json_file(path: &Path) -> Result<JsonValue> {
+    let st = load_file(path)?;
+    Ok(st.parse()?)
 }
 
-pub fn save_json_file<T: Serialize>(path: &Path, value: &T, pretty: bool) -> Result<()> {
-    let file = File::create(path)?;
+pub fn save_json_file(path: &Path, value: &JsonValue, pretty: bool) -> Result<()> {
+    let mut file = File::create(path)?;
 
     if pretty {
-        serde_json::to_writer_pretty(file, value)?;
+        value.format_to(&mut file)?;
     } else {
-        serde_json::to_writer(file, value)?;
+        value.write_to(&mut file)?;
     }
 
     Ok(())

+ 2 - 25
src/util/time.rs

@@ -19,7 +19,6 @@
 use std::{fmt, time::UNIX_EPOCH};
 
 use darkfi_serial::{SerialDecodable, SerialEncodable};
-use serde::{Deserialize, Serialize};
 
 use crate::Result;
 
@@ -129,18 +128,7 @@ impl TimeKeeper {
 }
 
 /// Wrapper struct to represent system timestamps.
-#[derive(
-    Clone,
-    Copy,
-    Debug,
-    Serialize,
-    Deserialize,
-    SerialEncodable,
-    SerialDecodable,
-    PartialEq,
-    PartialOrd,
-    Eq,
-)]
+#[derive(Clone, Copy, Debug, SerialEncodable, SerialDecodable, PartialEq, PartialOrd, Eq)]
 pub struct Timestamp(pub u64);
 
 impl Timestamp {
@@ -167,18 +155,7 @@ impl std::fmt::Display for Timestamp {
     }
 }
 
-#[derive(
-    Clone,
-    Copy,
-    Debug,
-    Serialize,
-    Deserialize,
-    SerialEncodable,
-    SerialDecodable,
-    PartialEq,
-    PartialOrd,
-    Eq,
-)]
+#[derive(Clone, Copy, Debug, SerialEncodable, SerialDecodable, PartialEq, PartialOrd, Eq)]
 pub struct NanoTimestamp(pub u128);
 
 impl NanoTimestamp {

+ 25 - 16
tests/jsonrpc.rs

@@ -18,8 +18,8 @@
 
 use async_std::{net::TcpListener, sync::Arc, task};
 use async_trait::async_trait;
-use serde_json::{json, Value};
 use smol::channel::{Receiver, Sender};
+use tinyjson::JsonValue;
 use url::Url;
 
 use darkfi::{
@@ -37,25 +37,34 @@ struct RpcSrv {
 }
 
 impl RpcSrv {
-    async fn pong(&self, id: Value, _params: &[Value]) -> JsonResult {
-        JsonResponse::new(json!("pong"), id).into()
+    async fn pong(&self, id: JsonValue, _params: JsonValue) -> JsonResult {
+        JsonResponse::new(JsonValue::String("pong".to_string()), id).into()
     }
 
-    async fn kill(&self, id: Value, _params: &[Value]) -> JsonResult {
+    async fn kill(&self, id: JsonValue, _params: JsonValue) -> JsonResult {
         self.stop_sub.0.send(()).await.unwrap();
-        JsonResponse::new(json!("bye"), id).into()
+        JsonResponse::new(JsonValue::String("bye".to_string()), id).into()
     }
 }
 
 #[async_trait]
 impl RequestHandler for RpcSrv {
     async fn handle_request(&self, req: JsonRequest) -> JsonResult {
-        let params = req.params.as_array().unwrap();
+        assert!(req.params.is_array());
+        let method = String::try_from(req.method).unwrap();
+        let params = req.params;
 
-        match req.method.as_str() {
-            Some("ping") => return self.pong(req.id, params).await,
-            Some("kill") => return self.kill(req.id, params).await,
-            Some(_) | None => return JsonError::new(ErrorCode::MethodNotFound, None, req.id).into(),
+        match method.as_str() {
+            "ping" => return self.pong(req.id, params).await,
+            "kill" => return self.kill(req.id, params).await,
+            _ => {
+                return JsonError::new(
+                    ErrorCode::MethodNotFound,
+                    None,
+                    *req.id.get::<f64>().unwrap() as u16,
+                )
+                .into()
+            }
         }
     }
 }
@@ -81,17 +90,17 @@ async fn jsonrpc_reqrep() -> Result<()> {
     });
 
     let client = RpcClient::new(endpoint, None).await?;
-    let req = JsonRequest::new("ping", json!([]));
+    let req = JsonRequest::new("ping", JsonValue::from(vec![]));
     let rep = client.request(req).await?;
 
-    let rep = rep.as_str().unwrap();
-    assert_eq!(rep, "pong");
+    let rep = String::try_from(rep).unwrap();
+    assert_eq!(&rep, "pong");
 
-    let req = JsonRequest::new("kill", json!([]));
+    let req = JsonRequest::new("kill", JsonValue::from(vec![]));
     let rep = client.request(req).await?;
 
-    let rep = rep.as_str().unwrap();
-    assert_eq!(rep, "bye");
+    let rep = String::try_from(rep).unwrap();
+    assert_eq!(&rep, "bye");
 
     Ok(())
 }