diff --git a/script/research/blockchain-explorer/src/main.rs b/script/research/blockchain-explorer/src/main.rs index 827349c73..05da5dcb0 100644 --- a/script/research/blockchain-explorer/src/main.rs +++ b/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; diff --git a/script/research/blockchain-explorer/src/rpc.rs b/script/research/blockchain-explorer/src/rpc.rs index 930aa3bcc..a5bad7d01 100644 --- a/script/research/blockchain-explorer/src/rpc.rs +++ b/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 // ===================== diff --git a/script/research/blockchain-explorer/src/rpc_contracts.rs b/script/research/blockchain-explorer/src/rpc_contracts.rs new file mode 100644 index 000000000..e03fa717e --- /dev/null +++ b/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 . + */ + +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::>().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 = 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::>().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::().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::>().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::().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::().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::::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() + } + } + } +}