Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions changelog.d/sh-cap-message.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
Fixed

- **The `--max-sh-creates` refusal names the real numbers.** It read
`(default 10000)` whatever the configured cap was. It now reports the
scripthash's create count and the cap in force, for example
`scripthash join exceeds --max-sh-creates (14437 creates, cap 10000)`.
Esplora still answers 503.
2 changes: 1 addition & 1 deletion crates/rbitcoin-esplora/src/esplora_sh_journey.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ async fn cap_refuses_three_creates(q: &Query, app: &Router) {
let (st, body) = oneshot_http(app, get_with_client(&format!("/scripthash/{h51}"), "cap")).await;
assert_eq!(st, 503, "{body}");
assert!(
body.contains("scripthash join exceeds --max-sh-creates"),
body.contains("scripthash join exceeds --max-sh-creates (3 creates, cap 2)"),
"{body}"
);
let (st, utxo) =
Expand Down
3 changes: 3 additions & 0 deletions crates/rbitcoin-esplora/src/server.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1130,6 +1130,9 @@ pub(crate) fn store_err(e: rbitcoin_query::QueryError) -> Response {
StoreError::Pruned { .. } => (StatusCode::NOT_FOUND, "pruned").into_response(),
StoreError::Stale(m) => (StatusCode::SERVICE_UNAVAILABLE, m).into_response(),
StoreError::Rejected(m) => (StatusCode::SERVICE_UNAVAILABLE, m).into_response(),
e @ StoreError::ShCreatesCap { .. } => {
(StatusCode::SERVICE_UNAVAILABLE, e.to_string()).into_response()
}
other => (StatusCode::INTERNAL_SERVER_ERROR, other.to_string()).into_response(),
}
}
Expand Down
3 changes: 0 additions & 3 deletions crates/rbitcoin-query/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1306,9 +1306,6 @@ impl Query {
self.reconstruct_archived.swap(0, AtomicOrdering::Relaxed)
}

pub const MAX_SH_CREATES_MSG: &'static str =
"scripthash join exceeds --max-sh-creates (default 10000)";

/// [`StoreError::Rejected`] payload from [`Self::block_queue_offer`] for
/// wire that does not decode. Only this refusal is the sender's fault.
pub const UNDECODABLE_WIRE_MSG: &'static str = "block wire does not decode";
Expand Down
4 changes: 2 additions & 2 deletions crates/rbitcoin-query/src/query_sh_caps_journey.rs
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@ fn sh_history_caps() {
q.scripthash_history(&sh).map(|_| ()).unwrap_err(),
] {
assert!(
matches!(err, StoreError::Rejected(m) if m == Query::MAX_SH_CREATES_MSG),
err.to_string() == "scripthash join exceeds --max-sh-creates (7 creates, cap 2)",
"{err}"
);
}
Expand Down Expand Up @@ -157,7 +157,7 @@ fn sh_history_caps() {
)
.unwrap_err();
assert!(
matches!(err, StoreError::Rejected(m) if m == Query::MAX_SH_CREATES_MSG),
err.to_string() == "scripthash join exceeds --max-sh-creates (7 creates, cap 2)",
"{err}"
);

Expand Down
2 changes: 1 addition & 1 deletion crates/rbitcoin-query/src/scripthash.rs
Original file line number Diff line number Diff line change
Expand Up @@ -602,7 +602,7 @@ impl Query {
if cap > 0 && !paging {
let n = self.scripthash_create_count(scripthash)?;
if n > cap {
return Err(StoreError::Rejected(Query::MAX_SH_CREATES_MSG));
return Err(StoreError::ShCreatesCap { creates: n, cap });
}
}
let t_pages = std::time::Instant::now();
Expand Down
22 changes: 18 additions & 4 deletions crates/rbitcoin-store/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,11 @@ pub enum StoreError {
Stale(&'static str),
/// Request refused (DoS cap) — not on-disk corruption.
Rejected(&'static str),
/// Unpaged scripthash join over `--max-sh-creates` (DoS cap) — not on-disk corruption.
ShCreatesCap {
creates: u32,
cap: u32,
},
/// Class A seqsigwit dropped at/below the prune watermark. Not corruption.
Pruned {
height: u32,
Expand Down Expand Up @@ -95,6 +100,10 @@ impl fmt::Display for StoreError {
StoreError::Layout(m) => write!(f, "{m}"),
StoreError::Stale(m) => write!(f, "{m}"),
StoreError::Rejected(m) => f.write_str(m),
StoreError::ShCreatesCap { creates, cap } => write!(
f,
"scripthash join exceeds --max-sh-creates ({creates} creates, cap {cap})"
),
StoreError::Pruned { height } => write!(f, "pruned data at height {height}"),
}
}
Expand Down Expand Up @@ -144,8 +153,12 @@ mod tests {
StoreError::Unavailable,
StoreError::Layout("seqsigwit is on a cold datadir".into()),
StoreError::Stale("chain view moved"),
StoreError::Rejected("scripthash join exceeds --max-sh-creates (default 10000)"),
StoreError::Rejected("block wire does not decode"),
StoreError::Pruned { height: 12 },
StoreError::ShCreatesCap {
creates: 14437,
cap: 10000,
},
];
let texts: Vec<String> = arms.iter().map(|e| e.to_string()).collect();
assert_eq!(texts[0], "invalid store magic");
Expand All @@ -161,11 +174,12 @@ mod tests {
assert_eq!(texts[9], "io_uring unavailable");
assert_eq!(texts[10], "seqsigwit is on a cold datadir");
assert_eq!(texts[11], "chain view moved");
assert_eq!(texts[12], "block wire does not decode");
assert_eq!(texts[13], "pruned data at height 12");
assert_eq!(
texts[12],
"scripthash join exceeds --max-sh-creates (default 10000)"
texts[14],
"scripthash join exceeds --max-sh-creates (14437 creates, cap 10000)"
);
assert_eq!(texts[13], "pruned data at height 12");
for e in &arms {
assert!(e.source().is_none());
}
Expand Down
2 changes: 1 addition & 1 deletion docs/operator/storage.md
Original file line number Diff line number Diff line change
Expand Up @@ -151,7 +151,7 @@ Electrum/Esplora **start without** `--sh-index`. Address/scripthash methods then
`--max-sh-creates N` (conf `max_sh_creates=`) defaults to **10000**. An unpaged
Electrum history or full stats join with more than N creates is refused
before Class A expand: Esplora **503** / Electrum JSON-RPC error
`scripthash join exceeds --max-sh-creates (default 10000)`. **0** is unlimited.
`scripthash join exceeds --max-sh-creates (<creates> creates, cap <N>)`. **0** is unlimited.
A history request that names a page (Esplora's 25, or any caller that sets a
limit) is still served: the join stops once that page is full.

Expand Down
Loading