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

explorer: contract RPC module to handle explorerd requests

This commit introduces a new RPC contract module to the Explorerd implementation. The module adds three RPC methods to enhance the functionality of the explorer service:

- `contracts.get_native_contracts`: Retrieves the native contracts deployed in the DarkFi network.

- `contracts.get_contract_source_code_paths`: Fetches the source code paths for a specified contract.

- `contracts.get_contract_source`: Retrieves the source code content for a given contract and source path.

These additions will allow users to retrieve contract-related information more through the RPC interface, enhancing the overall functionality of the explorer.

### Testing
Manual testing has been performed through the UI to verify the correct functionality of the new RPC methods, ensuring they properly retrieve and display contract-related information.

We opted for manual testing because the underlying service and data layer already have reliable unit tests, providing confidence in the core functionality. This approach sufficiently validates the functionality of the new RPC methods within the overall explorer context.
kalm 1 год назад
Родитель
Сommit
14e38bfa28

+ 1 - 0
script/research/blockchain-explorer/src/main.rs

@@ -57,6 +57,7 @@ mod error;
 /// JSON-RPC requests handler and methods
 mod rpc;
 mod rpc_blocks;
+mod rpc_contracts;
 mod rpc_statistics;
 mod rpc_transactions;
 

+ 13 - 0
script/research/blockchain-explorer/src/rpc.rs

@@ -58,6 +58,19 @@ impl RequestHandler<()> for Explorerd {
             }
             "blocks.get_block_by_hash" => self.blocks_get_block_by_hash(req.id, req.params).await,
 
+            // =====================
+            // Contract methods
+            // =====================
+            "contracts.get_native_contracts" => {
+                self.contracts_get_native_contracts(req.id, req.params).await
+            }
+            "contracts.get_contract_source_code_paths" => {
+                self.contracts_get_contract_source_code_paths(req.id, req.params).await
+            }
+            "contracts.get_contract_source" => {
+                self.contracts_get_contract_source(req.id, req.params).await
+            }
+
             // =====================
             // Transactions methods
             // =====================

+ 168 - 0
script/research/blockchain-explorer/src/rpc_contracts.rs

