diff --git a/Cargo.lock b/Cargo.lock index 0c696356f..15ed72730 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -8453,6 +8453,7 @@ dependencies = [ name = "zaino-state" version = "0.1.2" dependencies = [ + "anyhow", "arc-swap", "async-trait", "bitflags 2.9.4", diff --git a/integration-tests/tests/json_server.rs b/integration-tests/tests/json_server.rs index 2a6b0ebde..de3cac884 100644 --- a/integration-tests/tests/json_server.rs +++ b/integration-tests/tests/json_server.rs @@ -715,8 +715,10 @@ async fn z_get_address_utxos_inner() { mod zcashd { use super::*; + #[allow(deprecated)] pub(crate) mod zcash_indexer { - use zaino_state::LightWalletIndexer; + use zaino_fetch::jsonrpsee::response::block_header::GetBlockHeaderError; + use zaino_state::{FetchServiceError, LightWalletIndexer}; use zebra_rpc::methods::GetBlock; use super::*; @@ -939,6 +941,23 @@ mod zcashd { .unwrap(); assert_eq!(zcashd_get_block_header, zainod_block_header_response); } + + // Try to fetch a non-existent block header + match zcashd_subscriber + .get_block_header( + "00000000008b4fdc4bae2868208f752526abfa441121cc0ac6526ddcb827befe".into(), + false, + ) + .await + { + Err(FetchServiceError::RpcError(rpc_error)) => { + match GetBlockHeaderError::try_from(rpc_error) { + Ok(GetBlockHeaderError::MissingBlock { .. }) => (), + other => panic!("unexpected method error mapping: {:?}", other), + } + } + other => panic!("unexpected top-level error: {other:?}"), + } } #[tokio::test(flavor = "multi_thread")] diff --git a/zaino-fetch/src/jsonrpsee/connector.rs b/zaino-fetch/src/jsonrpsee/connector.rs index 8c7148ccc..1ecb46f40 100644 --- a/zaino-fetch/src/jsonrpsee/connector.rs +++ b/zaino-fetch/src/jsonrpsee/connector.rs @@ -5,10 +5,10 @@ use base64::{engine::general_purpose, Engine}; use http::Uri; use reqwest::{Client, ClientBuilder, Url}; +use serde::de::{DeserializeOwned, Error}; use serde::{Deserialize, Serialize}; use std::path::PathBuf; use std::{ - any::type_name, convert::Infallible, fmt, fs, net::SocketAddr, @@ -22,21 +22,24 @@ use std::{ use tracing::error; use zebra_rpc::client::ValidateAddressResponse; +use crate::jsonrpsee::connector::map_err::{MapRpcError, Mapper}; +use crate::jsonrpsee::error::{JsonRpcErrorKind, RpcErrorKind, ValidatorErrorKind}; use crate::jsonrpsee::response::address_deltas::GetAddressDeltasError; +use crate::jsonrpsee::response::info::GetInfoResponse; use crate::jsonrpsee::{ - error::{JsonRpcError, TransportError}, + error::TransportError, response::{ address_deltas::{GetAddressDeltasParams, GetAddressDeltasResponse}, block_deltas::{BlockDeltas, BlockDeltasError}, block_header::{GetBlockHeader, GetBlockHeaderError}, block_subsidy::GetBlockSubsidy, + blockchain_info::GetBlockchainInfoResponse, mining_info::GetMiningInfoWire, peer_info::GetPeerInfo, GetBalanceError, GetBalanceResponse, GetBlockCountResponse, GetBlockError, GetBlockHash, - GetBlockResponse, GetBlockchainInfoResponse, GetInfoResponse, GetMempoolInfoResponse, - GetSubtreesError, GetSubtreesResponse, GetTransactionResponse, GetTreestateError, - GetTreestateResponse, GetUtxosError, GetUtxosResponse, SendTransactionError, - SendTransactionResponse, TxidsError, TxidsResponse, + GetBlockResponse, GetMempoolInfoResponse, GetSubtreesError, GetSubtreesResponse, + GetTransactionResponse, GetTreestateError, GetTreestateResponse, GetUtxosError, + GetUtxosResponse, SendTransactionError, SendTransactionResponse, TxidsError, TxidsResponse, }, }; @@ -58,15 +61,11 @@ struct RpcResponse { error: Option, } -/// Json RPSee Error type. -#[derive(Serialize, Deserialize, Debug)] +#[derive(Serialize, Deserialize, Debug, Clone)] pub struct RpcError { - /// Error Code. pub code: i64, - /// Error Message. pub message: String, - /// Error Data. - pub data: Option, + pub data: Option, } impl RpcError { @@ -87,18 +86,43 @@ impl RpcError { fallback_message: impl Into, ) -> Self { RpcError { - // We can use the actual JSON-RPC code: code: error_obj.code() as i64, - // Or combine the fallback with the original message: message: format!("{}: {}", fallback_message.into(), error_obj.message()), - // If you want to store the data too: data: error_obj .data() .map(|raw| serde_json::from_str(raw.get()).unwrap()), } } + + pub fn kind(&self) -> RpcErrorKind { + let m = self.message.to_ascii_lowercase(); + match self.code { + // JSON-RPC standard + -32700 => RpcErrorKind::JsonRpc(JsonRpcErrorKind::Parse), + -32600 => RpcErrorKind::JsonRpc(JsonRpcErrorKind::InvalidRequest), + -32601 => RpcErrorKind::JsonRpc(JsonRpcErrorKind::MethodNotFound), + -32602 => RpcErrorKind::JsonRpc(JsonRpcErrorKind::InvalidParams), + -32603 => RpcErrorKind::JsonRpc(JsonRpcErrorKind::Internal), + + // Bitcoin/Zcash-ish + -28 => RpcErrorKind::Validator(ValidatorErrorKind::InWarmup), + -27 => RpcErrorKind::Validator(ValidatorErrorKind::AlreadyExists), + -26 | -25 => RpcErrorKind::Validator(ValidatorErrorKind::Rejected), + -5 | -8 if m.contains("not found") => { + RpcErrorKind::Validator(ValidatorErrorKind::NotFound) + } + + other => RpcErrorKind::Validator(ValidatorErrorKind::Other(other)), + } + } +} + +impl From for RpcError { + fn from(x: Infallible) -> Self { + match x {} // Unreachable + } } impl fmt::Display for RpcError { @@ -109,6 +133,39 @@ impl fmt::Display for RpcError { impl std::error::Error for RpcError {} +mod map_err { + use super::*; + + // Trait is generic over the MethodError type parameter + pub trait MapRpcError { + fn map(raw: RpcError) -> RpcRequestError; + } + + // A zero-sized type to host the impls + pub struct Mapper; + + // Method errors + impl MapRpcError for Mapper + where + MethodError: From + std::error::Error + Send + Sync + 'static, + { + #[inline] + fn map(raw: RpcError) -> RpcRequestError { + RpcRequestError::Method(MethodError::from(raw)) + } + } + + // Infallible endpoints + impl MapRpcError for Mapper { + #[inline] + fn map(raw: RpcError) -> RpcRequestError { + // A JSON-RPC error here means the node "misbehaved" (under our model) + // A better word is "unexpected" + RpcRequestError::UnexpectedErrorResponse(Box::new(raw)) + } + } +} + // Helper function to read and parse the cookie file content. // Zebra's RPC server expects Basic Auth with username "__cookie__" // and the token from the cookie file as the password. @@ -278,85 +335,87 @@ impl JsonRpSeeConnector { /// Sends a jsonRPC request and returns the response. /// NOTE: This function currently resends the call up to 5 times on a server response of "Work queue depth exceeded". /// This is because the node's queue can become overloaded and stop servicing RPCs. - async fn send_request< - T: std::fmt::Debug + Serialize, - R: std::fmt::Debug + for<'de> Deserialize<'de> + ResponseToError, - >( + async fn send_request( &self, method: &str, params: T, - ) -> Result> + ) -> Result> where - R::RpcError: Send + Sync + 'static, + T: std::fmt::Debug + Serialize, + R: std::fmt::Debug + DeserializeOwned, + MethodError: std::fmt::Debug + Send + Sync + 'static, + map_err::Mapper: map_err::MapRpcError, { - let id = self.id_counter.fetch_add(1, Ordering::SeqCst); + #[derive(Deserialize)] + struct RpcEnvelope { + result: Option, + error: Option, + } - let max_attempts = 5; - let mut attempts = 0; - loop { - attempts += 1; + let id = self.id_counter.fetch_add(1, Ordering::SeqCst); - let request_builder = self - .build_request(method, ¶ms, id) - .map_err(RpcRequestError::JsonRpc)?; + let request_builder = self + .build_request(method, ¶ms, id) + .map_err(RpcRequestError::JsonRpc)?; - let response = request_builder - .send() - .await - .map_err(|e| RpcRequestError::Transport(TransportError::ReqwestError(e)))?; + let response = request_builder + .send() + .await + .map_err(|e| RpcRequestError::Transport(TransportError::ReqwestError(e)))?; - let status = response.status(); + let status = response.status(); + let body_bytes = response + .bytes() + .await + .map_err(|e| RpcRequestError::Transport(TransportError::ReqwestError(e)))?; + let body_str = String::from_utf8_lossy(&body_bytes); - let body_bytes = response - .bytes() - .await - .map_err(|e| RpcRequestError::Transport(TransportError::ReqwestError(e)))?; + if body_str.contains("Work queue depth exceeded") { + return Err(RpcRequestError::ServerWorkQueueFull); + } - let body_str = String::from_utf8_lossy(&body_bytes); + if let Ok(env) = serde_json::from_slice::>(&body_bytes) { + if let Some(err) = env.error { + error!( + target: "zaino_fetch::jsonrpc", + method = %method, + id = id, + code = err.code, + kind = ?err.kind(), + message = %err.message, + params = tracing::field::debug(¶ms), + "JSON-RPC method error" + ); + // return Err(RpcRequestError::Method(MethodError::from(err))); - if body_str.contains("Work queue depth exceeded") { - if attempts >= max_attempts { - return Err(RpcRequestError::ServerWorkQueueFull); - } - tokio::time::sleep(std::time::Duration::from_millis(500)).await; - continue; + // This line picks the correct impl: + // - If MethodError = Infallible -> uses the Infallible impl + // - Else requires MethodError: From + Error … + return Err(>::map(err)); } + if let Some(result) = env.result { + return Ok(result); + } + return Err(RpcRequestError::Transport( + TransportError::EmptyResponseBody, + )); + } - let code = status.as_u16(); - return match code { - // Invalid - ..100 | 600.. => Err(RpcRequestError::Transport( - TransportError::InvalidStatusCode(code), - )), - // Informational | Redirection - 100..200 | 300..400 => Err(RpcRequestError::Transport( - TransportError::UnexpectedStatusCode(code), - )), - // Success - 200..300 => { - let response: RpcResponse = serde_json::from_slice(&body_bytes) - .map_err(|e| TransportError::BadNodeData(Box::new(e), type_name::()))?; - - match (response.error, response.result) { - (Some(error), _) => Err(RpcRequestError::Method( - R::RpcError::try_from(error).map_err(|e| { - RpcRequestError::UnexpectedErrorResponse(Box::new(e)) - })?, - )), - (None, Some(result)) => match result.to_error() { - Ok(r) => Ok(r), - Err(e) => Err(RpcRequestError::Method(e)), - }, - (None, None) => Err(RpcRequestError::Transport( - TransportError::EmptyResponseBody, - )), - } - // Error - } - 400..600 => Err(RpcRequestError::Transport(TransportError::ErrorStatusCode( - code, - ))), - }; + let code = status.as_u16(); + match code { + ..100 | 600.. => Err(RpcRequestError::Transport( + TransportError::InvalidStatusCode(code), + )), + 100..200 | 300..400 => Err(RpcRequestError::Transport( + TransportError::UnexpectedStatusCode(code), + )), + 200..300 => Err(RpcRequestError::Transport(TransportError::BadNodeData( + Box::new(serde_json::Error::custom("non-JSON response with 2xx")), + std::any::type_name::(), + ))), + 400..600 => Err(RpcRequestError::Transport(TransportError::ErrorStatusCode( + code, + ))), } } @@ -429,8 +488,7 @@ impl JsonRpSeeConnector { /// method: post /// tags: control pub async fn get_info(&self) -> Result> { - self.send_request::<(), GetInfoResponse>("getinfo", ()) - .await + self.send_request("getinfo", ()).await } /// @@ -440,8 +498,7 @@ impl JsonRpSeeConnector { pub async fn get_blockchain_info( &self, ) -> Result> { - self.send_request::<(), GetBlockchainInfoResponse>("getblockchaininfo", ()) - .await + self.send_request("getblockchaininfo", ()).await } /// Returns details on the active state of the TX memory pool. @@ -456,8 +513,7 @@ impl JsonRpSeeConnector { pub async fn get_mempool_info( &self, ) -> Result> { - self.send_request::<(), GetMempoolInfoResponse>("getmempoolinfo", ()) - .await + self.send_request("getmempoolinfo", ()).await } /// Returns data about each connected network node as a json array of objects. @@ -467,8 +523,7 @@ impl JsonRpSeeConnector { /// /// Current `zebrad` does not include the same fields as `zcashd`. pub async fn get_peer_info(&self) -> Result> { - self.send_request::<(), GetPeerInfo>("getpeerinfo", ()) - .await + self.send_request("getpeerinfo", ()).await } /// Returns the proof-of-work difficulty as a multiple of the minimum difficulty. @@ -479,8 +534,7 @@ impl JsonRpSeeConnector { pub async fn get_difficulty( &self, ) -> Result> { - self.send_request::<(), GetDifficultyResponse>("getdifficulty", ()) - .await + self.send_request("getdifficulty", ()).await } /// Returns block subsidy reward, taking into account the mining slow start and the founders reward, of block at index provided. @@ -619,8 +673,7 @@ impl JsonRpSeeConnector { /// [The function in rpc/blockchain.cpp](https://github.com/zcash/zcash/blob/654a8be2274aa98144c80c1ac459400eaf0eacbe/src/rpc/blockchain.cpp#L325) /// where `return chainActive.Tip()->GetBlockHash().GetHex();` is the [return expression](https://github.com/zcash/zcash/blob/654a8be2274aa98144c80c1ac459400eaf0eacbe/src/rpc/blockchain.cpp#L339)returning a `std::string` pub async fn get_best_blockhash(&self) -> Result> { - self.send_request::<(), GetBlockHash>("getbestblockhash", ()) - .await + self.send_request("getbestblockhash", ()).await } /// Returns the height of the most recent block in the best valid block chain @@ -632,8 +685,7 @@ impl JsonRpSeeConnector { pub async fn get_block_count( &self, ) -> Result> { - self.send_request::<(), GetBlockCountResponse>("getblockcount", ()) - .await + self.send_request("getblockcount", ()).await } /// Return information about the given Zcash address. @@ -658,8 +710,7 @@ impl JsonRpSeeConnector { /// method: post /// tags: blockchain pub async fn get_raw_mempool(&self) -> Result> { - self.send_request::<(), TxidsResponse>("getrawmempool", ()) - .await + self.send_request("getrawmempool", ()).await } /// Returns information about the given block's Sapling & Orchard tree state. @@ -676,7 +727,8 @@ impl JsonRpSeeConnector { hash_or_height: String, ) -> Result> { let params = vec![serde_json::to_value(hash_or_height).map_err(RpcRequestError::JsonRpc)?]; - self.send_request("z_gettreestate", params).await + // self.send_request("z_gettreestate", params).await + todo!() } /// Returns information about a range of Sapling or Orchard subtrees. @@ -707,7 +759,8 @@ impl JsonRpSeeConnector { serde_json::to_value(start_index).map_err(RpcRequestError::JsonRpc)?, ], }; - self.send_request("z_getsubtreesbyindex", params).await + // self.send_request("z_getsubtreesbyindex", params).await + todo!() } /// Returns the raw transaction data, as a [`GetTransactionResponse`]. @@ -736,7 +789,8 @@ impl JsonRpSeeConnector { ], }; - self.send_request("getrawtransaction", params).await + // self.send_request("getrawtransaction", params).await + todo!() } /// Returns the transaction ids made by the provided transparent addresses. @@ -763,7 +817,8 @@ impl JsonRpSeeConnector { "end": end }); - self.send_request("getaddresstxids", vec![params]).await + // self.send_request("getaddresstxids", vec![params]).await + todo!() } /// Returns all unspent outputs for a list of addresses. @@ -780,14 +835,16 @@ impl JsonRpSeeConnector { addresses: Vec, ) -> Result, RpcRequestError> { let params = vec![serde_json::json!({ "addresses": addresses })]; - self.send_request("getaddressutxos", params).await + // self.send_request("getaddressutxos", params).await + todo!() } /// Returns a json object containing mining-related information. /// /// `zcashd` reference (may be outdated): [`getmininginfo`](https://zcash.github.io/rpc/getmininginfo.html) pub async fn get_mining_info(&self) -> Result> { - self.send_request("getmininginfo", ()).await + // self.send_request("getmininginfo", ()).await + todo!() } /// Returns the estimated network solutions per second based on the last n blocks. @@ -824,8 +881,9 @@ impl JsonRpSeeConnector { // default to -1 params.push(serde_json::json!(-1_i32)) } + todo!() - self.send_request("getnetworksolps", params).await + // self.send_request("getnetworksolps", params).await } } diff --git a/zaino-fetch/src/jsonrpsee/error.rs b/zaino-fetch/src/jsonrpsee/error.rs index d2034ad1b..188e99b62 100644 --- a/zaino-fetch/src/jsonrpsee/error.rs +++ b/zaino-fetch/src/jsonrpsee/error.rs @@ -2,6 +2,8 @@ use std::io; +use crate::jsonrpsee::connector::RpcError; + /// Error type for JSON-RPC responses. #[derive(Debug, serde::Serialize, serde::Deserialize)] pub struct JsonRpcError { @@ -77,3 +79,27 @@ impl From for tonic::Status { err.to_grpc_status() } } + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum JsonRpcErrorKind { + Parse, + InvalidRequest, + MethodNotFound, + InvalidParams, + Internal, +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum ValidatorErrorKind { + NotFound, // (-5/-8) with “not found” + Rejected, // (-26/-25) + AlreadyExists, // (-27) + InWarmup, // (-28) + Other(i64), +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum RpcErrorKind { + JsonRpc(JsonRpcErrorKind), + Validator(ValidatorErrorKind), +} diff --git a/zaino-fetch/src/jsonrpsee/response.rs b/zaino-fetch/src/jsonrpsee/response.rs index 8b8623ce8..66a59f6e1 100644 --- a/zaino-fetch/src/jsonrpsee/response.rs +++ b/zaino-fetch/src/jsonrpsee/response.rs @@ -7,27 +7,21 @@ pub mod address_deltas; pub mod block_deltas; pub mod block_header; pub mod block_subsidy; +pub mod blockchain_info; pub mod common; +pub mod info; pub mod mining_info; pub mod peer_info; -use std::{convert::Infallible, num::ParseIntError}; +use std::convert::Infallible; use hex::FromHex; -use serde::{de::Error as DeserError, Deserialize, Deserializer, Serialize}; +use serde::de::Error as DeserError; -use zebra_chain::{ - amount::{Amount, NonNegative}, - block::Height, - value_balance::ValueBalance, - work::difficulty::CompactDifficulty, -}; -use zebra_rpc::{ - client::{GetBlockchainInfoBalance, ValidateAddressResponse}, - methods::opthex, -}; +use zebra_chain::{block::Height, work::difficulty::CompactDifficulty}; +use zebra_rpc::{client::ValidateAddressResponse, methods::opthex}; -use crate::jsonrpsee::connector::ResponseToError; +use crate::jsonrpsee::{connector::ResponseToError, response::common::balance::ChainBalance}; use super::connector::RpcError; @@ -39,68 +33,6 @@ impl TryFrom for Infallible { } } -/// Response to a `getinfo` RPC request. -/// -/// This is used for the output parameter of [`crate::jsonrpsee::connector::JsonRpSeeConnector::get_info`]. -#[derive(Clone, Debug, PartialEq, serde::Deserialize, serde::Serialize)] -pub struct GetInfoResponse { - /// The node version - #[serde(default)] - version: u64, - /// The node version build number - pub build: String, - /// The server sub-version identifier, used as the network protocol user-agent - pub subversion: String, - /// The protocol version - #[serde(default)] - #[serde(rename = "protocolversion")] - protocol_version: u32, - - /// The current number of blocks processed in the server - #[serde(default)] - blocks: u32, - - /// The total (inbound and outbound) number of connections the node has - #[serde(default)] - connections: usize, - - /// The proxy (if any) used by the server. Currently always `None` in Zebra. - #[serde(default)] - #[serde(skip_serializing_if = "Option::is_none")] - proxy: Option, - - /// The current network difficulty - #[serde(default)] - difficulty: f64, - - /// True if the server is running in testnet mode, false otherwise - #[serde(default)] - testnet: bool, - - /// The minimum transaction fee in ZEC/kB - #[serde(default)] - #[serde(rename = "paytxfee")] - pay_tx_fee: f64, - - /// The minimum relay fee for non-free transactions in ZEC/kB - #[serde(default)] - #[serde(rename = "relayfee")] - relay_fee: f64, - - /// The last error or warning message, or "no errors" if there are no errors - #[serde(default)] - errors: String, - - /// The time of the last error or warning message, or "no errors timestamp" if there are no errors - #[serde(default)] - #[serde(rename = "errorstimestamp")] - errors_timestamp: ErrorsTimestamp, -} - -impl ResponseToError for GetInfoResponse { - type RpcError = Infallible; -} - impl ResponseToError for GetDifficultyResponse { type RpcError = Infallible; } @@ -133,101 +65,6 @@ impl Default for ErrorsTimestamp { } } -impl From for zebra_rpc::methods::GetInfo { - fn from(response: GetInfoResponse) -> Self { - zebra_rpc::methods::GetInfo::new( - response.version, - response.build, - response.subversion, - response.protocol_version, - response.blocks, - response.connections, - response.proxy, - response.difficulty, - response.testnet, - response.pay_tx_fee, - response.relay_fee, - response.errors, - response.errors_timestamp.to_string(), - ) - } -} - -/// Response to a `getblockchaininfo` RPC request. -/// -/// This is used for the output parameter of [`crate::jsonrpsee::connector::JsonRpSeeConnector::get_blockchain_info`]. -#[derive(Clone, Debug, PartialEq, serde::Deserialize, serde::Serialize)] -pub struct GetBlockchainInfoResponse { - /// Current network name as defined in BIP70 (main, test, regtest) - pub chain: String, - - /// The current number of blocks processed in the server, numeric - pub blocks: zebra_chain::block::Height, - - /// The hash of the currently best block, in big-endian order, hex-encoded - #[serde(rename = "bestblockhash", with = "hex")] - pub best_block_hash: zebra_chain::block::Hash, - - /// If syncing, the estimated height of the chain, else the current best height, numeric. - /// - /// In Zebra, this is always the height estimate, so it might be a little inaccurate. - #[serde(rename = "estimatedheight")] - pub estimated_height: zebra_chain::block::Height, - - /// Chain supply balance - #[serde(default)] - #[serde(rename = "chainSupply")] - chain_supply: ChainBalance, - - /// Status of network upgrades - pub upgrades: indexmap::IndexMap< - zebra_rpc::methods::ConsensusBranchIdHex, - zebra_rpc::methods::NetworkUpgradeInfo, - >, - - /// Value pool balances - #[serde(rename = "valuePools")] - value_pools: [ChainBalance; 5], - - /// Branch IDs of the current and upcoming consensus rules - pub consensus: zebra_rpc::methods::TipConsensusBranch, - - /// The current number of headers we have validated in the best chain, that is, - /// the height of the best chain. - #[serde(default = "default_header")] - headers: Height, - - /// The estimated network solution rate in Sol/s. - #[serde(default)] - difficulty: f64, - - /// The verification progress relative to the estimated network chain tip. - #[serde(default)] - #[serde(rename = "verificationprogress")] - verification_progress: f64, - - /// The total amount of work in the best chain, hex-encoded. - #[serde(default)] - #[serde(rename = "chainwork")] - chain_work: ChainWork, - - /// Whether this node is pruned, currently always false in Zebra. - #[serde(default)] - pruned: bool, - - /// The estimated size of the block and undo files on disk - #[serde(default)] - size_on_disk: u64, - - /// The current number of note commitments in the commitment tree - #[serde(default)] - commitments: u64, -} - -impl ResponseToError for GetBlockchainInfoResponse { - type RpcError = Infallible; -} - /// Response to a `getdifficulty` RPC request. #[derive(Clone, Debug, PartialEq, serde::Deserialize, serde::Serialize)] pub struct GetDifficultyResponse(pub f64); @@ -240,147 +77,6 @@ impl ResponseToError for GetNetworkSolPsResponse { type RpcError = Infallible; } -fn default_header() -> Height { - Height(0) -} - -#[derive(Clone, Debug, PartialEq, serde::Deserialize, serde::Serialize)] -#[serde(untagged)] -/// A wrapper type to allow both kinds of ChainWork -pub enum ChainWork { - /// Returned from zcashd, a chainwork is a String representing a - /// base-16 integer - Str(String), - /// Returned from zebrad, a chainwork is an integer - Num(u64), -} - -/// Error type used for the `chainwork` field of the `getblockchaininfo` RPC request. -#[derive(Debug, thiserror::Error)] -pub enum ChainWorkError {} - -impl ResponseToError for ChainWork { - type RpcError = ChainWorkError; -} -impl TryFrom for ChainWorkError { - type Error = RpcError; - - fn try_from(value: RpcError) -> Result { - // TODO: attempt to convert RpcError into errors specific to this RPC response - Err(value) - } -} - -impl TryFrom for u64 { - type Error = ParseIntError; - - fn try_from(value: ChainWork) -> Result { - match value { - ChainWork::Str(s) => u64::from_str_radix(&s, 16), - ChainWork::Num(u) => Ok(u), - } - } -} - -impl Default for ChainWork { - fn default() -> Self { - ChainWork::Num(0) - } -} - -/// Wrapper struct for a Zebra [`GetBlockchainInfoBalance`], enabling custom -/// deserialisation logic to handle both zebrad and zcashd. -#[derive(Clone, Debug, PartialEq, Serialize)] -pub struct ChainBalance(GetBlockchainInfoBalance); - -impl ResponseToError for ChainBalance { - type RpcError = Infallible; -} - -impl<'de> Deserialize<'de> for ChainBalance { - fn deserialize(deserializer: D) -> Result - where - D: Deserializer<'de>, - { - #[derive(Deserialize, Debug)] - struct TempBalance { - #[serde(default)] - id: String, - #[serde(rename = "chainValue")] - chain_value: f64, - #[serde(rename = "chainValueZat")] - chain_value_zat: u64, - #[allow(dead_code)] - #[serde(default)] - monitored: bool, - } - let temp = TempBalance::deserialize(deserializer)?; - let computed_zat = (temp.chain_value * 100_000_000.0).round() as u64; - if computed_zat != temp.chain_value_zat { - return Err(D::Error::custom(format!( - "chainValue and chainValueZat mismatch: computed {} but got {}", - computed_zat, temp.chain_value_zat - ))); - } - let amount = Amount::::from_bytes(temp.chain_value_zat.to_le_bytes()) - .map_err(|e| DeserError::custom(e.to_string()))?; - match temp.id.as_str() { - "transparent" => Ok(ChainBalance(GetBlockchainInfoBalance::transparent( - amount, None, /*TODO: handle optional delta*/ - ))), - "sprout" => Ok(ChainBalance(GetBlockchainInfoBalance::sprout( - amount, None, /*TODO: handle optional delta*/ - ))), - "sapling" => Ok(ChainBalance(GetBlockchainInfoBalance::sapling( - amount, None, /*TODO: handle optional delta*/ - ))), - "orchard" => Ok(ChainBalance(GetBlockchainInfoBalance::orchard( - amount, None, /*TODO: handle optional delta*/ - ))), - // TODO: Investigate source of undocument 'lockbox' value - // that likely is intended to be 'deferred' - "lockbox" | "deferred" => Ok(ChainBalance(GetBlockchainInfoBalance::deferred( - amount, None, - ))), - "" => Ok(ChainBalance(GetBlockchainInfoBalance::chain_supply( - // The pools are immediately summed internally, which pool we pick doesn't matter here - ValueBalance::from_transparent_amount(amount), - ))), - otherwise => todo!("error: invalid chain id deser {otherwise}"), - } - } -} - -impl Default for ChainBalance { - fn default() -> Self { - Self(GetBlockchainInfoBalance::chain_supply(ValueBalance::zero())) - } -} - -impl TryFrom for zebra_rpc::methods::GetBlockchainInfoResponse { - fn try_from(response: GetBlockchainInfoResponse) -> Result { - Ok(zebra_rpc::methods::GetBlockchainInfoResponse::new( - response.chain, - response.blocks, - response.best_block_hash, - response.estimated_height, - response.chain_supply.0, - response.value_pools.map(|pool| pool.0), - response.upgrades, - response.consensus, - response.headers, - response.difficulty, - response.verification_progress, - response.chain_work.try_into()?, - response.pruned, - response.size_on_disk, - response.commitments, - )) - } - - type Error = ParseIntError; -} - /// The transparent balance of a set of addresses. /// /// This is used for the output parameter of [`crate::jsonrpsee::connector::JsonRpSeeConnector::get_address_balance`]. @@ -878,10 +574,16 @@ impl TryFrom for zebra_rpc::methods::GetBlock { block.solution.map(Into::into), block.bits, block.difficulty, - block.chain_supply.map(|supply| supply.0), + block.chain_supply.map(|supply| supply.into_inner()), block.value_pools.map( |[transparent, sprout, sapling, orchard, deferred]| { - [transparent.0, sprout.0, sapling.0, orchard.0, deferred.0] + [ + transparent.into_inner(), + sprout.into_inner(), + sapling.into_inner(), + orchard.into_inner(), + deferred.into_inner(), + ] }, ), block.trees.into(), diff --git a/zaino-fetch/src/jsonrpsee/response/address_deltas.rs b/zaino-fetch/src/jsonrpsee/response/address_deltas.rs index bd0f03f88..d8881e143 100644 --- a/zaino-fetch/src/jsonrpsee/response/address_deltas.rs +++ b/zaino-fetch/src/jsonrpsee/response/address_deltas.rs @@ -136,17 +136,23 @@ pub enum GetAddressDeltasError { InvalidBlockRange(u32, u32), } +impl From for GetAddressDeltasError { + fn from(value: RpcError) -> Self { + todo!() + } +} + impl ResponseToError for GetAddressDeltasResponse { type RpcError = GetAddressDeltasError; } -impl TryFrom for GetAddressDeltasError { - type Error = RpcError; +// impl TryFrom for GetAddressDeltasError { +// type Error = RpcError; - fn try_from(value: RpcError) -> Result { - Err(value) - } -} +// fn try_from(value: RpcError) -> Result { +// Err(value) +// } +// } /// Represents a change in the balance of a transparent address. #[derive(Debug, Clone, Eq, PartialEq, serde::Deserialize, serde::Serialize)] diff --git a/zaino-fetch/src/jsonrpsee/response/block_header.rs b/zaino-fetch/src/jsonrpsee/response/block_header.rs index 2edeb23de..530388681 100644 --- a/zaino-fetch/src/jsonrpsee/response/block_header.rs +++ b/zaino-fetch/src/jsonrpsee/response/block_header.rs @@ -4,7 +4,10 @@ use serde::{Deserialize, Serialize}; use zebra_rpc::methods::opthex; -use crate::jsonrpsee::connector::{ResponseToError, RpcError}; +use crate::jsonrpsee::{ + connector::{ResponseToError, RpcError}, + error::{JsonRpcErrorKind, RpcErrorKind, ValidatorErrorKind}, +}; /// Response to a `getblockheader` RPC request. #[allow(clippy::large_enum_variant)] @@ -29,8 +32,30 @@ pub enum GetBlockHeaderError { InvalidVerbosity(i8), /// The requested block hash or height could not be found - #[error("Block not found: {0}")] - MissingBlock(String), + #[error("Block not found")] + MissingBlock, + + #[error("rpc error {code}: {message}")] + Other { code: i64, message: String }, +} + +// Hook: RpcErrorKind -> MethodError +impl From for GetBlockHeaderError { + fn from(raw: RpcError) -> Self { + match raw.kind() { + RpcErrorKind::JsonRpc(JsonRpcErrorKind::InvalidParams) => { + // If you can parse the given verbosity from raw.message, do it. + GetBlockHeaderError::InvalidVerbosity(1) + } + RpcErrorKind::Validator(ValidatorErrorKind::NotFound) => { + GetBlockHeaderError::MissingBlock + } + _ => GetBlockHeaderError::Other { + code: raw.code, + message: raw.message, + }, + } + } } /// Verbose response to a `getblockheader` RPC request. @@ -117,19 +142,36 @@ impl ResponseToError for GetBlockHeader { type RpcError = GetBlockHeaderError; } -impl TryFrom for GetBlockHeaderError { - type Error = RpcError; - - fn try_from(value: RpcError) -> Result { - // If the block is not in Zebra's state, returns - // [error code `-8`.](https://github.com/zcash/zcash/issues/5758) - if value.code == -8 { - Ok(Self::MissingBlock(value.message)) - } else { - Err(value) - } - } -} +// impl TryFrom for GetBlockHeaderError { +// type Error = RpcError; + +// fn try_from(value: RpcError) -> Result { +// match value.code { +// -5 | -8 => Ok(Self::MissingBlock { +// code: value.code, +// message: value.message, +// }), +// _ => Err(value), +// } +// } +// } + +// impl From for RpcError { +// fn from(e: GetBlockHeaderError) -> Self { +// match e { +// GetBlockHeaderError::MissingBlock { code, message } => RpcError { +// code, +// message, +// data: None, +// }, +// GetBlockHeaderError::InvalidVerbosity(v) => RpcError { +// code: -32602, // TODO: Abstract JSON-RPC codes away +// message: format!("Invalid verbosity: {v}"), +// data: None, +// }, +// } +// } +// } #[cfg(test)] mod tests { diff --git a/zaino-fetch/src/jsonrpsee/response/blockchain_info.rs b/zaino-fetch/src/jsonrpsee/response/blockchain_info.rs new file mode 100644 index 000000000..a062724e1 --- /dev/null +++ b/zaino-fetch/src/jsonrpsee/response/blockchain_info.rs @@ -0,0 +1,157 @@ +//! Types associated with the `getinfo` RPC request. + +use zebra_chain::block::Height; + +use crate::jsonrpsee::{ + connector::{ResponseToError, RpcError}, + response::common::balance::ChainBalance, +}; + +use std::{convert::Infallible, num::ParseIntError}; + +fn default_header() -> Height { + Height(0) +} + +/// Response to a `getblockchaininfo` RPC request. +/// +/// This is used for the output parameter of [`crate::jsonrpsee::connector::JsonRpSeeConnector::get_blockchain_info`]. +#[derive(Clone, Debug, PartialEq, serde::Deserialize, serde::Serialize)] +pub struct GetBlockchainInfoResponse { + /// Current network name as defined in BIP70 (main, test, regtest) + pub chain: String, + + /// The current number of blocks processed in the server, numeric + pub blocks: zebra_chain::block::Height, + + /// The hash of the currently best block, in big-endian order, hex-encoded + #[serde(rename = "bestblockhash", with = "hex")] + pub best_block_hash: zebra_chain::block::Hash, + + /// If syncing, the estimated height of the chain, else the current best height, numeric. + /// + /// In Zebra, this is always the height estimate, so it might be a little inaccurate. + #[serde(rename = "estimatedheight")] + pub estimated_height: zebra_chain::block::Height, + + /// Chain supply balance + #[serde(default)] + #[serde(rename = "chainSupply")] + pub(super) chain_supply: ChainBalance, + + /// Status of network upgrades + pub upgrades: indexmap::IndexMap< + zebra_rpc::methods::ConsensusBranchIdHex, + zebra_rpc::methods::NetworkUpgradeInfo, + >, + + /// Value pool balances + #[serde(rename = "valuePools")] + value_pools: [ChainBalance; 5], + + /// Branch IDs of the current and upcoming consensus rules + pub consensus: zebra_rpc::methods::TipConsensusBranch, + + /// The current number of headers we have validated in the best chain, that is, + /// the height of the best chain. + #[serde(default = "default_header")] + headers: Height, + + /// The estimated network solution rate in Sol/s. + #[serde(default)] + difficulty: f64, + + /// The verification progress relative to the estimated network chain tip. + #[serde(default)] + #[serde(rename = "verificationprogress")] + verification_progress: f64, + + /// The total amount of work in the best chain, hex-encoded. + #[serde(default)] + #[serde(rename = "chainwork")] + chain_work: ChainWork, + + /// Whether this node is pruned, currently always false in Zebra. + #[serde(default)] + pruned: bool, + + /// The estimated size of the block and undo files on disk + #[serde(default)] + size_on_disk: u64, + + /// The current number of note commitments in the commitment tree + #[serde(default)] + commitments: u64, +} + +impl ResponseToError for GetBlockchainInfoResponse { + type RpcError = Infallible; +} + +impl TryFrom for zebra_rpc::methods::GetBlockchainInfoResponse { + fn try_from(response: GetBlockchainInfoResponse) -> Result { + Ok(zebra_rpc::methods::GetBlockchainInfoResponse::new( + response.chain, + response.blocks, + response.best_block_hash, + response.estimated_height, + response.chain_supply.into_inner(), + response.value_pools.map(|pool| pool.into_inner()), + response.upgrades, + response.consensus, + response.headers, + response.difficulty, + response.verification_progress, + response.chain_work.try_into()?, + response.pruned, + response.size_on_disk, + response.commitments, + )) + } + + type Error = ParseIntError; +} + +/// Error type used for the `chainwork` field of the `getblockchaininfo` RPC request. +#[derive(Debug, thiserror::Error)] +pub enum ChainWorkError {} + +#[derive(Clone, Debug, PartialEq, serde::Deserialize, serde::Serialize)] +#[serde(untagged)] +/// A wrapper type to allow both kinds of ChainWork +pub enum ChainWork { + /// Returned from zcashd, a chainwork is a String representing a + /// base-16 integer + Str(String), + /// Returned from zebrad, a chainwork is an integer + Num(u64), +} + +impl ResponseToError for ChainWork { + type RpcError = ChainWorkError; +} +impl TryFrom for ChainWorkError { + type Error = RpcError; + + fn try_from(value: RpcError) -> Result { + // TODO: attempt to convert RpcError into errors specific to this RPC response + Err(value) + } +} + +impl TryFrom for u64 { + type Error = ParseIntError; + + fn try_from(value: ChainWork) -> Result { + match value { + ChainWork::Str(s) => u64::from_str_radix(&s, 16), + ChainWork::Num(u) => Ok(u), + } + } +} + +impl Default for ChainWork { + fn default() -> Self { + ChainWork::Num(0) + } +} diff --git a/zaino-fetch/src/jsonrpsee/response/common.rs b/zaino-fetch/src/jsonrpsee/response/common.rs index bb198dd82..c5ba0849c 100644 --- a/zaino-fetch/src/jsonrpsee/response/common.rs +++ b/zaino-fetch/src/jsonrpsee/response/common.rs @@ -1,6 +1,7 @@ //! Common types used across jsonrpsee responses pub mod amount; +pub mod balance; use std::time::{Duration, SystemTime, UNIX_EPOCH}; diff --git a/zaino-fetch/src/jsonrpsee/response/common/balance.rs b/zaino-fetch/src/jsonrpsee/response/common/balance.rs new file mode 100644 index 000000000..914234338 --- /dev/null +++ b/zaino-fetch/src/jsonrpsee/response/common/balance.rs @@ -0,0 +1,98 @@ +//! Types used to represent a value pool's balance. + +use std::convert::Infallible; + +use serde::{de, Deserialize, Deserializer, Serialize}; +use zebra_chain::{ + amount::{Amount, NonNegative}, + value_balance::ValueBalance, +}; +use zebra_rpc::client::GetBlockchainInfoBalance; + +use crate::jsonrpsee::connector::ResponseToError; + +/// Wrapper struct for a Zebra [`GetBlockchainInfoBalance`], enabling custom +/// deserialisation logic to handle both zebrad and zcashd. +#[derive(Clone, Debug, PartialEq, Serialize)] +pub struct ChainBalance(GetBlockchainInfoBalance); + +impl ChainBalance { + /// Borrow the wrapped [`GetBlockchainInfoBalance`]. + pub fn as_inner(&self) -> &GetBlockchainInfoBalance { + &self.0 + } + + /// Borrow the wrapped [`GetBlockchainInfoBalance`] mutably. + pub fn as_inner_mut(&mut self) -> &mut GetBlockchainInfoBalance { + &mut self.0 + } + + /// Consume [`self`] and return the wrapped [`GetBlockchainInfoBalance`]. + pub fn into_inner(self) -> GetBlockchainInfoBalance { + self.0 + } +} + +impl ResponseToError for ChainBalance { + type RpcError = Infallible; +} + +impl<'de> Deserialize<'de> for ChainBalance { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + #[derive(Deserialize, Debug)] + struct TempBalance { + #[serde(default)] + id: String, + #[serde(rename = "chainValue")] + chain_value: f64, + #[serde(rename = "chainValueZat")] + chain_value_zat: u64, + #[allow(dead_code)] + #[serde(default)] + monitored: bool, + } + let temp = TempBalance::deserialize(deserializer)?; + let computed_zat = (temp.chain_value * 100_000_000.0).round() as u64; + if computed_zat != temp.chain_value_zat { + return Err(de::Error::custom(format!( + "chainValue and chainValueZat mismatch: computed {} but got {}", + computed_zat, temp.chain_value_zat + ))); + } + let amount = Amount::::from_bytes(temp.chain_value_zat.to_le_bytes()) + .map_err(|e| de::Error::custom(e.to_string()))?; + match temp.id.as_str() { + "transparent" => Ok(ChainBalance(GetBlockchainInfoBalance::transparent( + amount, None, /*TODO: handle optional delta*/ + ))), + "sprout" => Ok(ChainBalance(GetBlockchainInfoBalance::sprout( + amount, None, /*TODO: handle optional delta*/ + ))), + "sapling" => Ok(ChainBalance(GetBlockchainInfoBalance::sapling( + amount, None, /*TODO: handle optional delta*/ + ))), + "orchard" => Ok(ChainBalance(GetBlockchainInfoBalance::orchard( + amount, None, /*TODO: handle optional delta*/ + ))), + // TODO: Investigate source of undocument 'lockbox' value + // that likely is intended to be 'deferred' + "lockbox" | "deferred" => Ok(ChainBalance(GetBlockchainInfoBalance::deferred( + amount, None, + ))), + "" => Ok(ChainBalance(GetBlockchainInfoBalance::chain_supply( + // The pools are immediately summed internally, which pool we pick doesn't matter here + ValueBalance::from_transparent_amount(amount), + ))), + otherwise => todo!("error: invalid chain id deser {otherwise}"), + } + } +} + +impl Default for ChainBalance { + fn default() -> Self { + Self(GetBlockchainInfoBalance::chain_supply(ValueBalance::zero())) + } +} diff --git a/zaino-fetch/src/jsonrpsee/response/info.rs b/zaino-fetch/src/jsonrpsee/response/info.rs new file mode 100644 index 000000000..8ac22cc44 --- /dev/null +++ b/zaino-fetch/src/jsonrpsee/response/info.rs @@ -0,0 +1,87 @@ +//! Types associated with the `getinfo` RPC request. + +use std::convert::Infallible; + +use crate::jsonrpsee::{connector::ResponseToError, response::ErrorsTimestamp}; + +/// Response to a `getinfo` RPC request. +/// +/// This is used for the output parameter of [`crate::jsonrpsee::connector::JsonRpSeeConnector::get_info`]. +#[derive(Clone, Debug, PartialEq, serde::Deserialize, serde::Serialize)] +pub struct GetInfoResponse { + /// The node version + #[serde(default)] + version: u64, + /// The node version build number + pub build: String, + /// The server sub-version identifier, used as the network protocol user-agent + pub subversion: String, + /// The protocol version + #[serde(default)] + #[serde(rename = "protocolversion")] + protocol_version: u32, + + /// The current number of blocks processed in the server + #[serde(default)] + blocks: u32, + + /// The total (inbound and outbound) number of connections the node has + #[serde(default)] + connections: usize, + + /// The proxy (if any) used by the server. Currently always `None` in Zebra. + #[serde(default)] + #[serde(skip_serializing_if = "Option::is_none")] + proxy: Option, + + /// The current network difficulty + #[serde(default)] + difficulty: f64, + + /// True if the server is running in testnet mode, false otherwise + #[serde(default)] + testnet: bool, + + /// The minimum transaction fee in ZEC/kB + #[serde(default)] + #[serde(rename = "paytxfee")] + pay_tx_fee: f64, + + /// The minimum relay fee for non-free transactions in ZEC/kB + #[serde(default)] + #[serde(rename = "relayfee")] + relay_fee: f64, + + /// The last error or warning message, or "no errors" if there are no errors + #[serde(default)] + errors: String, + + /// The time of the last error or warning message, or "no errors timestamp" if there are no errors + #[serde(default)] + #[serde(rename = "errorstimestamp")] + errors_timestamp: ErrorsTimestamp, +} + +impl ResponseToError for GetInfoResponse { + type RpcError = Infallible; +} + +impl From for zebra_rpc::methods::GetInfo { + fn from(response: GetInfoResponse) -> Self { + zebra_rpc::methods::GetInfo::new( + response.version, + response.build, + response.subversion, + response.protocol_version, + response.blocks, + response.connections, + response.proxy, + response.difficulty, + response.testnet, + response.pay_tx_fee, + response.relay_fee, + response.errors, + response.errors_timestamp.to_string(), + ) + } +} diff --git a/zaino-state/Cargo.toml b/zaino-state/Cargo.toml index dec25fac2..f86c3fc7c 100644 --- a/zaino-state/Cargo.toml +++ b/zaino-state/Cargo.toml @@ -59,6 +59,7 @@ arc-swap = { workspace = true } reqwest.workspace = true bitflags = { workspace = true } derive_more = { workspace = true, features = ["from"] } +anyhow.workspace = true [dev-dependencies] diff --git a/zaino-state/src/backends/fetch.rs b/zaino-state/src/backends/fetch.rs index 46ffd1ed9..285fc2c52 100644 --- a/zaino-state/src/backends/fetch.rs +++ b/zaino-state/src/backends/fetch.rs @@ -248,7 +248,7 @@ impl ZcashIndexer for FetchServiceSubscriber { &self, params: GetAddressDeltasParams, ) -> Result { - Ok(self.fetcher.get_address_deltas(params).await?) + Ok(self.fetcher.get_address_deltas(params).await.unwrap()) } /// Returns software information from the RPC server, as a [`GetInfo`] JSON struct. @@ -356,7 +356,8 @@ impl ZcashIndexer for FetchServiceSubscriber { data: None, }) })?) - .await? + .await + .unwrap() .into()) } @@ -382,7 +383,8 @@ impl ZcashIndexer for FetchServiceSubscriber { Ok(self .fetcher .send_raw_transaction(raw_transaction_hex) - .await? + .await + .unwrap() .into()) } @@ -418,7 +420,8 @@ impl ZcashIndexer for FetchServiceSubscriber { Ok(self .fetcher .get_block(hash_or_height, verbosity) - .await? + .await + .unwrap() .try_into()?) } @@ -430,7 +433,7 @@ impl ZcashIndexer for FetchServiceSubscriber { /// /// Note: This method has only been implemented in `zcashd`. Zebra has no intention of supporting it. async fn get_block_deltas(&self, hash: String) -> Result { - Ok(self.fetcher.get_block_deltas(hash).await?) + Ok(self.fetcher.get_block_deltas(hash).await.unwrap()) } async fn get_block_header( @@ -531,7 +534,8 @@ impl ZcashIndexer for FetchServiceSubscriber { Ok(self .fetcher .get_treestate(hash_or_height) - .await? + .await + .unwrap() .try_into()?) } @@ -562,7 +566,8 @@ impl ZcashIndexer for FetchServiceSubscriber { Ok(self .fetcher .get_subtrees_by_index(pool, start_index.0, limit.map(|limit_index| limit_index.0)) - .await? + .await + .unwrap() .into()) } @@ -625,7 +630,8 @@ impl ZcashIndexer for FetchServiceSubscriber { Ok(self .fetcher .get_address_txids(addresses, start, end) - .await? + .await + .unwrap() .transactions) } @@ -656,7 +662,8 @@ impl ZcashIndexer for FetchServiceSubscriber { data: None, }) })?) - .await? + .await + .unwrap() .into_iter() .map(|utxos| utxos.into()) .collect()) @@ -1637,3 +1644,26 @@ impl LightWalletIndexer for FetchServiceSubscriber { ))) } } + +#[allow(deprecated)] +#[cfg(test)] +mod tests { + use zaino_fetch::jsonrpsee::{ + connector::RpcRequestError, response::block_header::GetBlockHeaderError, + }; + + use crate::FetchServiceError; + + #[test] + fn downcast_typed_method_error() { + let block_header_error = GetBlockHeaderError::MissingBlock; + + let fs_err: FetchServiceError = RpcRequestError::Method(block_header_error).into(); + + if let FetchServiceError::RpcMethod(e) = &fs_err { + assert!(e.downcast_ref::().is_some()); + } else { + panic!("expected RpcMethod variant"); + } + } +} diff --git a/zaino-state/src/error.rs b/zaino-state/src/error.rs index 7b999e7d7..04e9ab933 100644 --- a/zaino-state/src/error.rs +++ b/zaino-state/src/error.rs @@ -7,7 +7,7 @@ use crate::BlockHash; use std::{any::type_name, fmt::Display}; -use zaino_fetch::jsonrpsee::connector::RpcRequestError; +use zaino_fetch::jsonrpsee::connector::{RpcError, RpcRequestError}; impl From> for StateServiceError { fn from(value: RpcRequestError) -> Self { @@ -135,37 +135,6 @@ impl From for tonic::Status { } } -impl From> for FetchServiceError { - fn from(value: RpcRequestError) -> Self { - match value { - RpcRequestError::Transport(transport_error) => { - FetchServiceError::JsonRpcConnectorError(transport_error) - } - RpcRequestError::JsonRpc(error) => { - FetchServiceError::Critical(format!("argument failed to serialze: {error}")) - } - RpcRequestError::InternalUnrecoverable(e) => { - FetchServiceError::Critical(format!("Internal unrecoverable error: {e}")) - } - RpcRequestError::ServerWorkQueueFull => FetchServiceError::Critical( - "Server queue full. Handling for this not yet implemented".to_string(), - ), - RpcRequestError::Method(e) => FetchServiceError::Critical(format!( - "unhandled rpc-specific {} error: {}", - type_name::(), - e.to_string() - )), - RpcRequestError::UnexpectedErrorResponse(error) => { - FetchServiceError::Critical(format!( - "unhandled rpc-specific {} error: {}", - type_name::(), - error - )) - } - } - } -} - /// Errors related to the `FetchService`. #[deprecated] #[derive(Debug, thiserror::Error)] @@ -197,6 +166,34 @@ pub enum FetchServiceError { /// Serialization error. #[error("Serialization error: {0}")] SerializationError(#[from] zebra_chain::serialization::SerializationError), + + // Any method error (typed or raw) with backtrace and downcast + #[error(transparent)] + RpcMethod(#[from] anyhow::Error), +} + +impl From> for FetchServiceError +where + E: std::error::Error + Send + Sync + 'static, +{ + fn from(e: RpcRequestError) -> Self { + match e { + RpcRequestError::Method(me) => FetchServiceError::RpcMethod(me.into()), + RpcRequestError::Transport(t) => FetchServiceError::JsonRpcConnectorError(t), + RpcRequestError::JsonRpc(j) => FetchServiceError::RpcMethod(anyhow::anyhow!( + "request input failed to serialize: {j}" + )), + RpcRequestError::ServerWorkQueueFull => FetchServiceError::RpcMethod(anyhow::anyhow!( + "Server queue full. Handling not yet implemented" + )), + RpcRequestError::UnexpectedErrorResponse(u) => { + FetchServiceError::RpcMethod(anyhow::anyhow!("unexpected error response: {u}")) + } + RpcRequestError::InternalUnrecoverable(s) => { + FetchServiceError::RpcMethod(anyhow::anyhow!("Internal unrecoverable error: {s}")) + } + } + } } #[allow(deprecated)] @@ -220,9 +217,13 @@ impl From for tonic::Status { FetchServiceError::SerializationError(err) => { tonic::Status::internal(format!("Serialization error: {err}")) } + FetchServiceError::RpcMethod(err) => { + tonic::Status::internal(format!("RPC error: {err:?}")) + } } } } + /// These aren't the best conversions, but the MempoolError should go away /// in favor of a new type with the new chain cache is complete impl From> for MempoolError {