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

darkfid2/rpc: misc and blockchain methods added

aggstam 3 лет назад
Родитель
Сommit
48057c25b0
6 измененных файлов с 368 добавлено и 20 удалено
  1. 1 0
      Cargo.lock
  2. 1 0
      bin/darkfid2/Cargo.toml
  3. 66 0
      bin/darkfid2/src/error.rs
  4. 7 3
      bin/darkfid2/src/main.rs
  5. 78 17
      bin/darkfid2/src/rpc.rs
  6. 215 0
      bin/darkfid2/src/rpc_blockchain.rs

+ 1 - 0
Cargo.lock

@@ -1781,6 +1781,7 @@ version = "0.4.1"
 dependencies = [
 dependencies = [
  "async-std",
  "async-std",
  "async-trait",
  "async-trait",
+ "blake3",
  "darkfi",
  "darkfi",
  "darkfi-consensus-contract",
  "darkfi-consensus-contract",
  "darkfi-contract-test-harness",
  "darkfi-contract-test-harness",

+ 1 - 0
bin/darkfid2/Cargo.toml

@@ -18,6 +18,7 @@ darkfi-sdk = {path = "../../src/sdk"}
 darkfi-serial = {path = "../../src/serial"}
 darkfi-serial = {path = "../../src/serial"}
 
 
 # Misc
 # Misc
+blake3 = "1.4.1"
 log = "0.4.19"
 log = "0.4.19"
 sled = "0.34.7"
 sled = "0.34.7"
 
 

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

@@ -0,0 +1,66 @@
+/* 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 serde_json::Value;
+
+use darkfi::rpc::jsonrpc::{ErrorCode::ServerError, JsonError, JsonResult};
+
+/// Custom RPC errors available for darkfid.
+/// Please sort them sensefully.
+pub enum RpcError {
+    // Transaction-related errors
+    _TxSimulationFail = -32110,
+    _TxBroadcastFail = -32111,
+
+    // State-related errors,
+    _NotSynced = -32120,
+    UnknownSlot = -32121,
+
+    // Parsing errors
+    _ParseError = -32190,
+
+    // Contract-related errors
+    ContractZkasDbNotFound = -32200,
+}
+
+fn to_tuple(e: RpcError) -> (i64, String) {
+    let msg = match e {
+        // Transaction-related errors
+        RpcError::_TxSimulationFail => "Failed simulating transaction state change",
+        RpcError::_TxBroadcastFail => "Failed broadcasting transaction",
+        // State-related errors
+        RpcError::_NotSynced => "Blockchain is not synced",
+        RpcError::UnknownSlot => "Did not find slot",
+        // Parsing errors
+        RpcError::_ParseError => "Parse error",
+        // Contract-related errors
+        RpcError::ContractZkasDbNotFound => "zkas database not found for given contract",
+    };
+
+    (e as i64, msg.to_string())
+}
+
+pub fn server_error(e: RpcError, id: Value, msg: Option<&str>) -> JsonResult {
+    let (code, default_msg) = to_tuple(e);
+
+    if let Some(message) = msg {
+        return JsonError::new(ServerError(code), Some(message.to_string()), id).into()
+    }
+
+    JsonError::new(ServerError(code), Some(default_msg), id).into()
+}

+ 7 - 3
bin/darkfid2/src/main.rs

@@ -36,8 +36,12 @@ use darkfi_contract_test_harness::vks;
 #[cfg(test)]
 #[cfg(test)]
 mod tests;
 mod tests;
 
 
+mod error;
+use error::{server_error, RpcError};
+
 /// JSON-RPC requests handler
 /// JSON-RPC requests handler
 mod rpc;
 mod rpc;
+mod rpc_blockchain;
 
 
 /// Utility functions
 /// Utility functions
 mod utils;
 mod utils;
@@ -86,16 +90,16 @@ struct Args {
 pub struct Darkfid {
 pub struct Darkfid {
     sync_p2p: P2pPtr,
     sync_p2p: P2pPtr,
     consensus_p2p: Option<P2pPtr>,
     consensus_p2p: Option<P2pPtr>,
-    _validator: ValidatorPtr,
+    validator: ValidatorPtr,
 }
 }
 
 
 impl Darkfid {
 impl Darkfid {
     pub async fn new(
     pub async fn new(
         sync_p2p: P2pPtr,
         sync_p2p: P2pPtr,
         consensus_p2p: Option<P2pPtr>,
         consensus_p2p: Option<P2pPtr>,
-        _validator: ValidatorPtr,
+        validator: ValidatorPtr,
     ) -> Self {
     ) -> Self {
-        Self { sync_p2p, consensus_p2p, _validator }
+        Self { sync_p2p, consensus_p2p, validator }
     }
     }
 }
 }
 
 

+ 78 - 17
bin/darkfid2/src/rpc.rs

@@ -26,6 +26,7 @@ use darkfi::{
         jsonrpc::{ErrorCode, JsonError, JsonRequest, JsonResponse, JsonResult},
         jsonrpc::{ErrorCode, JsonError, JsonRequest, JsonResponse, JsonResult},
         server::RequestHandler,
         server::RequestHandler,
     },
     },
+    util::time::Timestamp,
 };
 };
 
 
 use crate::Darkfid;
 use crate::Darkfid;
@@ -42,10 +43,33 @@ impl RequestHandler for Darkfid {
         debug!(target: "RPC", "--> {}", serde_json::to_string(&req).unwrap());
         debug!(target: "RPC", "--> {}", serde_json::to_string(&req).unwrap());
 
 
         match req.method.as_str() {
         match req.method.as_str() {
-            Some("ping") => self.pong(req.id, params).await,
+            // =====================
+            // Miscellaneous methods
+            // =====================
+            Some("ping") => return self.pong(req.id, params).await,
+            Some("clock") => return self.clock(req.id, params).await,
+            Some("sync_dnet_switch") => return self.sync_dnet_switch(req.id, params).await,
+            Some("sync_dnet_info") => return self.sync_dnet_info(req.id, params).await,
+            Some("consensus_dnet_switch") => {
+                return self.consensus_dnet_switch(req.id, params).await
+            }
+            Some("consensus_dnet_info") => return self.consensus_dnet_info(req.id, params).await,
 
 
-            Some("dnet_switch") => self.dnet_switch(req.id, params).await,
-            Some("dnet_info") => self.dnet_info(req.id, params).await,
+            // ==================
+            // Blockchain methods
+            // ==================
+            Some("blockchain.get_slot") => return self.blockchain_get_slot(req.id, params).await,
+            Some("blockchain.get_tx") => return self.blockchain_get_tx(req.id, params).await,
+            Some("blockchain.last_known_slot") => {
+                return self.blockchain_last_known_slot(req.id, params).await
+            }
+            Some("blockchain.lookup_zkas") => {
+                return self.blockchain_lookup_zkas(req.id, params).await
+            }
+
+            // ==============
+            // Invalid method
+            // ==============
             Some(_) | None => JsonError::new(ErrorCode::MethodNotFound, None, req.id).into(),
             Some(_) | None => JsonError::new(ErrorCode::MethodNotFound, None, req.id).into(),
         }
         }
     }
     }
@@ -61,42 +85,79 @@ impl Darkfid {
     }
     }
 
 
     // RPCAPI:
     // RPCAPI:
-    // Activate or deactivate dnet in the P2P stack.
+    // Returns current system clock in `Timestamp` format.
+    //
+    // --> {"jsonrpc": "2.0", "method": "clock", "params": [], "id": 1}
+    // <-- {"jsonrpc": "2.0", "result": {...}, "id": 1}
+    async fn clock(&self, id: Value, _params: &[Value]) -> JsonResult {
+        JsonResponse::new(json!(Timestamp::current_time()), id).into()
+    }
+
+    // RPCAPI:
+    // Activate or deactivate dnet in the sync P2P stack.
     // By sending `true`, dnet will be activated, and by sending `false` dnet
     // By sending `true`, dnet will be activated, and by sending `false` dnet
     // will be deactivated. Returns `true` on success.
     // will be deactivated. Returns `true` on success.
     //
     //
-    // --> {"jsonrpc": "2.0", "method": "dnet_switch", "params": [true], "id": 42}
+    // --> {"jsonrpc": "2.0", "method": "sync_dnet_switch", "params": [true], "id": 42}
     // <-- {"jsonrpc": "2.0", "result": true, "id": 42}
     // <-- {"jsonrpc": "2.0", "result": true, "id": 42}
-    async fn dnet_switch(&self, id: Value, params: &[Value]) -> JsonResult {
+    async fn sync_dnet_switch(&self, id: Value, params: &[Value]) -> JsonResult {
         if params.len() != 1 && params[0].as_bool().is_none() {
         if params.len() != 1 && params[0].as_bool().is_none() {
             return JsonError::new(ErrorCode::InvalidParams, None, id).into()
             return JsonError::new(ErrorCode::InvalidParams, None, id).into()
         }
         }
 
 
         if params[0].as_bool().unwrap() {
         if params[0].as_bool().unwrap() {
             self.sync_p2p.dnet_enable().await;
             self.sync_p2p.dnet_enable().await;
-            if self.consensus_p2p.is_some() {
-                self.consensus_p2p.clone().unwrap().dnet_enable().await;
-            }
         } else {
         } else {
             self.sync_p2p.dnet_disable().await;
             self.sync_p2p.dnet_disable().await;
-            if self.consensus_p2p.is_some() {
-                self.consensus_p2p.clone().unwrap().dnet_disable().await;
-            }
         }
         }
 
 
         JsonResponse::new(json!(true), id).into()
         JsonResponse::new(json!(true), id).into()
     }
     }
 
 
     // RPCAPI:
     // RPCAPI:
-    // Retrieves P2P network information.
+    // Retrieves sync P2P network information.
     //
     //
-    // --> {"jsonrpc": "2.0", "method": "dnet_info", "params": [], "id": 42}
+    // --> {"jsonrpc": "2.0", "method": "sync_dnet_info", "params": [], "id": 42}
     // <-- {"jsonrpc": "2.0", result": {"nodeID": [], "nodeinfo": [], "id": 42}
     // <-- {"jsonrpc": "2.0", result": {"nodeID": [], "nodeinfo": [], "id": 42}
-    async fn dnet_info(&self, id: Value, _params: &[Value]) -> JsonResult {
-        let mut dnet_info = self.sync_p2p.dnet_info().await;
+    async fn sync_dnet_info(&self, id: Value, _params: &[Value]) -> JsonResult {
+        let dnet_info = self.sync_p2p.dnet_info().await;
+        JsonResponse::new(net::P2p::map_dnet_info(dnet_info), id).into()
+    }
+
+    // RPCAPI:
+    // Activate or deactivate dnet in the consensus P2P stack.
+    // By sending `true`, dnet will be activated, and by sending `false` dnet
+    // will be deactivated. Returns `true` on success.
+    //
+    // --> {"jsonrpc": "2.0", "method": "consensus_dnet_switch", "params": [true], "id": 42}
+    // <-- {"jsonrpc": "2.0", "result": true, "id": 42}
+    async fn consensus_dnet_switch(&self, id: Value, params: &[Value]) -> JsonResult {
+        if params.len() != 1 && params[0].as_bool().is_none() {
+            return JsonError::new(ErrorCode::InvalidParams, None, id).into()
+        }
+
         if self.consensus_p2p.is_some() {
         if self.consensus_p2p.is_some() {
-            dnet_info.extend(self.consensus_p2p.clone().unwrap().dnet_info().await);
+            if params[0].as_bool().unwrap() {
+                self.consensus_p2p.clone().unwrap().dnet_enable().await;
+            } else {
+                self.consensus_p2p.clone().unwrap().dnet_disable().await;
+            }
         }
         }
+
+        JsonResponse::new(json!(true), id).into()
+    }
+
+    // RPCAPI:
+    // Retrieves consensus P2P network information.
+    //
+    // --> {"jsonrpc": "2.0", "method": "consensus_dnet_info", "params": [], "id": 42}
+    // <-- {"jsonrpc": "2.0", result": {"nodeID": [], "nodeinfo": [], "id": 42}
+    async fn consensus_dnet_info(&self, id: Value, _params: &[Value]) -> JsonResult {
+        let dnet_info = if self.consensus_p2p.is_some() {
+            self.consensus_p2p.clone().unwrap().dnet_info().await
+        } else {
+            vec![]
+        };
         JsonResponse::new(net::P2p::map_dnet_info(dnet_info), id).into()
         JsonResponse::new(net::P2p::map_dnet_info(dnet_info), id).into()
     }
     }
 }
 }

+ 215 - 0
bin/darkfid2/src/rpc_blockchain.rs

@@ -0,0 +1,215 @@
+/* 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::str::FromStr;
+
+use darkfi_sdk::crypto::ContractId;
+use darkfi_serial::{deserialize, serialize};
+use log::{debug, error};
+use serde_json::{json, Value};
+
+use darkfi::{
+    rpc::jsonrpc::{
+        ErrorCode::{InternalError, InvalidParams, ParseError},
+        JsonError, JsonResponse, JsonResult,
+    },
+    runtime::vm_runtime::SMART_CONTRACT_ZKAS_DB_NAME,
+};
+
+use crate::{server_error, Darkfid, RpcError};
+
+impl Darkfid {
+    // RPCAPI:
+    // Queries the blockchain database for a block in the given slot.
+    // Returns a readable block upon success.
+    //
+    // **Params:**
+    // * `array[0]`: `u64` slot ID
+    //
+    // **Returns:**
+    // * [`BlockInfo`](https://darkrenaissance.github.io/darkfi/development/darkfi/consensus/block/struct.BlockInfo.html)
+    //   struct as a JSON object
+    //
+    // --> {"jsonrpc": "2.0", "method": "blockchain.get_slot", "params": [0], "id": 1}
+    // <-- {"jsonrpc": "2.0", "result": {...}, "id": 1}
+    pub async fn blockchain_get_slot(&self, id: Value, params: &[Value]) -> JsonResult {
+        if params.len() != 1 || !params[0].is_u64() {
+            return JsonError::new(InvalidParams, None, id).into()
+        }
+
+        let slot = params[0].as_u64().unwrap();
+        let validator = self.validator.read().await;
+
+        let blocks = match validator.blockchain.get_blocks_by_slot(&[slot]) {
+            Ok(v) => {
+                drop(validator);
+                v
+            }
+            Err(e) => {
+                error!("[RPC] blockchain.get_slot: Failed fetching block by slot: {}", e);
+                return JsonError::new(InternalError, None, id).into()
+            }
+        };
+
+        if blocks.is_empty() {
+            return server_error(RpcError::UnknownSlot, id, None)
+        }
+
+        JsonResponse::new(json!(serialize(&blocks[0])), id).into()
+    }
+
+    // RPCAPI:
+    // Queries the blockchain database for a given transaction.
+    // Returns a serialized `Transaction` object.
+    //
+    // **Params:**
+    // * `array[0]`: Hex-encoded transaction hash string
+    //
+    // **Returns:**
+    // * Serialized [`Transaction`](https://darkrenaissance.github.io/darkfi/development/darkfi/tx/struct.Transaction.html)
+    //   object
+    //
+    // --> {"jsonrpc": "2.0", "method": "blockchain.get_tx", "params": ["TxHash"], "id": 1}
+    // <-- {"jsonrpc": "2.0", "result": {...}, "id": 1}
+    pub async fn blockchain_get_tx(&self, id: Value, params: &[Value]) -> JsonResult {
+        if params.len() != 1 {
+            return JsonError::new(InvalidParams, None, id).into()
+        }
+
+        let tx_hash_str = if let Some(tx_hash_str) = params[0].as_str() {
+            tx_hash_str
+        } else {
+            return JsonError::new(InvalidParams, None, id).into()
+        };
+
+        let tx_hash = if let Ok(tx_hash) = blake3::Hash::from_hex(tx_hash_str) {
+            tx_hash
+        } else {
+            return JsonError::new(ParseError, None, id).into()
+        };
+
+        let validator = self.validator.read().await;
+
+        let txs = match validator.blockchain.transactions.get(&[tx_hash], true) {
+            Ok(txs) => {
+                drop(validator);
+                txs
+            }
+            Err(e) => {
+                error!("[RPC] blockchain.get_tx: Failed fetching tx by hash: {}", e);
+                return JsonError::new(InternalError, None, id).into()
+            }
+        };
+        // This would be an logic error somewhere
+        assert_eq!(txs.len(), 1);
+        // and strict was used during .get()
+        let tx = txs[0].as_ref().unwrap();
+
+        JsonResponse::new(json!(serialize(tx)), id).into()
+    }
+
+    // RPCAPI:
+    // Queries the blockchain database to find the last known slot
+    //
+    // **Params:**
+    // * `None`
+    //
+    // **Returns:**
+    // * `u64` ID of the last known slot
+    //
+    // --> {"jsonrpc": "2.0", "method": "blockchain.last_known_slot", "params": [], "id": 1}
+    // <-- {"jsonrpc": "2.0", "result": 1234, "id": 1}
+    pub async fn blockchain_last_known_slot(&self, id: Value, params: &[Value]) -> JsonResult {
+        if !params.is_empty() {
+            return JsonError::new(InvalidParams, None, id).into()
+        }
+
+        let blockchain = { self.validator.read().await.blockchain.clone() };
+        let Ok(last_slot) = blockchain.last() else {
+            return JsonError::new(InternalError, None, id).into()
+        };
+
+        JsonResponse::new(json!(last_slot.0), id).into()
+    }
+
+    // RPCAPI:
+    // Performs a lookup of zkas bincodes for a given contract ID and returns all of
+    // them, including their namespace.
+    //
+    // **Params:**
+    // * `array[0]`: base58-encoded contract ID string
+    //
+    // **Returns:**
+    // * `array[n]`: Pairs of: `zkas_namespace` string, serialized
+    //   [`ZkBinary`](https://darkrenaissance.github.io/darkfi/development/darkfi/zkas/decoder/struct.ZkBinary.html)
+    //   object
+    //
+    // --> {"jsonrpc": "2.0", "method": "blockchain.lookup_zkas", "params": ["6Ef42L1KLZXBoxBuCDto7coi9DA2D2SRtegNqNU4sd74"], "id": 1}
+    // <-- {"jsonrpc": "2.0", "result": [["Foo", [...]], ["Bar", [...]]], "id": 1}
+    pub async fn blockchain_lookup_zkas(&self, id: Value, params: &[Value]) -> JsonResult {
+        if params.len() != 1 || !params[0].is_string() {
+            return JsonError::new(InvalidParams, None, id).into()
+        }
+
+        let contract_id = match ContractId::from_str(params[0].as_str().unwrap()) {
+            Ok(v) => v,
+            Err(e) => {
+                error!("[RPC] blockchain.lookup_zkas: Error decoding string to ContractId: {}", e);
+                return JsonError::new(InvalidParams, None, id).into()
+            }
+        };
+
+        let blockchain = { self.validator.read().await.blockchain.clone() };
+
+        let Ok(zkas_db) = blockchain.contracts.lookup(
+            &blockchain.sled_db,
+            &contract_id,
+            SMART_CONTRACT_ZKAS_DB_NAME,
+        ) else {
+            error!(
+                "[RPC] blockchain.lookup_zkas: Did not find zkas db for ContractId: {}",
+                contract_id
+            );
+            return server_error(RpcError::ContractZkasDbNotFound, id, None)
+        };
+
+        let mut ret: Vec<(String, Vec<u8>)> = vec![];
+
+        for i in zkas_db.iter() {
+            debug!("Iterating over zkas db");
+            let Ok((zkas_ns, zkas_bytes)) = i else {
+                error!("Internal sled error iterating db");
+                return JsonError::new(InternalError, None, id).into()
+            };
+
+            let Ok(zkas_ns) = deserialize(&zkas_ns) else {
+                return JsonError::new(InternalError, None, id).into()
+            };
+
+            let Ok((zkas_bincode, _)): Result<(Vec<u8>, Vec<u8>), std::io::Error> =
+                deserialize(&zkas_bytes)
+            else {
+                return JsonError::new(InternalError, None, id).into()
+            };
+
+            ret.push((zkas_ns, zkas_bincode.to_vec()));
+        }
+
+        JsonResponse::new(json!(ret), id).into()
+    }
+}