@@ -0,0 +1,168 @@
+/* This file is part of DarkFi (https://dark.fi)
+ *
+ * Copyright (C) 2020-2024 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 log::error;
+use tinyjson::JsonValue;
+
+use darkfi::rpc::jsonrpc::{
+    ErrorCode::{InternalError, InvalidParams},
+    JsonError, JsonResponse, JsonResult,
+};
+use darkfi_sdk::crypto::ContractId;
+
+use crate::Explorerd;
+
+impl Explorerd {
+    // RPCAPI:
+    // Retrieves the native contracts deployed in the DarkFi network.
+    // Returns a JSON array containing Contract IDs along with their associated metadata upon success.
+    //
+    // **Params:**
+    // * `None`
+    //
+    // **Returns:**
+    // * Array of `ContractRecord`s encoded into a JSON.
+    //
+    // --> {"jsonrpc": "2.0", "method": "contracts.get_native_contracts", "params": ["5cc...2f9"], "id": 1}
+    // <-- {"jsonrpc": "2.0", "result": ["BZHKGQ26bzmBithTQYTJtjo2QdCqpkR9tjSBopT4yf4o", "Money Contract", "The money contract..."], "id": 1}
+    pub async fn contracts_get_native_contracts(&self, id: u16, params: JsonValue) -> JsonResult {
+        // Ensure that the parameters are empty
+        let params = params.get::<Vec<JsonValue>>().unwrap();
+        if !params.is_empty() {
+            return JsonError::new(InvalidParams, None, id).into()
+        }
+
+        // Retrieve native contracts and handle potential errors
+        let contract_records = match self.service.get_native_contracts() {
+            Ok(v) => v,
+            Err(e) => {
+                error!(target: "explorerd::rpc_contracts::contracts_get_native_contracts", "Failed fetching native contracts: {}", e);
+                return JsonError::new(InternalError, None, id).into()
+            }
+        };
+
+        // Transform contract records into a JSON array and return the result
+        if contract_records.is_empty() {
+            JsonResponse::new(JsonValue::Array(vec![]), id).into()
+        } else {
+            let json_blocks: Vec<JsonValue> = contract_records
+                .into_iter()
+                .map(|contract_record| contract_record.to_json_array())
+                .collect();
+            JsonResponse::new(JsonValue::Array(json_blocks), id).into()
+        }
+    }
+
+    // RPCAPI:
+    // Retrieves the source code paths for the contract associated with the specified Contract ID.
+    // Returns a JSON array containing the source code paths upon success.
+    //
+    // **Params:**
+    // * `array[0]`: `String` Contract ID
+    //
+    // **Returns:**
+    // * `JsonArray` containing source code paths for the specified Contract ID.
+    //
+    // Example Call:
+    // --> {"jsonrpc": "2.0", "method": "contracts.get_contract_source_code_paths", "params": ["BZHKGQ26bzmBithTQYTJtjo2QdCqpkR9tjSBopT4yf4o"], "id": 1}
+    // <-- {"jsonrpc": "2.0", "result": ["path/to/source1.rs", "path/to/source2.rs"], "id": 1}
+    pub async fn contracts_get_contract_source_code_paths(
+        &self,
+        id: u16,
+        params: JsonValue,
+    ) -> JsonResult {
+        // Validate that a single required parameter is provided and is of type String
+        let params = params.get::<Vec<JsonValue>>().unwrap();
+        if params.len() != 1 || !params[0].is_string() {
+            return JsonError::new(InvalidParams, None, id).into()
+        }
+
+        // Validate the provided contract ID and convert it into a ContractId object
+        let contact_id_str = params[0].get::<String>().unwrap();
+        let contract_id = match ContractId::from_str(contact_id_str) {
+            Ok(contract_id) => contract_id,
+            Err(e) => return JsonError::new(InternalError, Some(e.to_string()), id).into(),
+        };
+
+        // Retrieve source code paths for the contract, transform them into a JsonResponse, and return the result
+        match self.service.get_contract_source_paths(&contract_id) {
+            Ok(paths) => {
+                let transformed_paths =
+                    paths.iter().map(|path| JsonValue::String(path.clone())).collect();
+                JsonResponse::new(JsonValue::Array(transformed_paths), id).into()
+            }
+            Err(e) => {
+                error!(
+                    target: "explorerd::rpc_contracts::contracts_get_contract_source_code_paths",
+                    "Failed fetching contract source code paths: {e:?}");
+                JsonError::new(InternalError, None, id).into()
+            }
+        }
+    }
+
+    // RPCAPI:
+    // Retrieves contract source code content using the provided Contract ID and source path.
+    // Returns the source code content as a JSON string upon success.
+    //
+    // **Params:**
+    // * `array[0]`: `String` Contract ID
+    // * `array[1]`: `String` Source path
+    //
+    // **Returns:**
+    // * `String` containing the content of the contract source file.
+    //
+    // Example Call:
+    // --> {"jsonrpc": "2.0", "method": "contracts.get_contract_source", "params": ["BZHKGQ26bzmBithTQYTJtjo2QdCqpkR9tjSBopT4yf4o", "client/lib.rs"], "id": 1}
+    // <-- {"jsonrpc": "2.0", "result": "/* This file is ...", "id": 1}
+    pub async fn contracts_get_contract_source(&self, id: u16, params: JsonValue) -> JsonResult {
+        // Validate that the required parameters are provided
+        let params = params.get::<Vec<JsonValue>>().unwrap();
+        if params.len() != 2 || !params[0].is_string() || !params[1].is_string() {
+            return JsonError::new(InvalidParams, None, id).into()
+        }
+
+        // Validate and extract the provided Contract ID
+        let contact_id_str = params[0].get::<String>().unwrap();
+        let contract_id = match ContractId::from_str(contact_id_str) {
+            Ok(contract_id) => contract_id,
+            Err(e) => return JsonError::new(InternalError, Some(e.to_string()), id).into(),
+        };
+
+        // Extract the provided source path
+        let source_path = params[1].get::<String>().unwrap();
+
+        // Retrieve the contract source code, transform it into a JsonResponse, and return the result
+        match self.service.get_contract_source_content(&contract_id, source_path) {
+            Ok(Some(source_file)) => JsonResponse::new(JsonValue::String(source_file), id).into(),
+            Ok(None) => {
+                let empty_value =
+                    JsonValue::from(std::collections::HashMap::<String, JsonValue>::new());
+                JsonResponse::new(empty_value, id).into()
+            }
+            Err(e) => {
+                error!(
+                    target: "explorerd::rpc_contracts::contracts_get_contract_source",
+                    "Failed fetching contract source code: {}", e
+                );
+                JsonError::new(InternalError, None, id).into()
+            }
+        }
+    }
+}