Skip to content

Conversation

@tnull
Copy link
Contributor

@tnull tnull commented Jan 20, 2026

Replace the reqwest dependency with bitreq, co-Authored-By: HAL 9000.

Replace the `reqwest` dependency with `bitreq`, a lighter HTTP(s) client
that reduces code bloat and dependency overhead.

Key changes:
- Use `bitreq::Client` for connection pooling and reuse
- Update all HTTP request handling to use bitreq's API
- Remove `reqwest::header::HeaderMap` in favor of `HashMap<String, String>`
- Simplify `LnurlAuthToJwtProvider::new()` to no longer return a `Result`
- Use `serde_json::from_slice()` directly for JSON parsing
- Build script uses bitreq's blocking `send()` method

Co-Authored-By: HAL 9000
Signed-off-by: Elias Rohrer <dev@tnull.de>
@tnull tnull requested review from TheBlueMatt and tankyleo January 20, 2026 09:08
@ldk-reviews-bot
Copy link

ldk-reviews-bot commented Jan 20, 2026

👋 Thanks for assigning @tankyleo as a reviewer!
I'll wait for their review and will help manage the review process.
Once they submit their review, I'll check if a second reviewer would be helpful.

let auth_response =
auth_request.send_async().await.map_err(VssHeaderProviderError::from)?;
let lnurl_auth_response: LnurlAuthResponse =
serde_json::from_slice(&auth_response.into_bytes()).map_err(|e| {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We could use bitreq's json-using-serde which goes through UTF-8 parsing first, but this seemed simpler and followed what the reqwest::Response::json method did (cf https://docs.rs/reqwest/latest/src/reqwest/async_impl/response.rs.html#269-273). Not sure if anyone has an opinion here?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

either is fine to me 🤷‍♂️

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@tnull sounds to me like this is something we can upstream to bitreq no ? ie pass the Vec<u8> body straight into serde_json::from_slice, and skip the utf8 parsing, which seems a net plus.

Copy link
Contributor Author

@tnull tnull Jan 21, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@tnull sounds to me like this is something we can upstream to bitreq no ? ie pass the Vec<u8> body straight into serde_json::from_slice, and skip the utf8 parsing, which seems a net plus.

Yeah, I considered that too. Just haven't fully made my mind up whether not validating UTF8 is okay. Maybe it's here, but not necessarily in the general case?

Copy link
Contributor Author

@tnull tnull left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Honestly not sure why the idna crate suddenly started failing to build under 1.75.0, and we aim to soon replace the whole url dependency with our own Url type, but for now we can also just fix the issue by bumping MSRV to 1.85, since LDK Node already requires that anyways and we aim too soon also update LDK to that.

@TheBlueMatt
Copy link
Contributor

and we aim too soon also update LDK to that.

Wait, what? This is news to me. Can we just remove the cursed url crate?

let auth_response =
auth_request.send_async().await.map_err(VssHeaderProviderError::from)?;
let lnurl_auth_response: LnurlAuthResponse =
serde_json::from_slice(&auth_response.into_bytes()).map_err(|e| {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

either is fine to me 🤷‍♂️

@tnull
Copy link
Contributor Author

tnull commented Jan 20, 2026

Wait, what? This is news to me.

No, Matt, we discussed this plan for hours at the Rust Summit. We landed on that most ecosystem crates (BDK, etc) would update ~right away given their release cycles, but that LDK would defer bumping until after Ubuntu 2604 is out, which will of course happen this April.

Can we just remove the cursed url crate?

Yeah, working on it as we speak. Just did a minurl release (cf. https://docs.rs/minurl/latest/minurl/), but should also have a PR for upstreaming it to bitreq up by the end of today.

@TheBlueMatt
Copy link
Contributor

that LDK would defer bumping until after Ubuntu 2604 is out, which will of course happen this April.

Right, April is a ways off though and we likely don't want to bump the day that a new release comes out (I'm kinda tired of us rushing to bump the day something comes out for the sake of it without a specific motivating rust feature, at least now that we've stripped out the stupid HTTP deps that keep causing dep-MSRV bumps), and I thought we wanted to upstream long before then lightningdevkit/rust-lightning#4323

const APPLICATION_OCTET_STREAM: &str = "application/octet-stream";
const DEFAULT_TIMEOUT: std::time::Duration = std::time::Duration::from_secs(10);
const DEFAULT_TIMEOUT_SECS: u64 = 10;
const MAX_RESPONSE_BODY_SIZE: usize = 500 * 1024 * 1024; // 500 MiB
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Now bumped this considerably, as of course 10MB would be much too small. Let me know if you have an opinion on a better value here @TheBlueMatt @tankyleo

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Mmm, yea, good question. On the server side we defaulted to 1G because its postgres' limit. Not that "just because its postgres' limit" is a good reason to do anything, though. 500 seems fine enough to me, honestly, though. As long as its documented I don't feel super strongly.

@tnull tnull force-pushed the 2026-01-switch-to-bitreq branch from 15114d4 to ef17d83 Compare January 20, 2026 12:31
.with_body(request_body)
.with_timeout(DEFAULT_TIMEOUT_SECS)
.with_max_body_size(Some(MAX_RESPONSE_BODY_SIZE))
.with_pipelining();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm bitreq says "Note that because pipelined requests may be replayed in case of failure, you should only set this on idempotent requests", and our PutObjectRequest is not idempotent.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

and our PutObjectRequest is not idempotent

Why is it not idempotent? If the request fails, we also assume the HTTP client to retry? This is essentially the same, no?

parent_key: Xpriv,
url: String,
default_headers: HashMap<String, String>,
client: reqwest::Client,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are we sure we want to delete the cached client here ? Thought we could cache it since the first get request in the lnurl auth flow is always to the same URL.

Copy link
Contributor Author

@tnull tnull Jan 21, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

AFAIU, we we'll only retrieve a new JWT token if the old one expired, which should be in the order of days or at least ours. It seems to me that keeping a client around (and potentially even keeping a connection open) is a bunch of unnecessary overhead if we just make single requests that rarely.

.map_err(VssHeaderProviderError::from)?;
let lnurl_request = bitreq::get(&self.url)
.with_headers(self.default_headers.clone())
.with_timeout(DEFAULT_TIMEOUT_SECS);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I know this is a trusted server, but do we want to set max body size here to prevent any possible OOM crashes ? same for the get request below.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

True, I overlooked that. Do you have a suggestion for a good limit here?

if status.is_success() {

let http_request = bitreq::post(url)
.with_header("content-type", APPLICATION_OCTET_STREAM)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: like you've done elsewhere, let's put this hard coded string in a constant ? Here and also in the lnurl auth module.

impl VssError {
/// Create new instance of `VssError`
pub fn new(status: StatusCode, payload: Bytes) -> VssError {
pub fn new(status_code: i32, payload: Vec<u8>) -> VssError {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: we don't need ownership and can pass a &[u8] here.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, but if we don't end up cloneing at the callsite, isn't move semantics even preferable to passing a reference?

let auth_response =
auth_request.send_async().await.map_err(VssHeaderProviderError::from)?;
let lnurl_auth_response: LnurlAuthResponse =
serde_json::from_slice(&auth_response.into_bytes()).map_err(|e| {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@tnull sounds to me like this is something we can upstream to bitreq no ? ie pass the Vec<u8> body straight into serde_json::from_slice, and skip the utf8 parsing, which seems a net plus.

@tnull tnull moved this to Goal: Merge in Weekly Goals Jan 21, 2026
@tankyleo tankyleo self-requested a review January 21, 2026 19:52
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: Goal: Merge

Development

Successfully merging this pull request may close these issues.

4 participants