Repository navigation
esplora: one request limit for the whole listener, and --esplora-max-inflight #1180
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
15621dd
0e55e12
514d80e
33c8928
257f253
9a88526
b4f48b3
7a86398
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,18 @@ | ||
| Fixed | ||
|
|
||
| - **The Esplora request limit bounds the listener, not each route.** | ||
| axum's `Router::layer` wraps every route on its own, so | ||
| `ConcurrencyLimitLayer` gave each of the listener's routes its own 256 | ||
| permits: in practice no limit. The limit is now one semaphore shared by | ||
| every route and the fallback. Requests over it wait for a slot; they are | ||
| not refused. The request timeout now covers that wait too, so a queue of | ||
| slow requests ahead cannot hold a later one past one timeout (**408**). | ||
| The `/blindbit/v1/` routes share this limit too. A BlindBit request waits | ||
| for its own permit first, so one queued there holds no listener slot. | ||
|
|
||
| Added | ||
|
|
||
| - **`--esplora-max-inflight N`** (conf `esplora_max_inflight=`, NixOS | ||
| `services.rbitcoin.esplora.maxInflight`) sets that limit. The default stays | ||
| 256. A busy public listener may want more. Values above tokio's | ||
| semaphore limit are refused at startup. |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -340,6 +340,9 @@ pub struct NodeConfig { | |
| /// Max `blockchain.scripthash.subscribe` entries per Electrum connection | ||
| /// (>= 1). Default [`rbitcoin_electrum::DEFAULT_MAX_SCRIPTHASH_SUBS`]. | ||
| pub electrum_max_subs: usize, | ||
| /// Max Esplora requests in flight across the whole listener (>= 1). | ||
| /// Default [`rbitcoin_electrum::DEFAULT_MAX_CONNECTIONS`]. | ||
| pub esplora_max_inflight: usize, | ||
| /// Opt-in Esplora `GET /block-template` (GBT template JSON). Default off. | ||
| pub esplora_block_template: bool, | ||
| /// Prometheus `GET /metrics` on the health listener. Default off. | ||
|
|
@@ -450,6 +453,7 @@ impl Default for NodeConfig { | |
| max_sh_creates: rbitcoin_query::DEFAULT_MAX_SH_CREATES, | ||
| heavy_sh_creates: rbitcoin_query::DEFAULT_HEAVY_SH_CREATES, | ||
| electrum_max_subs: rbitcoin_electrum::DEFAULT_MAX_SCRIPTHASH_SUBS, | ||
| esplora_max_inflight: rbitcoin_electrum::DEFAULT_MAX_CONNECTIONS, | ||
| esplora_block_template: false, | ||
| metrics: false, | ||
| esplora_onion: true, | ||
|
|
@@ -1328,6 +1332,19 @@ impl NodeConfig { | |
| } | ||
| self.electrum_max_subs = n; | ||
| } | ||
| "esplora_max_inflight" => { | ||
| let n: usize = val | ||
| .parse() | ||
| .map_err(|e| NodeError::Config(format!("conf esplora_max_inflight: {e}")))?; | ||
| // `Semaphore::new` panics above `MAX_PERMITS`. | ||
| if n == 0 || n > tokio::sync::Semaphore::MAX_PERMITS { | ||
| return Err(NodeError::Config(format!( | ||
| "conf esplora_max_inflight must be >= 1 and <= {}", | ||
| tokio::sync::Semaphore::MAX_PERMITS | ||
| ))); | ||
| } | ||
| self.esplora_max_inflight = n; | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Addressed in 5ca9103.
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Addressed in 5ca9103. |
||
| } | ||
| "esplora_block_template" => { | ||
| self.esplora_block_template = parse_conf_bool(val) | ||
| .map_err(|e| NodeError::Config(format!("conf esplora_block_template: {e}")))?; | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
blindbit_v1_routes_share_one_inflight_limitreturns 200 forGET /blocks/tip/heightifGlobalConcurrencyLimitLayer::newmoves back insidelistener_layers.max_connectionsisMAX + 1, and the three queued asks are the ones that must not hold a slot, so this GET still has a permit when/blindbit/v1has its own semaphore. The check fails only when those asks hold a shared slot, which is the permit order.Set
max_connectionsto the number of admitted BlindBit bodies and require this GET to stay unanswered until one body is dropped. Keep a spare slot only for the order check.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Addressed in b4f48b3. After the order check, a stalled
POST /txtakes the last slot andGET /blocks/tip/heightmust wait until it's dropped. With a separateGlobalConcurrencyLimitLayer::newper router, the test fails at that wait.