From 2dc61f50d41619ce99e4837451e7937836b2d0ad Mon Sep 17 00:00:00 2001 From: mononaut Date: Thu, 13 Aug 2026 05:22:13 +0000 Subject: [PATCH 1/2] adjust page sizes --- src/config.rs | 2 +- src/rest.rs | 99 ++++++++++++++++++++++++++++++++------------------- 2 files changed, 64 insertions(+), 37 deletions(-) diff --git a/src/config.rs b/src/config.rs index 63a97808..5a8f063c 100644 --- a/src/config.rs +++ b/src/config.rs @@ -258,7 +258,7 @@ impl Config { .arg( Arg::with_name("rest_default_max_address_summary_txs") .long("rest-default-max-address-summary-txs") - .help("The default number of transactions returned by the address summary endpoints.") + .help("The default and maximum number of transactions returned by the address summary endpoints.") .default_value("5000") ) .arg( diff --git a/src/rest.rs b/src/rest.rs index 3f4c84d2..84f20545 100644 --- a/src/rest.rs +++ b/src/rest.rs @@ -32,7 +32,7 @@ use rayon::iter::ParallelIterator; use tokio::sync::oneshot; use hyperlocal::UnixServerExt; -use std::{cmp, fs}; +use std::fs; #[cfg(feature = "liquid")] use { crate::elements::{peg::PegoutValue, AssetMeta, AssetSorting, IssuanceValue}, @@ -70,10 +70,19 @@ const TTL_LONG: u32 = 157_784_630; // ttl for static resources (5 years) const TTL_SHORT: u32 = 10; // ttl for volatie resources const TTL_MEMPOOL_RECENT: u32 = 5; // ttl for GET /mempool/recent const CONF_FINAL: usize = 10; // reorgs deeper than this are considered unlikely +const MAX_HISTORY_TXS: usize = 100; // internal api prefix const INTERNAL_PREFIX: &str = "internal"; +fn capped_max_txs(query_params: &HashMap, default: usize, limit: usize) -> usize { + query_params + .get("max_txs") + .and_then(|value| value.parse::().ok()) + .unwrap_or(default) + .min(limit) +} + #[derive(Serialize, Deserialize)] struct BlockValue { id: String, @@ -968,10 +977,11 @@ fn handle_request( None, ) => { let script_hash = to_scripthash(script_type, script_str, config.network_type)?; - let max_txs = query_params - .get("max_txs") - .and_then(|s| s.parse::().ok()) - .unwrap_or(config.rest_default_max_mempool_txs); + let max_txs = capped_max_txs( + &query_params, + config.rest_default_max_mempool_txs, + MAX_HISTORY_TXS, + ); let after_txid = query_params .get("after_txid") .and_then(|s| s.parse::().ok()); @@ -1070,10 +1080,11 @@ fn handle_request( }) .collect(); - let max_txs = query_params - .get("max_txs") - .and_then(|s| s.parse::().ok()) - .unwrap_or(config.rest_default_max_mempool_txs); + let max_txs = capped_max_txs( + &query_params, + config.rest_default_max_mempool_txs, + MAX_HISTORY_TXS, + ); let after_txid = query_params .get("after_txid") .and_then(|s| s.parse::().ok()); @@ -1158,10 +1169,11 @@ fn handle_request( ) => { let script_hash = to_scripthash(script_type, script_str, config.network_type)?; let last_seen_txid = last_seen_txid.and_then(|txid| txid.parse::().ok()); - let max_txs = query_params - .get("max_txs") - .and_then(|s| s.parse::().ok()) - .unwrap_or(config.rest_default_chain_txs_per_page); + let max_txs = capped_max_txs( + &query_params, + config.rest_default_chain_txs_per_page, + MAX_HISTORY_TXS, + ); let mut txs = query .chain() @@ -1204,12 +1216,10 @@ fn handle_request( ) => { let script_hash = to_scripthash(script_type, script_str, config.network_type)?; let last_seen_txid = last_seen_txid.and_then(|txid| txid.parse::().ok()); - let max_txs = cmp::min( + let max_txs = capped_max_txs( + &query_params, + config.rest_default_max_address_summary_txs, config.rest_default_max_address_summary_txs, - query_params - .get("max_txs") - .and_then(|s| s.parse::().ok()) - .unwrap_or(config.rest_default_max_address_summary_txs), ); let last_seen_txid_location = if let Some(txid) = &last_seen_txid { @@ -1285,12 +1295,10 @@ fn handle_request( .collect(); let last_seen_txid = last_seen_txid.and_then(|txid| txid.parse::().ok()); - let max_txs = cmp::min( + let max_txs = capped_max_txs( + &query_params, + config.rest_default_max_address_summary_txs, config.rest_default_max_address_summary_txs, - query_params - .get("max_txs") - .and_then(|s| s.parse::().ok()) - .unwrap_or(config.rest_default_max_address_summary_txs), ); let last_seen_txid_location = if let Some(txid) = &last_seen_txid { @@ -1336,10 +1344,11 @@ fn handle_request( None, ) => { let script_hash = to_scripthash(script_type, script_str, config.network_type)?; - let max_txs = query_params - .get("max_txs") - .and_then(|s| s.parse::().ok()) - .unwrap_or(config.rest_default_max_mempool_txs); + let max_txs = capped_max_txs( + &query_params, + config.rest_default_max_mempool_txs, + MAX_HISTORY_TXS, + ); let txs = query .mempool() @@ -1729,10 +1738,11 @@ fn handle_request( } (&Method::GET, Some(&"mempool"), Some(&"txids"), Some(&"page"), last_seen_txid, None) => { let last_seen_txid = last_seen_txid.and_then(|txid| txid.parse::().ok()); - let max_txs = query_params - .get("max_txs") - .and_then(|s| s.parse::().ok()) - .unwrap_or(config.rest_max_mempool_txid_page_size); + let max_txs = capped_max_txs( + &query_params, + config.rest_max_mempool_txid_page_size, + config.rest_max_mempool_txid_page_size, + ); json_response( query.mempool().txids_page(max_txs, last_seen_txid), TTL_SHORT, @@ -1787,10 +1797,11 @@ fn handle_request( None, ) => { let last_seen_txid = last_seen_txid.and_then(|txid| txid.parse::().ok()); - let max_txs = query_params - .get("max_txs") - .and_then(|s| s.parse::().ok()) - .unwrap_or(config.rest_max_mempool_page_size); + let max_txs = capped_max_txs( + &query_params, + config.rest_max_mempool_page_size, + config.rest_max_mempool_page_size, + ); let txs = query .mempool() .txs_page(max_txs, last_seen_txid) @@ -2234,12 +2245,28 @@ impl From for HttpError { #[cfg(test)] mod tests { - use super::{confirmed_after_txid, TxidLocation}; + use super::{capped_max_txs, confirmed_after_txid, TxidLocation}; use crate::chain::Txid; use crate::rest::HttpError; use serde_json::Value; use std::collections::HashMap; + #[test] + fn test_capped_max_txs() { + let mut query_params = HashMap::new(); + + assert_eq!(capped_max_txs(&query_params, 25, 100), 25); + + query_params.insert("max_txs".to_string(), "20".to_string()); + assert_eq!(capped_max_txs(&query_params, 25, 100), 20); + + query_params.insert("max_txs".to_string(), "1000000".to_string()); + assert_eq!(capped_max_txs(&query_params, 25, 100), 100); + + query_params.insert("max_txs".to_string(), "invalid".to_string()); + assert_eq!(capped_max_txs(&query_params, 25, 100), 25); + } + #[test] fn test_parse_query_param() { let mut query_params = HashMap::new(); From 129bd2a9e3446bc12a3c1645d23b908c816deaaa Mon Sep 17 00:00:00 2001 From: mononaut Date: Wed, 14 Oct 2026 06:34:45 +0000 Subject: [PATCH 2/2] align max page sizes --- src/config.rs | 8 +++--- src/rest.rs | 69 +++++++++++++++------------------------------------ 2 files changed, 24 insertions(+), 53 deletions(-) diff --git a/src/config.rs b/src/config.rs index 5a8f063c..47a4aee9 100644 --- a/src/config.rs +++ b/src/config.rs @@ -246,13 +246,13 @@ impl Config { .arg( Arg::with_name("rest_default_chain_txs_per_page") .long("rest-default-chain-txs-per-page") - .help("The default number of on-chain transactions returned by the txs endpoints.") + .help("The default and maximum number of on-chain transactions returned by the txs endpoints.") .default_value("25") ) .arg( Arg::with_name("rest_default_max_mempool_txs") .long("rest-default-max-mempool-txs") - .help("The default number of mempool transactions returned by the txs endpoints.") + .help("The default and maximum number of mempool transactions returned by the txs endpoints.") .default_value("50") ) .arg( @@ -264,13 +264,13 @@ impl Config { .arg( Arg::with_name("rest_max_mempool_page_size") .long("rest-max-mempool-page-size") - .help("The maximum number of transactions returned by the paginated /internal/mempool/txs endpoint.") + .help("The default and maximum number of transactions returned by the paginated /internal/mempool/txs endpoint.") .default_value("1000") ) .arg( Arg::with_name("rest_max_mempool_txid_page_size") .long("rest-max-mempool-txid-page-size") - .help("The maximum number of transactions returned by the paginated /mempool/txids/page endpoint.") + .help("The default and maximum number of transactions returned by the paginated /mempool/txids/page endpoint.") .default_value("10000") ) .arg( diff --git a/src/rest.rs b/src/rest.rs index 84f20545..b8b90479 100644 --- a/src/rest.rs +++ b/src/rest.rs @@ -70,17 +70,15 @@ const TTL_LONG: u32 = 157_784_630; // ttl for static resources (5 years) const TTL_SHORT: u32 = 10; // ttl for volatie resources const TTL_MEMPOOL_RECENT: u32 = 5; // ttl for GET /mempool/recent const CONF_FINAL: usize = 10; // reorgs deeper than this are considered unlikely -const MAX_HISTORY_TXS: usize = 100; // internal api prefix const INTERNAL_PREFIX: &str = "internal"; -fn capped_max_txs(query_params: &HashMap, default: usize, limit: usize) -> usize { +fn capped_max_txs(query_params: &HashMap, page_size: usize) -> usize { query_params .get("max_txs") .and_then(|value| value.parse::().ok()) - .unwrap_or(default) - .min(limit) + .map_or(page_size, |max_txs| max_txs.min(page_size)) } #[derive(Serialize, Deserialize)] @@ -977,11 +975,7 @@ fn handle_request( None, ) => { let script_hash = to_scripthash(script_type, script_str, config.network_type)?; - let max_txs = capped_max_txs( - &query_params, - config.rest_default_max_mempool_txs, - MAX_HISTORY_TXS, - ); + let max_txs = capped_max_txs(&query_params, config.rest_default_max_mempool_txs); let after_txid = query_params .get("after_txid") .and_then(|s| s.parse::().ok()); @@ -1080,11 +1074,7 @@ fn handle_request( }) .collect(); - let max_txs = capped_max_txs( - &query_params, - config.rest_default_max_mempool_txs, - MAX_HISTORY_TXS, - ); + let max_txs = capped_max_txs(&query_params, config.rest_default_max_mempool_txs); let after_txid = query_params .get("after_txid") .and_then(|s| s.parse::().ok()); @@ -1169,11 +1159,7 @@ fn handle_request( ) => { let script_hash = to_scripthash(script_type, script_str, config.network_type)?; let last_seen_txid = last_seen_txid.and_then(|txid| txid.parse::().ok()); - let max_txs = capped_max_txs( - &query_params, - config.rest_default_chain_txs_per_page, - MAX_HISTORY_TXS, - ); + let max_txs = capped_max_txs(&query_params, config.rest_default_chain_txs_per_page); let mut txs = query .chain() @@ -1216,11 +1202,8 @@ fn handle_request( ) => { let script_hash = to_scripthash(script_type, script_str, config.network_type)?; let last_seen_txid = last_seen_txid.and_then(|txid| txid.parse::().ok()); - let max_txs = capped_max_txs( - &query_params, - config.rest_default_max_address_summary_txs, - config.rest_default_max_address_summary_txs, - ); + let max_txs = + capped_max_txs(&query_params, config.rest_default_max_address_summary_txs); let last_seen_txid_location = if let Some(txid) = &last_seen_txid { find_txid(txid, &query.mempool(), query.chain()) @@ -1295,11 +1278,8 @@ fn handle_request( .collect(); let last_seen_txid = last_seen_txid.and_then(|txid| txid.parse::().ok()); - let max_txs = capped_max_txs( - &query_params, - config.rest_default_max_address_summary_txs, - config.rest_default_max_address_summary_txs, - ); + let max_txs = + capped_max_txs(&query_params, config.rest_default_max_address_summary_txs); let last_seen_txid_location = if let Some(txid) = &last_seen_txid { find_txid(txid, &query.mempool(), query.chain()) @@ -1344,11 +1324,7 @@ fn handle_request( None, ) => { let script_hash = to_scripthash(script_type, script_str, config.network_type)?; - let max_txs = capped_max_txs( - &query_params, - config.rest_default_max_mempool_txs, - MAX_HISTORY_TXS, - ); + let max_txs = capped_max_txs(&query_params, config.rest_default_max_mempool_txs); let txs = query .mempool() @@ -1738,11 +1714,7 @@ fn handle_request( } (&Method::GET, Some(&"mempool"), Some(&"txids"), Some(&"page"), last_seen_txid, None) => { let last_seen_txid = last_seen_txid.and_then(|txid| txid.parse::().ok()); - let max_txs = capped_max_txs( - &query_params, - config.rest_max_mempool_txid_page_size, - config.rest_max_mempool_txid_page_size, - ); + let max_txs = capped_max_txs(&query_params, config.rest_max_mempool_txid_page_size); json_response( query.mempool().txids_page(max_txs, last_seen_txid), TTL_SHORT, @@ -1797,11 +1769,7 @@ fn handle_request( None, ) => { let last_seen_txid = last_seen_txid.and_then(|txid| txid.parse::().ok()); - let max_txs = capped_max_txs( - &query_params, - config.rest_max_mempool_page_size, - config.rest_max_mempool_page_size, - ); + let max_txs = capped_max_txs(&query_params, config.rest_max_mempool_page_size); let txs = query .mempool() .txs_page(max_txs, last_seen_txid) @@ -2255,16 +2223,19 @@ mod tests { fn test_capped_max_txs() { let mut query_params = HashMap::new(); - assert_eq!(capped_max_txs(&query_params, 25, 100), 25); + assert_eq!(capped_max_txs(&query_params, 25), 25); query_params.insert("max_txs".to_string(), "20".to_string()); - assert_eq!(capped_max_txs(&query_params, 25, 100), 20); + assert_eq!(capped_max_txs(&query_params, 25), 20); + + query_params.insert("max_txs".to_string(), "26".to_string()); + assert_eq!(capped_max_txs(&query_params, 25), 25); - query_params.insert("max_txs".to_string(), "1000000".to_string()); - assert_eq!(capped_max_txs(&query_params, 25, 100), 100); + query_params.insert("max_txs".to_string(), usize::MAX.to_string()); + assert_eq!(capped_max_txs(&query_params, 25), 25); query_params.insert("max_txs".to_string(), "invalid".to_string()); - assert_eq!(capped_max_txs(&query_params, 25, 100), 25); + assert_eq!(capped_max_txs(&query_params, 25), 25); } #[test]