asking the mempool to a bitcoin node on testnet via rpc and using the rust library hyper 1.1.0

66 views Asked by At

I need to use bitcoin-rpc protocol to request the list of the transactions in the mempool to a running bitcoin node on testnet. I also need hyper for some specific reasons (I need a low level library). I could make it with the version 0.14.28, but I want to use the latest release, with which I am not succeeding. As a disclaimer, I have a relatively short experience with coding, so I particularly enjoy long and describing answers! The code I tried is the following. It prints "A" but remain stuck without printing "B", that makes me think that the server is not responding.

use base64::Engine;
use bytes::Bytes;
use http_body_util::{BodyExt, Full};
use hyper::header::{AUTHORIZATION, CONTENT_TYPE};
use hyper::Request;
use hyper_util::rt::TokioIo;
use serde::{Deserialize, Serialize};
use serde_json::json;
use tokio::net::TcpStream;

#[tokio::main]
async fn main() {
    let response = send_json_rpc_request("getrawmempool", json!([])).await;
    match response {
        Ok(result) => {
            let response_: Result<Vec<String>, ()> =
                match serde_json::from_value(result.result.unwrap()) {
                    Ok(response_inner) => response_inner,
                    Err(_) => {
                        println!("Deserialization error");
                        Err(())
                    }
                };
            println!("The mempool is: {:?}", response_.unwrap());
        }
        Err(error) => {
            println!("Error: {:?}", error);
        }
    }
}

async fn send_json_rpc_request<T: for<'de> Deserialize<'de> + std::fmt::Debug>(
    method: &str,
    params: serde_json::Value,
) -> Result<JsonRpcResult<T>, JsonRpcError> {
    let url = "http://127.0.0.1:18332".parse::<hyper::Uri>().unwrap();
    let host = url.host().expect("uri has no host");
    let port = url.port_u16().unwrap_or(18332);
    let address = format!("{}:{}", host, port);
    let stream = TcpStream::connect(address).await.unwrap();
    let io = TokioIo::new(stream);
    let (mut sender, _conn) = hyper::client::conn::http1::handshake(io).await.unwrap();
    let (username, password) = ("username", "password");
    let request = JsonRpcRequest {
        jsonrpc: "2.0".to_string(),
        method: method.to_string(),
        params,
        id: 1,
    };

    let request_body = serde_json::to_string(&request).unwrap();

    let req = Request::builder()
        .method("POST")
        .uri(url)
        .header(CONTENT_TYPE, "application/json")
        .header(
            AUTHORIZATION,
            format!(
                "Basic {}",
                base64::engine::general_purpose::STANDARD
                    .encode(format!("{}:{}", username, password))
            ),
        )
        .body(Full::<Bytes>::from(request_body))
        .unwrap();

    println!("A");
    let response = sender.send_request(req).await.unwrap();

    println!("B");
    let status = response.status();
    let body = response.into_body().collect().await.unwrap().to_bytes();

    if status.is_success() {
        serde_json::from_slice(&body).unwrap()
    } else {
        match serde_json::from_slice(&body) {
            Ok(error_response) => Err(error_response),
            Err(e) => Err(JsonRpcError {
                code: -1,
                message: format!("Deserialization error {:?}", e),
            }),
        }
    }
}
#[derive(Debug, Serialize)]
struct JsonRpcRequest {
    jsonrpc: String,
    method: String,
    params: serde_json::Value,
    id: u64,
}

#[derive(Debug, Deserialize)]
pub struct JsonRpcResult<T> {
    result: Option<T>,
    error: Option<JsonRpcError>,
    id: u64,
}

#[derive(Debug, Deserialize, Clone)]
pub struct JsonRpcError {
    code: i32,
    message: String,
}

