Skip to content

Commit

Permalink
feat: Add JSONRPC server (#1)
Browse files Browse the repository at this point in the history
* feat: Add JSONRPC starter for http and websockets

Based on https://github.com/paritytech/jsonrpsee/tree/master/examples/examples

* chore: Add JSONRPC to main function

* chore: Add additional dependencies

* chore: Add needed features

* fix: Fix deps and env variables
  • Loading branch information
WillPapper authored Jan 7, 2025
1 parent d390c05 commit 814b569
Show file tree
Hide file tree
Showing 3 changed files with 109 additions and 3 deletions.
9 changes: 9 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,12 @@ edition = "2021"

[dependencies]
alloy = "0.9.2"
anyhow = "1.0"
derive_more = "1.0.0"
hex = "0.4.3"
http-body-util = "0.1.2"
hyper = { version = "1.5", features = ["full"] }
jsonrpsee = { version = "0.24", features = ["server", "client", "http-client", "ws-client"] }
# `bundled` causes us to automatically compile and link in an up to date
# version of SQLite for you. This avoids many common build issues, and
# avoids depending on the version of SQLite on the users system (or your
Expand All @@ -20,6 +24,11 @@ rusqlite = { version = "0.32", features = ["bundled", "functions"] }
serde = { version = "1.0", features = ["derive"] }
strum = { version = "0.26", features = ["derive", "std"] }
thiserror = "2.0.9"
tokio = { version = "1.42", features = ["full", "rt", "rt-multi-thread", "macros"] }
tower = "0.5"
tower-http = { version = "0.6", features = ["trace"] }
tracing = "0.1"
tracing-subscriber = { version = "0.3", features = ["env-filter"] }

[[bin]]
name = "sqlite"
Expand Down
96 changes: 95 additions & 1 deletion src/jsonrpc.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,98 @@
// Exposes a JSON-RPC interface that is compatible with the Ethereum JSON-RPC API
// https://ethereum.org/en/developers/docs/apis/json-rpc/
// Will read data via sqlite.rs. See `src/sqlite.rs` for the current
// implementation.
// implementation.

use std::net::SocketAddr;
use std::time::Duration;

use hyper::body::Bytes;
use hyper::Request;
use http_body_util::Full;
use jsonrpsee::core::client::ClientT;
use jsonrpsee::http_client::HttpClient;
use jsonrpsee::server::{RpcModule, Server};
use jsonrpsee::ws_client::WsClientBuilder;
use jsonrpsee::rpc_params;
use tokio::task;
use tower_http::trace::{DefaultMakeSpan, DefaultOnResponse};
use tower_http::LatencyUnit;
use tracing_subscriber::util::SubscriberInitExt;

pub async fn run_server() -> anyhow::Result<()> {
// Use a default filter if RUST_LOG is not set
let filter = tracing_subscriber::EnvFilter::try_from_default_env()
.unwrap_or_else(|_| tracing_subscriber::EnvFilter::new("info"))
.add_directive("jsonrpsee[method_call{name = \"say_hello\"}]=trace".parse()?);

tracing_subscriber::FmtSubscriber::builder()
.with_env_filter(filter)
.finish()
.try_init()?;

// Run both HTTP and WebSocket servers concurrently
let http_addr = task::spawn(run_http_server());
let ws_addr = task::spawn(run_ws_server());

// Wait for both servers to start and print their addresses
let http_addr = http_addr.await??;
let ws_addr = ws_addr.await??;

tracing::info!("HTTP server running on {}", http_addr);
tracing::info!("WebSocket server running on {}", ws_addr);

// Example HTTP client
let http_client_url = format!("http://{}", http_addr);
let http_client = HttpClient::builder()
.build(http_client_url)?;

// Create a separate middleware for tracing the client requests
let _trace_layer = tower_http::trace::TraceLayer::new_for_http()
.on_request(|request: &Request<Full<Bytes>>, _span: &tracing::Span| {
tracing::info!(request = ?request, "on_request")
})
.on_body_chunk(|chunk: &Bytes, latency: Duration, _: &tracing::Span| {
tracing::info!(size_bytes = chunk.len(), latency = ?latency, "sending body chunk")
})
.make_span_with(DefaultMakeSpan::new().include_headers(true))
.on_response(DefaultOnResponse::new().include_headers(true).latency_unit(LatencyUnit::Micros));

let response: Result<String, _> = http_client.request("say_hello", rpc_params![1_u64, 2, 3]).await;
tracing::info!("HTTP client response: {:?}", response);

// Example WebSocket client
let ws_client_url = format!("ws://{}", ws_addr);
let ws_client = WsClientBuilder::default().build(&ws_client_url).await?;
let ws_response: String = ws_client.request("say_hello", rpc_params![]).await?;
tracing::info!("WebSocket client response: {:?}", ws_response);

Ok(())
}

async fn run_http_server() -> anyhow::Result<SocketAddr> {
let server = Server::builder().build("127.0.0.1:0".parse::<SocketAddr>()?).await?;
let mut module = RpcModule::new(());
module.register_method("say_hello", |_, _, _| "Hello from HTTP!")?;

let addr = server.local_addr()?;
let handle = server.start(module);

// Run HTTP server in the background
tokio::spawn(handle.stopped());

Ok(addr)
}

async fn run_ws_server() -> anyhow::Result<SocketAddr> {
let server = Server::builder().build("127.0.0.1:0".parse::<SocketAddr>()?).await?;
let mut module = RpcModule::new(());
module.register_method("say_hello", |_, _, _| "Hello from WebSocket!")?;

let addr = server.local_addr()?;
let handle = server.start(module);

// Run WebSocket server in the background
tokio::spawn(handle.stopped());

Ok(addr)
}
7 changes: 5 additions & 2 deletions src/main.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
// Will handle setup tasks. See `src/sqlite.rs` for the current implementation.
fn main() {
mod jsonrpc;

#[tokio::main]
async fn main() {
println!("MintVM started");
jsonrpc::run_server().await.expect("Failed to start JSON-RPC server");
}

0 comments on commit 814b569

Please sign in to comment.