/* This file is part of DarkFi (https://dark.fi)
*
* Copyright (C) 2020-2025 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::{collections::HashSet, sync::Arc, time::Instant};
use async_trait::async_trait;
use smol::lock::{MutexGuard, RwLock};
use tinyjson::JsonValue;
use tracing::{debug, error, trace, warn};
use url::Url;
use darkfi::{
error::RpcError,
rpc::{
client::RpcClient,
jsonrpc::{
validate_empty_params, ErrorCode, JsonError, JsonRequest, JsonResponse, JsonResult,
},
server::RequestHandler,
},
system::StoppableTaskPtr,
Error, Result,
};
use crate::{
error::{server_error, ExplorerdError},
Explorerd,
};
/// RPC block related requests
pub mod blocks;
/// RPC handlers for contract-related perations
pub mod contracts;
/// RPC handlers for blockchain statistics and metrics
pub mod statistics;
/// RPC handlers for transaction data, lookups, and processing
pub mod transactions;
#[async_trait]
impl RequestHandler<()> for Explorerd {
/// Handles an incoming JSON-RPC request by executing the appropriate individual request handler
/// implementation based on the request's `method` field and using the provided parameters.
/// Supports methods across various categories, including block-related queries, contract interactions,
/// transaction lookups, statistical queries, and miscellaneous operations. If an invalid
/// method is requested, an appropriate error is returned.
///
/// The function performs the error handling, allowing individual RPC method handlers to propagate
/// errors via the `?` operator. It ensures uniform translation of errors into JSON-RPC error responses.
/// Additionally, it handles the creation of `JsonResponse` or `JsonError` objects, enabling method
/// handlers to focus solely on core logic. Individual RPC handlers return a `JsonValue`, which this
/// function translates into the corresponding `JsonResult`.
///
/// Unified logging is incorporated, so individual handlers only propagate the error
/// for it to be logged. Logs include detailed error information, such as method names, parameters,
/// and JSON-RPC errors, providing consistent and informative error trails for debugging.
///
/// ## Example Log Message
/// ```
/// 05:11:02 [ERROR] RPC Request Failure: method: transactions.get_transactions_by_header_hash,
/// params: ["0x0222"], error: {"error":{"code":-32602,"message":"Invalid header hash: 0x0222"},
/// "id":1,"jsonrpc":"2.0"}
/// ```
async fn handle_request(&self, req: JsonRequest) -> JsonResult {
debug!(target: "explorerd::rpc", "--> {}", req.stringify().unwrap());
// Store method and params for later use
let method = req.method.as_str();
let params = &req.params;
// Handle ping case, as it returns a JsonResponse
if method == "ping" {
return self.pong(req.id, params.clone()).await
}
// Match all other methods
let result = match req.method.as_str() {
// =====================
// Blocks methods
// =====================
"blocks.get_last_n_blocks" => self.blocks_get_last_n_blocks(params).await,
"blocks.get_blocks_in_heights_range" => {
self.blocks_get_blocks_in_heights_range(params).await
}
"blocks.get_block_by_hash" => self.blocks_get_block_by_hash(params).await,
// =====================
// Transactions methods
// =====================
"transactions.get_transactions_by_header_hash" => {
self.transactions_get_transactions_by_header_hash(params).await
}
"transactions.get_transaction_by_hash" => {
self.transactions_get_transaction_by_hash(params).await
}
// =====================
// Statistics methods
// =====================
"statistics.get_basic_statistics" => self.statistics_get_basic_statistics(params).await,
"statistics.get_metric_statistics" => {
self.statistics_get_metric_statistics(params).await
}
"statistics.get_latest_metric_statistics" => {
self.statistics_get_latest_metric_statistics(params).await
}
// =====================
// Contract methods
// =====================
"contracts.get_native_contracts" => self.contracts_get_native_contracts(params).await,
"contracts.get_contract_source_code_paths" => {
self.contracts_get_contract_source_code_paths(params).await
}
"contracts.get_contract_source" => self.contracts_get_contract_source(params).await,
// =====================
// Miscellaneous methods
// =====================
"ping_darkfid" => self.ping_darkfid(params).await,
// TODO: add any other useful methods
// ==============
// Invalid method
// ==============
_ => Err(RpcError::MethodNotFound(method.to_string()).into()),
};
// Process the result of the individual request handler, handling success or errors and translating
// them into an appropriate `JsonResult`.
match result {
// Successfully completed the request
Ok(value) => JsonResponse::new(value, req.id).into(),
// Handle errors when processing parameters
Err(Error::RpcServerError(RpcError::InvalidJson(e))) => {
let json_error =
JsonError::new(ErrorCode::InvalidParams, Some(e.to_string()), req.id);
// Log the parameter error
log_request_failure(&req.method, params, &json_error);
// Convert error to JsonResult
json_error.into()
}
// Handle server errors
Err(Error::RpcServerError(RpcError::ServerError(e))) => {
// Remove the extra '&' and reference directly from e
let json_error = match e.downcast_ref::() {
Some(e_expl) => {
// Successfully downcast to ExplorerdRpcError; call the typed function
server_error(e_expl, req.id, None)
}
None => {
// Return InternalError with the logged details
JsonError::new(ErrorCode::InternalError, Some(e.to_string()), req.id)
}
};
// Log the server error
log_request_failure(&req.method, params, &json_error);
// Convert error to JsonResult
json_error.into()
}
// Catch-all for any other unexpected errors
Err(e) => {
// Return InternalError with the logged details
let json_error =
JsonError::new(ErrorCode::InternalError, Some(e.to_string()), req.id);
// Log the unexpected error
log_request_failure(&req.method, params, &json_error);
// Convert error to JsonResult
json_error.into()
}
}
}
async fn connections_mut(&self) -> MutexGuard<'_, HashSet> {
self.rpc_connections.lock().await
}
}
/// A RPC client for interacting with a Darkfid JSON-RPC endpoint, enabling communication with Darkfid blockchain nodes.
/// Supports connection management, request handling, and graceful shutdowns.
/// Implemented for shared access across ownership boundaries using `Arc`, with connection state managed via an `RwLock`.
pub struct DarkfidRpcClient {
/// JSON-RPC client used to communicate with the Darkfid daemon. A value of `None` indicates no active connection.
/// The `RwLock` allows the client to be shared across ownership boundaries while managing the connection state.
rpc_client: RwLock