Changeset View
Changeset View
Standalone View
Standalone View
chronik/chronik-http/src/server.rs
// Copyright (c) 2022 The Bitcoin developers | // Copyright (c) 2022 The Bitcoin developers | ||||
// Distributed under the MIT software license, see the accompanying | // Distributed under the MIT software license, see the accompanying | ||||
// file COPYING or http://www.opensource.org/licenses/mit-license.php. | // file COPYING or http://www.opensource.org/licenses/mit-license.php. | ||||
//! Module for [`ChronikServer`]. | //! Module for [`ChronikServer`]. | ||||
use std::collections::HashMap; | use std::collections::HashMap; | ||||
use std::{net::SocketAddr, sync::Arc}; | use std::{net::SocketAddr, sync::Arc}; | ||||
use abc_rust_error::{Result, WrapErr}; | use abc_rust_error::{Result, WrapErr}; | ||||
use axum::{ | use axum::{ | ||||
extract::{Path, Query, WebSocketUpgrade}, | extract::{Path, Query, WebSocketUpgrade}, | ||||
response::IntoResponse, | response::IntoResponse, | ||||
routing, Extension, Router, | routing, Extension, Router, | ||||
}; | }; | ||||
use bitcoinsuite_core::tx::TxId; | use bitcoinsuite_core::tx::TxId; | ||||
use chronik_indexer::indexer::ChronikIndexer; | use chronik_indexer::indexer::{ChronikIndexer, Node}; | ||||
use chronik_proto::proto; | use chronik_proto::proto; | ||||
use hyper::server::conn::AddrIncoming; | use hyper::server::conn::AddrIncoming; | ||||
use thiserror::Error; | use thiserror::Error; | ||||
use tokio::sync::RwLock; | use tokio::sync::RwLock; | ||||
use crate::{ | use crate::{ | ||||
error::ReportError, handlers, protobuf::Protobuf, | error::ReportError, handlers, protobuf::Protobuf, | ||||
ws::handle_subscribe_socket, | ws::handle_subscribe_socket, | ||||
}; | }; | ||||
/// Ref-counted indexer with read or write access | /// Ref-counted indexer with read or write access | ||||
pub type ChronikIndexerRef = Arc<RwLock<ChronikIndexer>>; | pub type ChronikIndexerRef = Arc<RwLock<ChronikIndexer>>; | ||||
/// Ref-counted access to the bitcoind node | |||||
pub type NodeRef = Arc<Node>; | |||||
/// Params defining what and where to serve for [`ChronikServer`]. | /// Params defining what and where to serve for [`ChronikServer`]. | ||||
#[derive(Clone, Debug)] | #[derive(Clone, Debug)] | ||||
pub struct ChronikServerParams { | pub struct ChronikServerParams { | ||||
/// Host address (port + IP) where to serve Chronik at. | /// Host address (port + IP) where to serve Chronik at. | ||||
pub hosts: Vec<SocketAddr>, | pub hosts: Vec<SocketAddr>, | ||||
/// Indexer to read data from | /// Indexer to read data from | ||||
pub indexer: ChronikIndexerRef, | pub indexer: ChronikIndexerRef, | ||||
/// Access to the bitcoind node | |||||
pub node: NodeRef, | |||||
} | } | ||||
/// Chronik HTTP server, holding all the data/handles required to serve an | /// Chronik HTTP server, holding all the data/handles required to serve an | ||||
/// instance. | /// instance. | ||||
#[derive(Debug)] | #[derive(Debug)] | ||||
pub struct ChronikServer { | pub struct ChronikServer { | ||||
server_builders: Vec<hyper::server::Builder<AddrIncoming>>, | server_builders: Vec<hyper::server::Builder<AddrIncoming>>, | ||||
indexer: ChronikIndexerRef, | indexer: ChronikIndexerRef, | ||||
node: NodeRef, | |||||
} | } | ||||
/// Errors for [`ChronikServer`]. | /// Errors for [`ChronikServer`]. | ||||
#[derive(Debug, Eq, Error, PartialEq)] | #[derive(Debug, Eq, Error, PartialEq)] | ||||
pub enum ChronikServerError { | pub enum ChronikServerError { | ||||
/// Binding to host address failed | /// Binding to host address failed | ||||
#[error("Chronik failed binding to {0}: {1}")] | #[error("Chronik failed binding to {0}: {1}")] | ||||
FailedBindingAddress(SocketAddr, String), | FailedBindingAddress(SocketAddr, String), | ||||
Show All 27 Lines | pub fn setup(params: ChronikServerParams) -> Result<Self> { | ||||
axum::Server::try_bind(&host).map_err(|err| { | axum::Server::try_bind(&host).map_err(|err| { | ||||
FailedBindingAddress(host, err.to_string()).into() | FailedBindingAddress(host, err.to_string()).into() | ||||
}) | }) | ||||
}) | }) | ||||
.collect::<Result<Vec<_>>>()?; | .collect::<Result<Vec<_>>>()?; | ||||
Ok(ChronikServer { | Ok(ChronikServer { | ||||
server_builders, | server_builders, | ||||
indexer: params.indexer, | indexer: params.indexer, | ||||
node: params.node, | |||||
}) | }) | ||||
} | } | ||||
/// Serve a Chronik HTTP endpoint with the given parameters. | /// Serve a Chronik HTTP endpoint with the given parameters. | ||||
pub async fn serve(self) -> Result<()> { | pub async fn serve(self) -> Result<()> { | ||||
let app = Self::make_router(self.indexer); | let app = Self::make_router(self.indexer, self.node); | ||||
let servers = self | let servers = self | ||||
.server_builders | .server_builders | ||||
.into_iter() | .into_iter() | ||||
.zip(std::iter::repeat(app)) | .zip(std::iter::repeat(app)) | ||||
.map(|(server_builder, app)| { | .map(|(server_builder, app)| { | ||||
Box::pin(async move { | Box::pin(async move { | ||||
server_builder | server_builder | ||||
.serve(app.into_make_service()) | .serve(app.into_make_service()) | ||||
.await | .await | ||||
.map_err(|err| ServingFailed(err.to_string())) | .map_err(|err| ServingFailed(err.to_string())) | ||||
}) | }) | ||||
}); | }); | ||||
let (result, _, _) = futures::future::select_all(servers).await; | let (result, _, _) = futures::future::select_all(servers).await; | ||||
result?; | result?; | ||||
Ok(()) | Ok(()) | ||||
} | } | ||||
fn make_router(indexer: ChronikIndexerRef) -> Router { | fn make_router(indexer: ChronikIndexerRef, node: NodeRef) -> Router { | ||||
Router::new() | Router::new() | ||||
.route("/blockchain-info", routing::get(handle_blockchain_info)) | .route("/blockchain-info", routing::get(handle_blockchain_info)) | ||||
.route("/block/:hash_or_height", routing::get(handle_block)) | .route("/block/:hash_or_height", routing::get(handle_block)) | ||||
.route("/block-txs/:hash_or_height", routing::get(handle_block_txs)) | .route("/block-txs/:hash_or_height", routing::get(handle_block_txs)) | ||||
.route("/blocks/:start/:end", routing::get(handle_block_range)) | .route("/blocks/:start/:end", routing::get(handle_block_range)) | ||||
.route("/tx/:txid", routing::get(handle_tx)) | .route("/tx/:txid", routing::get(handle_tx)) | ||||
.route("/raw-tx/:txid", routing::get(handle_raw_tx)) | .route("/raw-tx/:txid", routing::get(handle_raw_tx)) | ||||
.route( | .route( | ||||
Show All 10 Lines | fn make_router(indexer: ChronikIndexerRef, node: NodeRef) -> Router { | ||||
) | ) | ||||
.route( | .route( | ||||
"/script/:type/:payload/utxos", | "/script/:type/:payload/utxos", | ||||
routing::get(handle_script_utxos), | routing::get(handle_script_utxos), | ||||
) | ) | ||||
.route("/ws", routing::get(handle_ws)) | .route("/ws", routing::get(handle_ws)) | ||||
.fallback(handlers::handle_not_found) | .fallback(handlers::handle_not_found) | ||||
.layer(Extension(indexer)) | .layer(Extension(indexer)) | ||||
.layer(Extension(node)) | |||||
} | } | ||||
} | } | ||||
async fn handle_blockchain_info( | async fn handle_blockchain_info( | ||||
Extension(indexer): Extension<ChronikIndexerRef>, | Extension(indexer): Extension<ChronikIndexerRef>, | ||||
) -> Result<Protobuf<proto::BlockchainInfo>, ReportError> { | ) -> Result<Protobuf<proto::BlockchainInfo>, ReportError> { | ||||
let indexer = indexer.read().await; | let indexer = indexer.read().await; | ||||
let blocks = indexer.blocks(); | let blocks = indexer.blocks(); | ||||
▲ Show 20 Lines • Show All 116 Lines • Show Last 20 Lines |