diff --git a/chronik/chronik-http/src/handlers.rs b/chronik/chronik-http/src/handlers.rs index be4ea3e97..4d25b99b6 100644 --- a/chronik/chronik-http/src/handlers.rs +++ b/chronik/chronik-http/src/handlers.rs @@ -1,153 +1,115 @@ //! Module for Chronik handlers. use std::{collections::HashMap, fmt::Display, str::FromStr}; use abc_rust_error::{Report, Result}; -use bitcoinsuite_core::{ - error::DataError, - script::{ScriptType, ScriptTypeError, ScriptVariant}, -}; use chronik_indexer::indexer::ChronikIndexer; use chronik_proto::proto; use hyper::Uri; use thiserror::Error; -use crate::error::ReportError; +use crate::{error::ReportError, parse::parse_script_variant_hex}; /// Errors for HTTP handlers. #[derive(Debug, Error, PartialEq)] pub enum ChronikHandlerError { /// Not found #[error("404: Not found: {0}")] RouteNotFound(Uri), - /// Invalid hex - #[error("400: Invalid hex: {0}")] - InvalidHex(hex::FromHexError), - /// Query parameter has an invalid value #[error("400: Invalid param {param_name}: {param_value}, {msg}")] InvalidParam { /// Name of the param param_name: String, /// Value of the param param_value: String, /// Human-readable error message. msg: String, }, - - /// script_type is invalid - #[error("400: {0}")] - InvalidScriptType(ScriptTypeError), - - /// Script payload invalid for script_type - #[error("400: Invalid payload for {0:?}: {1}")] - InvalidScriptPayload(ScriptType, DataError), } use self::ChronikHandlerError::*; -fn parse_hex(payload: &str) -> Result<Vec<u8>, ChronikHandlerError> { - hex::decode(payload).map_err(ChronikHandlerError::InvalidHex) -} - -fn parse_script_variant( - script_type: &str, - payload: &str, -) -> Result<ScriptVariant> { - let script_type = script_type - .parse::<ScriptType>() - .map_err(InvalidScriptType)?; - Ok( - ScriptVariant::from_type_and_payload(script_type, &parse_hex(payload)?) - .map_err(|err| InvalidScriptPayload(script_type, err))?, - ) -} - fn get_param<T: FromStr>( params: &HashMap<String, String>, param_name: &str, ) -> Result<Option<T>> where T::Err: Display, { let Some(param) = params.get(param_name) else { return Ok(None) }; Ok(Some(param.parse::<T>().map_err(|err| InvalidParam { param_name: param_name.to_string(), param_value: param.to_string(), msg: err.to_string(), })?)) } /// Fallback route that returns a 404 response pub async fn handle_not_found(uri: Uri) -> Result<(), ReportError> { Err(Report::from(RouteNotFound(uri)).into()) } /// Return a page of the confirmed txs of the given script. /// Scripts are identified by script_type and payload. pub async fn handle_script_confirmed_txs( script_type: &str, payload: &str, query_params: &HashMap<String, String>, indexer: &ChronikIndexer, ) -> Result<proto::TxHistoryPage> { - let script_variant = parse_script_variant(script_type, payload)?; + let script_variant = parse_script_variant_hex(script_type, payload)?; let script_history = indexer.script_history()?; let page_num: u32 = get_param(query_params, "page")?.unwrap_or(0); let page_size: u32 = get_param(query_params, "page_size")?.unwrap_or(25); let script = script_variant.to_script(); script_history.confirmed_txs(&script, page_num as usize, page_size as usize) } /// Return a page of the tx history of the given script, in reverse /// chronological order, i.e. the latest transaction first and then going back /// in time. Scripts are identified by script_type and payload. pub async fn handle_script_history( script_type: &str, payload: &str, query_params: &HashMap<String, String>, indexer: &ChronikIndexer, ) -> Result<proto::TxHistoryPage> { - let script_type = script_type - .parse::<ScriptType>() - .map_err(InvalidScriptType)?; - let script_variant = - ScriptVariant::from_type_and_payload(script_type, &parse_hex(payload)?) - .map_err(|err| InvalidScriptPayload(script_type, err))?; + let script_variant = parse_script_variant_hex(script_type, payload)?; let script_history = indexer.script_history()?; let page_num: u32 = get_param(query_params, "page")?.unwrap_or(0); let page_size: u32 = get_param(query_params, "page_size")?.unwrap_or(25); let script = script_variant.to_script(); script_history.rev_history(&script, page_num as usize, page_size as usize) } /// Return a page of the confirmed txs of the given script. /// Scripts are identified by script_type and payload. pub async fn handle_script_unconfirmed_txs( script_type: &str, payload: &str, indexer: &ChronikIndexer, ) -> Result<proto::TxHistoryPage> { - let script_variant = parse_script_variant(script_type, payload)?; + let script_variant = parse_script_variant_hex(script_type, payload)?; let script_history = indexer.script_history()?; let script = script_variant.to_script(); script_history.unconfirmed_txs(&script) } /// Return the UTXOs of the given script. /// Scripts are identified by script_type and payload. pub async fn handle_script_utxos( script_type: &str, payload: &str, indexer: &ChronikIndexer, ) -> Result<proto::ScriptUtxos> { - let script_variant = parse_script_variant(script_type, payload)?; + let script_variant = parse_script_variant_hex(script_type, payload)?; let script_utxos = indexer.script_utxos()?; let script = script_variant.to_script(); let utxos = script_utxos.utxos(&script)?; Ok(proto::ScriptUtxos { script: script.bytecode().to_vec(), utxos, }) } diff --git a/chronik/chronik-http/src/lib.rs b/chronik/chronik-http/src/lib.rs index dfb8222f6..999a0a14a 100644 --- a/chronik/chronik-http/src/lib.rs +++ b/chronik/chronik-http/src/lib.rs @@ -1,14 +1,15 @@ // Copyright (c) 2022 The Bitcoin developers // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. //! Crate for the HTTP endpoint for Chronik. abc_rust_lint::lint! { pub mod error; pub mod handlers; + pub mod parse; pub mod protobuf; pub mod server; pub(crate) mod validation; pub mod ws; } diff --git a/chronik/chronik-http/src/parse.rs b/chronik/chronik-http/src/parse.rs new file mode 100644 index 000000000..433c70c87 --- /dev/null +++ b/chronik/chronik-http/src/parse.rs @@ -0,0 +1,55 @@ +// Copyright (c) 2023 The Bitcoin developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +//! Module for parsing data coming from clients. + +use abc_rust_error::Result; +use bitcoinsuite_core::{ + error::DataError, + script::{ScriptType, ScriptTypeError, ScriptVariant}, +}; +use thiserror::Error; + +/// Errors indicating parsing failed. +#[derive(Debug, Error, PartialEq)] +pub enum ChronikParseError { + /// Invalid hex + #[error("400: Invalid hex: {0}")] + InvalidHex(hex::FromHexError), + + /// script_type is invalid + #[error("400: {0}")] + InvalidScriptType(ScriptTypeError), + + /// Script payload invalid for script_type + #[error("400: Invalid payload for {0:?}: {1}")] + InvalidScriptPayload(ScriptType, DataError), +} + +use self::ChronikParseError::*; + +/// Parse the data as hex. +pub fn parse_hex(payload: &str) -> Result<Vec<u8>> { + Ok(hex::decode(payload).map_err(InvalidHex)?) +} + +/// Parse the [`ScriptVariant`] with a hex payload (e.g. from URL). +pub fn parse_script_variant_hex( + script_type: &str, + payload_hex: &str, +) -> Result<ScriptVariant> { + parse_script_variant(script_type, &parse_hex(payload_hex)?) +} + +/// Parse the [`ScriptVariant`] with a byte payload (e.g. from protobuf). +pub fn parse_script_variant( + script_type: &str, + payload: &[u8], +) -> Result<ScriptVariant> { + let script_type = script_type + .parse::<ScriptType>() + .map_err(InvalidScriptType)?; + Ok(ScriptVariant::from_type_and_payload(script_type, payload) + .map_err(|err| InvalidScriptPayload(script_type, err))?) +}