From 4c1f084415058bc28ee25e7404e38c2e00383176 Mon Sep 17 00:00:00 2001 From: junderw Date: Mon, 29 Sep 2025 21:44:23 +0900 Subject: [PATCH] Feat: Add X-Bitcoin-Version header to all responses from REST API. Include bitcoind subversion string --- src/config.rs | 4 +++- src/daemon.rs | 4 ++++ src/rest.rs | 22 ++++++++++++---------- 3 files changed, 19 insertions(+), 11 deletions(-) diff --git a/src/config.rs b/src/config.rs index a5e903c..813c641 100644 --- a/src/config.rs +++ b/src/config.rs @@ -4,7 +4,7 @@ use std::fs; use std::net::SocketAddr; use std::net::ToSocketAddrs; use std::path::{Path, PathBuf}; -use std::sync::Arc; +use std::sync::{Arc, OnceLock}; use stderrlog; use crate::chain::Network; @@ -17,6 +17,8 @@ use bitcoin::Network as BNetwork; pub(crate) const APP_NAME: &str = "mempool-electrs"; pub(crate) const ELECTRS_VERSION: &str = env!("CARGO_PKG_VERSION"); pub(crate) const GIT_HASH: Option<&str> = option_env!("GIT_HASH"); +// This will be set only once in the Daemon::new() constructor at startup +pub(crate) static BITCOIND_SUBVER: OnceLock = OnceLock::new(); lazy_static! { pub(crate) static ref VERSION_STRING: String = { diff --git a/src/daemon.rs b/src/daemon.rs index 4c398f3..912510d 100644 --- a/src/daemon.rs +++ b/src/daemon.rs @@ -18,6 +18,7 @@ use bitcoin::consensus::encode::{deserialize, serialize}; use elements::encode::{deserialize, serialize}; use crate::chain::{Block, BlockHash, BlockHeader, Network, Transaction, Txid}; +use crate::config::BITCOIND_SUBVER; use crate::metrics::{HistogramOpts, HistogramVec, Metrics}; use crate::signal::Waiter; use crate::util::HeaderList; @@ -363,6 +364,9 @@ impl Daemon { network_info.subversion, ) } + // Insert the subversion (/Satoshi xx.xx.xx(comment)/) string from bitcoind + _ = BITCOIND_SUBVER.set(network_info.subversion); + let blockchain_info = daemon.getblockchaininfo()?; info!("{:?}", blockchain_info); if blockchain_info.pruned { diff --git a/src/rest.rs b/src/rest.rs index 70726a9..6763235 100644 --- a/src/rest.rs +++ b/src/rest.rs @@ -1,5 +1,5 @@ use crate::chain::{address, BlockHash, Network, OutPoint, Script, Transaction, TxIn, TxOut, Txid}; -use crate::config::{Config, VERSION_STRING}; +use crate::config::{Config, BITCOIND_SUBVER, VERSION_STRING}; use crate::errors; use crate::metrics::Metrics; use crate::new_index::{compute_script_hash, Query, SpendingInput, Utxo}; @@ -16,7 +16,10 @@ use bitcoin::blockdata::opcodes; use bitcoin::hashes::hex::{FromHex, ToHex}; use bitcoin::hashes::Error as HashError; use hex::{self, FromHexError}; -use hyper::service::{make_service_fn, service_fn}; +use hyper::{ + header::HeaderValue, + service::{make_service_fn, service_fn}, +}; use hyper::{Body, Method, Response, Server, StatusCode}; use prometheus::{HistogramOpts, HistogramVec}; use rayon::iter::ParallelIterator; @@ -625,14 +628,19 @@ async fn run_server( Response::builder() .status(err.0) .header("Content-Type", "text/plain") - .header("X-Powered-By", &**VERSION_STRING) .body(Body::from(err.1)) .unwrap() }); + resp.headers_mut() + .insert("X-Powered-By", HeaderValue::from_static(&VERSION_STRING)); if let Some(ref origins) = config.cors { resp.headers_mut() .insert("Access-Control-Allow-Origin", origins.parse().unwrap()); } + if let Some(subver) = BITCOIND_SUBVER.get() { + resp.headers_mut() + .insert("X-Bitcoin-Version", HeaderValue::from_static(subver)); + } timer.observe_duration(); Ok::<_, hyper::Error>(resp) } @@ -815,7 +823,6 @@ fn handle_request( .status(StatusCode::OK) .header("Content-Type", "application/octet-stream") .header("Cache-Control", format!("public, max-age={:}", TTL_LONG)) - .header("X-Powered-By", &**VERSION_STRING) .body(Body::from(raw)) .unwrap()) } @@ -1385,7 +1392,6 @@ fn handle_request( .status(StatusCode::OK) .header("Content-Type", content_type) .header("Cache-Control", format!("public, max-age={:}", ttl)) - .header("X-Powered-By", &**VERSION_STRING) .body(body) .unwrap()) } @@ -1771,7 +1777,6 @@ fn handle_request( // Disable caching because we don't currently support caching with query string params .header("Cache-Control", "no-store") .header("Content-Type", "application/json") - .header("X-Powered-By", &**VERSION_STRING) .header("X-Total-Results", total_num.to_string()) .body(Body::from(serde_json::to_string(&assets)?)) .unwrap()) @@ -1915,7 +1920,6 @@ where .status(status) .header("Content-Type", "text/plain") .header("Cache-Control", format!("public, max-age={:}", ttl)) - .header("X-Powered-By", &**VERSION_STRING) .body(message.into()) .unwrap()) } @@ -1925,7 +1929,6 @@ fn json_response(value: T, ttl: u32) -> Result, Htt Ok(Response::builder() .header("Content-Type", "application/json") .header("Cache-Control", format!("public, max-age={:}", ttl)) - .header("X-Powered-By", &**VERSION_STRING) .body(Body::from(value)) .unwrap()) } @@ -1936,8 +1939,7 @@ fn json_response(value: T, ttl: u32) -> Result, Htt // ) -> Result, HttpError> { // let response = Response::builder() // .header("Content-Type", "application/json") -// .header("Cache-Control", format!("public, max-age={:}", ttl)) -// .header("X-Powered-By", &**VERSION_STRING); +// .header("Cache-Control", format!("public, max-age={:}", ttl)); // Ok(match value { // Ok(v) => response // .body(Body::from(serde_json::to_string(&v)?))