diff --git a/chronik/chronik-http/src/electrum.rs b/chronik/chronik-http/src/electrum.rs --- a/chronik/chronik-http/src/electrum.rs +++ b/chronik/chronik-http/src/electrum.rs @@ -4,9 +4,12 @@ //! Module for [`ChronikElectrumServer`]. +use std::str::FromStr; use std::{net::SocketAddr, sync::Arc}; use abc_rust_error::Result; +use bitcoinsuite_core::tx::TxId; +use chronik_util::log; use futures::future; use karyon_jsonrpc::{RPCError, RPCMethod, RPCService, Server}; use karyon_net::{Addr, Endpoint}; @@ -34,8 +37,8 @@ #[derive(Debug)] pub struct ChronikElectrumServer { hosts: Vec<SocketAddr>, - _indexer: ChronikIndexerRef, - _node: NodeRef, + indexer: ChronikIndexerRef, + node: NodeRef, } /// Errors for [`ChronikElectrumServer`]. @@ -52,14 +55,18 @@ struct ChronikElectrumRPCServerEndpoint {} +struct ChronikElectrumRPCBlockchainEndpoint { + indexer: ChronikIndexerRef, + node: NodeRef, +} + impl ChronikElectrumServer { /// Binds the Chronik server on the given hosts pub fn setup(params: ChronikElectrumServerParams) -> Result<Self> { Ok(ChronikElectrumServer { hosts: params.hosts, - // FIXME below params are unused but will be used in the future - _indexer: params.indexer, - _node: params.node, + indexer: params.indexer, + node: params.node, }) } @@ -68,12 +75,18 @@ // The behavior is to bind the endpoint name to its method name like so: // endpoint.method as the name of the RPC let server_endpoint = Arc::new(ChronikElectrumRPCServerEndpoint {}); + let blockchain_endpoint = + Arc::new(ChronikElectrumRPCBlockchainEndpoint { + indexer: self.indexer, + node: self.node, + }); + let endpoints = (server_endpoint, blockchain_endpoint); let servers = self .hosts .into_iter() - .zip(std::iter::repeat(server_endpoint)) - .map(|(host, server_endpoint)| { + .zip(std::iter::repeat(endpoints)) + .map(|(host, endpoints)| { Box::pin(async move { // Don't use the karyon Endpoint parsing as it doesn't // appear to support IPv6. @@ -88,7 +101,8 @@ FailedBindingAddress(host, err.to_string()) })?; let server = builder - .service(server_endpoint) + .service(endpoints.0) + .service(endpoints.1) .build() .await .map_err(|err| ServingFailed(err.to_string()))?; @@ -127,3 +141,56 @@ Ok(Value::Null) } } + +impl RPCService for ChronikElectrumRPCBlockchainEndpoint { + fn name(&self) -> String { + "blockchain".to_string() + } + + fn get_method(&self, name: &str) -> Option<RPCMethod<'_>> { + match name { + "transaction.get" => Some(Box::new(move |params: Value| { + Box::pin(self.transaction_get(params)) + })), + _ => None, + } + } +} + +impl ChronikElectrumRPCBlockchainEndpoint { + async fn transaction_get(&self, params: Value) -> Result<Value, RPCError> { + let txid = match params { + Value::Array(arr) => arr + .get(0) + .ok_or(RPCError::InvalidParams("empty positional parameters")) + .cloned(), + Value::Object(obj) => match obj.get("txid") { + Some(txid) => Ok(txid.clone()), + None => Err(RPCError::InvalidParams( + "missing txid in named parameters", + )), + }, + _ => Err(RPCError::InvalidParams( + "params must be an array or an object", + )), + }?; + let txid = match txid { + Value::String(s) => Ok(s), + _ => Err(RPCError::InvalidParams( + "txid must be a hexadecimal string", + )), + }?; + let txid = TxId::from_str(&*txid) + .or(Err(RPCError::InvalidParams("failed to parse txid")))?; + + log!("{txid}\n"); + let indexer = self.indexer.read().await; + let raw_tx = indexer + .txs(&self.node) + .raw_tx_by_id(&txid) + .or(Err(RPCError::InvalidRequest("Unknown txid")))?; + + log!("{raw_tx:?}\n"); + Ok(Value::Null) + } +}