Skip to content

Conversation

@leet4tari
Copy link
Contributor

Description
upgrade reqwest from v0.11 to v0.12
upgrade hyper from v0.14 to v1.6.0 - need for changes in reqwest v0.12

Motivation and Context
Avoid RUSTSEC-2025-0134 and assist with tari-project/tari-ootle#1672

How Has This Been Tested?
Builds all binaries in locally and in fork

What process can a PR reviewer use to test or verify this change?
Check the audit report that reqwest is now v0.12 or above

Breaking Changes

  • None
  • Requires data directory on base node to be deleted
  • Requires hard fork
  • Other - Please specify

@leet4tari leet4tari self-assigned this Dec 18, 2025
@gemini-code-assist
Copy link

Summary of Changes

Hello @leet4tari, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request focuses on a critical infrastructure update, bringing the HTTP client (reqwest) and server (hyper) libraries to their latest major versions. This move is essential for maintaining security, leveraging new features, and ensuring compatibility with the evolving Rust ecosystem. The upgrade to hyper v1.0, in particular, required a comprehensive overhaul of HTTP body processing within the merge mining proxy, adapting to its new asynchronous I/O model.

Highlights

  • Dependency Upgrades: Upgraded the reqwest crate from version 0.11 to 0.12 and the hyper crate from version 0.14 to 1.6.0. This is a significant major version bump for both libraries.
  • Security Fix: The upgrade addresses RUSTSEC-2025-0134, enhancing the security posture of the project.
  • Hyper v1.0 API Adaptation: The upgrade to hyper v1.0 necessitated substantial refactoring in the minotari_merge_mining_proxy application, particularly in how HTTP request and response bodies are handled, moving from hyper::Body to http_body_util and http_body traits.
  • New Dependencies: Introduced new dependencies such as http-body, http-body-util, hyper-util, tokio-stream, and tower to support the updated hyper and reqwest versions.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

Copy link

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

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

Code Review

This pull request upgrades reqwest from v0.11 to v0.12 and hyper from v0.14 to v1.0 to address RUSTSEC-2025-0134. The changes correctly adapt the codebase to the new APIs of these libraries. My review focuses on ensuring the migration is complete and correct. I've identified a regression in the server's shutdown behavior and a couple of minor improvements in error handling.

Comment on lines 124 to 137
loop {
let (tcp, _) = listener.accept().await?;
let svc = randomx_service.clone();
let io = hyper_util::rt::TokioIo::new(tcp);

tokio::task::spawn(async move {
if let Err(e) = http1::Builder::new()
.serve_connection(io, &*svc)
.await
{
error!("Connection error: {}", e);
}
});
}

Choose a reason for hiding this comment

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

high

The new server implementation uses an infinite loop which will never terminate gracefully. The previous hyper::Server implementation supported graceful shutdown. This is a regression in behavior, as the server can now only be stopped by killing the process.

A graceful shutdown mechanism should be added, for example by listening for a Ctrl+C signal. This would also allow the start_merge_miner function to terminate cleanly, which it currently does not.

            let mut shutdown = Box::pin(tokio::signal::ctrl_c());

            loop {
                tokio::select! {
                    biased;
                    _ = &mut shutdown => {
                        info!(target: LOG_TARGET, "Shutdown signal received, terminating server.");
                        break;
                    }
                    res = listener.accept() => {
                        let (tcp, _) = res?;
                        let svc = randomx_service.clone();
                        let io = hyper_util::rt::TokioIo::new(tcp);

                        tokio::task::spawn(async move {
                            if let Err(e) = http1::Builder::new()
                                .serve_connection(io, &*svc)
                                .await
                            {
                                error!("Connection error: {}", e);
                            }
                        });
                    }
                }
            }
            return Ok(());

Ok(bytes)
pub async fn read_body_until_end<B: HttpBodyTrait + Unpin>(body: B) -> Result<BytesMut, MmProxyError> {
let collected = body.collect().await
.map_err(|_e| MmProxyError::InvalidMonerodResponse("Failed to read body".to_string()))?;

Choose a reason for hiding this comment

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

medium

The error from body.collect().await is being ignored. While collect() on a body is not expected to fail often, it's better to include the actual error in the log message for easier debugging if it does happen.

Suggested change
.map_err(|_e| MmProxyError::InvalidMonerodResponse("Failed to read body".to_string()))?;
.map_err(|e| MmProxyError::InvalidMonerodResponse(format!("Failed to read body: {}", e)))?;

}
fn json_response_to_boxbody(resp: Response<serde_json::Value>) -> Response<ProxyBody> {
let (parts, body) = resp.into_parts();
let body_bytes = serde_json::to_vec(&body).unwrap_or_default();

Choose a reason for hiding this comment

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

medium

serde_json::to_vec(&body) on a serde_json::Value should not fail. However, using unwrap_or_default() will silently produce an empty response body if it ever does, which can make debugging difficult. Using expect() with a descriptive message is safer, as it would cause a panic and make any unexpected problem immediately obvious.

Suggested change
let body_bytes = serde_json::to_vec(&body).unwrap_or_default();
let body_bytes = serde_json::to_vec(&body).expect("json::Value should always be serializable");

@leet4tari leet4tari requested a review from a team as a code owner December 19, 2025 09:06
Copy link
Member

@sdbondi sdbondi left a comment

Choose a reason for hiding this comment

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

utACK

}

async fn serve(listener: TcpListener, service: MergeMiningProxyService) -> Result<(), MmProxyError> {
loop {
Copy link
Collaborator

Choose a reason for hiding this comment

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

we need to add a shutdown here, you can use TariShutdown if you want, but this is an endless loop here

Copy link
Member

@sdbondi sdbondi Jan 12, 2026

Choose a reason for hiding this comment

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

Shutdown here isn't necessary; the calling code will stop polling the future returned from serve on interrupt/shutdown/ctrl+c and will drop it at the end of the function.

  let mut shutdown = Box::pin(tokio::signal::ctrl_c());
            let mut serve_fut = Box::pin(serve(listener, randomx_service));
            tokio::select! {
                _ = &mut shutdown => {
                    info!(target: LOG_TARGET, "Ctrl-C received, shutting down merge mining proxy...");
                    println!("Ctrl-C: shutting down merge mining proxy...");
                }
                result = &mut serve_fut => {
                    if let Err(e) = result {
                        error!(target: LOG_TARGET, "Error in merge mining proxy service: {}", e);
                    }
                }
            }
            Ok(())
        },

Copy link
Collaborator

Choose a reason for hiding this comment

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

100% correct
But if someone else calls this function without calling it from some tokio loop with a select, its a going to end up in an endless loop.

I would rather we combine to two have an loop with the tokio select loop awaiting listener.accept() and the shutdown.

The way async fn serve( is written is assumed its called from a service that will drop it somehow, but you would never know that just by looking at the rust function header or place from where its called.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants