mod.rs
  1  /* This file is part of DarkFi (https://dark.fi)
  2   *
  3   * Copyright (C) 2020-2025 Dyne.org foundation
  4   *
  5   * This program is free software: you can redistribute it and/or modify
  6   * it under the terms of the GNU Affero General Public License as
  7   * published by the Free Software Foundation, either version 3 of the
  8   * License, or (at your option) any later version.
  9   *
 10   * This program is distributed in the hope that it will be useful,
 11   * but WITHOUT ANY WARRANTY; without even the implied warranty of
 12   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 13   * GNU Affero General Public License for more details.
 14   *
 15   * You should have received a copy of the GNU Affero General Public License
 16   * along with this program.  If not, see <https://www.gnu.org/licenses/>.
 17   */
 18  
 19  use std::sync::{Arc, Mutex};
 20  
 21  use lazy_static::lazy_static;
 22  use smol::Executor;
 23  use tempdir::TempDir;
 24  use tinyjson::JsonValue;
 25  use tracing::warn;
 26  use url::Url;
 27  
 28  use darkfi::{
 29      rpc::{
 30          jsonrpc::{ErrorCode, JsonRequest, JsonResult},
 31          server::RequestHandler,
 32      },
 33      util::logger::{setup_test_logger, Level},
 34  };
 35  
 36  use crate::Explorerd;
 37  
 38  // Defines a global `Explorerd` instance shared across all tests
 39  lazy_static! {
 40      static ref EXPLORERD_INSTANCE: Mutex<Option<Arc<Explorerd>>> = Mutex::new(None);
 41  }
 42  
 43  #[cfg(test)]
 44  /// Sets up the `Explorerd` instance for testing, ensuring a single instance is initialized only
 45  /// once and shared among subsequent setup calls.
 46  pub fn setup() -> Arc<Explorerd> {
 47      let mut instance = EXPLORERD_INSTANCE.lock().expect("Failed to lock EXPLORERD_INSTANCE mutex");
 48  
 49      if instance.is_none() {
 50          // Initialize logger for the first time
 51          if setup_test_logger(
 52              &["sled", "runtime", "net"],
 53              false,
 54              Level::Info,
 55              //Level::Verbose,
 56              //Level::Debug,
 57              //Level::Trace
 58          )
 59          .is_err()
 60          {
 61              warn!("Logger already initialized");
 62          }
 63  
 64          // Prepare parameters for Explorerd::new
 65          let temp_dir = TempDir::new("explorerd").expect("Failed to create temp dir");
 66          let db_path_buf = temp_dir.path().join("explorerd_0");
 67          let db_path =
 68              db_path_buf.to_str().expect("Failed to convert db_path to string").to_string();
 69          let darkfid_endpoint = Url::parse("http://127.0.0.1:8240").expect("Invalid URL");
 70          let executor = Arc::new(Executor::new());
 71  
 72          // Block on the async function to resolve Explorerd::new
 73          let explorerd = smol::block_on(Explorerd::new(db_path, darkfid_endpoint, executor))
 74              .expect("Failed to initialize Explorerd instance");
 75  
 76          // Store the initialized instance in the global Mutex
 77          *instance = Some(Arc::new(explorerd));
 78      }
 79  
 80      // Return a clone of the shared instance
 81      Arc::clone(instance.as_ref().unwrap())
 82  }
 83  
 84  /// Auxiliary function that validates the correct handling of an invalid JSON-RPC parameter. It
 85  /// prepares a JSON-RPC request with the provided method and params. It then sends the request using
 86  /// the [`Explorerd::handle_request`] function of the provided [`Explorerd`] instance. Verifies the
 87  /// response is an error, matching the expected error code and message.
 88  pub async fn validate_invalid_rpc_parameter(
 89      explorerd: &Explorerd,
 90      method_name: &str,
 91      params: &[JsonValue],
 92      expected_error_code: i32,
 93      expected_error_message: &str,
 94  ) {
 95      // Prepare an invalid JSON-RPC request with the provided `params`
 96      let request = JsonRequest {
 97          id: 1,
 98          jsonrpc: "2.0",
 99          method: method_name.to_string(),
100          params: JsonValue::Array(params.to_vec()),
101      };
102  
103      // Call `handle_request` on the Explorerd instance
104      let response = explorerd.handle_request(request).await;
105  
106      // Verify response is a `JsonError` with the appropriate error code and message
107      match response {
108          JsonResult::Error(actual_error) => {
109              assert_eq!(actual_error.error.message, expected_error_message);
110              assert_eq!(actual_error.error.code, expected_error_code);
111          }
112          _ => panic!(
113              "Expected a JSON error response for method: {method_name}, but got something else"
114          ),
115      }
116  }
117  
118  /// Auxiliary function that validates the handling of non-empty parameters when they are supposed
119  /// to be empty for the given RPC `method`. It uses the provided [`Explorerd`] instance to ensure
120  /// that unexpected non-empty parameters result in the expected error for invalid parameters.
121  pub async fn validate_empty_rpc_parameters(explorerd: &Explorerd, method: &str) {
122      // Prepare a JSON-RPC request for `ping_darkfid`
123      let request = JsonRequest {
124          id: 1,
125          jsonrpc: "2.0",
126          method: method.to_string(),
127          params: JsonValue::Array(vec![JsonValue::String("non_empty_param".to_string())]),
128      };
129  
130      // Call `handle_request` on the Explorerd instance.
131      let response = explorerd.handle_request(request).await;
132  
133      // Verify the response is a `JsonError` with the `PingFailed` error code
134      match response {
135          JsonResult::Error(actual_error) => {
136              let expected_error_code = ErrorCode::InvalidParams.code();
137              let expected_error_msg =
138                  "Parameters not permited, received: \"[\\\"non_empty_param\\\"]\"";
139              assert_eq!(actual_error.error.code, expected_error_code);
140              assert_eq!(actual_error.error.message, expected_error_msg);
141          }
142          _ => panic!("Expected a JSON object for the response, but got something else"),
143      }
144  }
145  
146  /// Auxiliary function that validates the handling of an invalid contract ID when calling the specified
147  /// JSON-RPC method, ensuring appropriate error responses from provided [`Explorerd`].
148  pub fn validate_invalid_rpc_contract_id(explorerd: &Explorerd, method: &str) {
149      validate_invalid_rpc_hash_parameter(explorerd, method, "contract_id", "Invalid contract ID");
150  }
151  
152  /// Auxiliary function that validates the handling of an invalid header hash when calling the specified
153  /// JSON-RPC `method`, ensuring appropriate error responses from provided [`Explorerd`].
154  pub fn validate_invalid_rpc_header_hash(explorerd: &Explorerd, method: &str) {
155      validate_invalid_rpc_hash_parameter(explorerd, method, "header_hash", "Invalid header hash");
156  }
157  
158  /// Auxiliary function that validates the handling of an invalid tx hash when calling the specified JSON-RPC
159  /// `method`, ensuring appropriate error responses from provided [`Explorerd`].
160  pub fn validate_invalid_rpc_tx_hash(explorerd: &Explorerd, method: &str) {
161      validate_invalid_rpc_hash_parameter(explorerd, method, "tx_hash", "Invalid tx hash");
162  }
163  
164  /// Auxiliary function that validates the correct handling of invalid hash parameters
165  /// when calling the given RPC `method` using the provided [`Explorerd`]. This includes checks for
166  /// missing parameters, incorrect parameter types, and invalid hash values, ensuring it returns
167  /// error responses matching the expected error codes and messages.
168  fn validate_invalid_rpc_hash_parameter(
169      explorerd: &Explorerd,
170      method: &str,
171      parameter_name: &str,
172      invalid_hash_value_message: &str,
173  ) {
174      smol::block_on(async {
175          // Test for missing `parameter_name` parameter
176          validate_invalid_rpc_parameter(
177              explorerd,
178              method,
179              &[],
180              ErrorCode::InvalidParams.code(),
181              &format!("Parameter '{parameter_name}' at index 0 is missing"),
182          )
183          .await;
184  
185          // Test for invalid `parameter_name` parameter type
186          validate_invalid_rpc_parameter(
187              explorerd,
188              method,
189              &[JsonValue::Number(123.0)],
190              ErrorCode::InvalidParams.code(),
191              &format!("Parameter '{parameter_name}' is not a valid string"),
192          )
193          .await;
194  
195          // Test for invalid `contract_id` value
196          validate_invalid_rpc_parameter(
197              explorerd,
198              method,
199              &[JsonValue::String("0x0222".to_string())],
200              ErrorCode::InvalidParams.code(),
201              &format!("{invalid_hash_value_message}: 0x0222"),
202          )
203          .await;
204      });
205  }