I also note that the representation of the body of request in variable "req" is the follfowing

    &req = Request {
        method: POST,
        uri: http://127.0.0.1:18332/,
        version: HTTP/1.1,
        headers: {
            "content-type": "application/json",
            "authorization": "Basic dXNlcm5hbWU6cGFzc3dvcmQ=",
        },
        body: Full {
            data: Some(
                b"{\"jsonrpc\":\"2.0\",\"method\":\"getrawmempool\",\"params\":[],\"id\":1}",
            ),
        },
    }

and if I use the previous version of hyper (0.14.28), with which it worked, the request body is represented slightly differently. I don't know if this is the issue.

&req = Request {
    method: POST,
    uri: http://127.0.0.1:18332/,
    version: HTTP/1.1,
    headers: {
        "content-type": "application/json",
        "authorization": "Basic dXNlcm5hbWU6cGFzc3dvcmQ=",
    },
    body: Body(
        Full(
            b"{\"jsonrpc\":\"2.0\",\"method\":\"getrawmempool\",\"params\":[],\"id\":1}",
        ),
    ),
}

Finally, the bitcoin node that I am running is the latest master, with commit 4b1196a9855dcd188a24f393aa2fa21e2d61f061. The configuration of the bitcoin node is the following

[test]
testnet=1
server=1
datadir=<path to your testnet blockchain>
rpcuser=username
rpcpassword=password
rpcport=18332
1

There are 1 answers

0
Lorban On

Eventually I relied on the Client in legacy module of hyper_util. This code should work

use base64::Engine;
use bytes::Bytes;
use http_body_util::{BodyExt, Full};
use hyper::header::{AUTHORIZATION, CONTENT_TYPE};
use hyper::Request;
use hyper_util::client::legacy::Client;
use hyper_util::rt::TokioExecutor;
use serde::{Deserialize, Serialize};
use serde_json::json;

#[tokio::main]
async fn main() {
    let response = send_json_rpc_request("getrawmempool", json!([])).await;
    match response {
        Ok(result) => {
            let response_deserialized: JsonRpcResult<Vec<String>> =
                serde_json::from_str(&result).unwrap();
            let result_inner = response_deserialized.result.unwrap();
            println!("Transactions: {:?}", result_inner);
        }
        Err(error) => {
            println!("Error: {:?}", error);
        }
    }
}

async fn send_json_rpc_request(
    method: &str,
    params: serde_json::Value,
) -> Result<String, JsonRpcError> {
    let url = "http://127.0.0.1:18332".parse::<hyper::Uri>().unwrap();
    let (username, password) = ("username", "password");
    let client: Client<_, Full<Bytes>> = Client::builder(TokioExecutor::new()).build_http();
    let request = JsonRpcRequest {
        jsonrpc: "2.0".to_string(),
        method: method.to_string(),
        params,
        id: 1,
    };

    let request_body = serde_json::to_string(&request).unwrap();

    let req = Request::builder()
        .method("POST")
        .uri(url)
        .header(CONTENT_TYPE, "application/json")
        .header(
            AUTHORIZATION,
            format!(
                "Basic {}",
                base64::engine::general_purpose::STANDARD
                    .encode(format!("{}:{}", username, password))
            ),
        )
        .body(Full::<Bytes>::from(request_body))
        .unwrap();

    let response = client.request(req).await.unwrap();
    let status = response.status();
    let body = response.into_body().collect().await.unwrap().to_bytes();

    if status.is_success() {
        Ok(String::from_utf8(body.to_vec()).unwrap())
    } else {
        match serde_json::from_slice(&body) {
            Ok(error_response) => Err(error_response),
            Err(e) => Err(JsonRpcError {
                code: -1,
                message: format!("Deserialization error {:?}", e),
            }),
        }
    }
}
#[derive(Debug, Serialize)]
struct JsonRpcRequest {
    jsonrpc: String,
    method: String,
    params: serde_json::Value,
    id: u64,
}

#[derive(Debug, Deserialize)]
pub struct JsonRpcResult<T> {
    result: Option<T>,
    error: Option<JsonRpcError>,
    id: u64,
}

#[derive(Debug, Deserialize, Clone)]
pub struct JsonRpcError {
    code: i32,
    message: String,
}