Skip to content

Commit

Permalink
Initial cli for running JSON-RPC against trin
Browse files Browse the repository at this point in the history
  • Loading branch information
lithp committed Nov 9, 2021
1 parent 00a9479 commit 57259ef
Show file tree
Hide file tree
Showing 4 changed files with 197 additions and 6 deletions.
46 changes: 40 additions & 6 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ tracing-subscriber = "0.2.18"
trin-core = { path = "trin-core" }
trin-history = { path = "trin-history" }
trin-state = { path = "trin-state" }
trin-cli = { path = "trin-cli" }

[dependencies.discv5]
version = "0.1.0-beta.10"
Expand All @@ -26,5 +27,6 @@ members = [
"trin-history",
"trin-state",
"trin-core",
"trin-cli",
"ethportal-peertest"
]
18 changes: 18 additions & 0 deletions trin-cli/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
[package]
name = "trin-cli"
version = "0.1.0"
edition = "2021"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
structopt="0.3.25"
trin-core = { path = "../trin-core" }
jsonrpc = "0.12.0"
serde_json = "1.0.68"
serde = "1.0.117"
thiserror = "1.0.29"

[[bin]]
name="trin-cli"
path="src/main.rs"
137 changes: 137 additions & 0 deletions trin-cli/src/main.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
use std::os::unix::net::UnixStream;
use std::path::PathBuf;
use structopt::StructOpt;
use thiserror::Error;

use trin_core::cli::DEFAULT_WEB3_IPC_PATH;

#[derive(StructOpt, Debug)]
#[structopt(
name = "trin-cli",
version = "0.0.1",
author = "Ethereum Foundation",
about = "Run JSON-RPC commands against trin nodes"
)]
struct Config {
#[structopt(
default_value(DEFAULT_WEB3_IPC_PATH),
long,
help = "path to JSON-RPC endpoint"
)]
ipc: PathBuf,

#[structopt(help = "e.g. discv5_routingTableInfo", required = true)]
endpoint: String,
}

fn main() {
let Config { ipc, endpoint } = Config::from_args();

eprintln!(
"Attempting RPC. endpoint={} file={}",
endpoint,
ipc.to_string_lossy()
);
let mut client = match TrinClient::from_ipc(&ipc) {
Ok(client) => client,
Err(err) => {
eprintln!("Could not connect. err={:?}", err);
return;
}
};

let req = client.build_request(endpoint.as_str());
let resp: Result<serde_json::Value, JsonRpcError> = client.make_request(req);

match resp {
Err(error) => {
eprintln!("error: {}", error);
}
Ok(value) => {
// unwrap: this value is safe to serialize, it was just deserialized!
println!("{}", serde_json::to_string_pretty(&value).unwrap());
}
}
}

fn build_request<'a>(method: &'a str, request_id: u64) -> jsonrpc::Request<'a> {
jsonrpc::Request {
method: method,
params: &[],
id: serde_json::json!(request_id),
jsonrpc: Some("2.0"),
}
}

pub trait TryClone {
fn try_clone(&self) -> std::io::Result<Self>
where
Self: Sized;
}

impl TryClone for UnixStream {
fn try_clone(&self) -> std::io::Result<Self> {
UnixStream::try_clone(self)
}
}

pub struct TrinClient<S>
where
S: std::io::Read + std::io::Write + TryClone,
{
stream: S,
request_id: u64,
}

impl TrinClient<UnixStream> {
fn from_ipc(path: &PathBuf) -> std::io::Result<Self> {
// TODO: a nice error if this file does not exist
Ok(Self {
stream: UnixStream::connect(path)?,
request_id: 0,
})
}
}

#[derive(Error, Debug)]
pub enum JsonRpcError {
#[error("Received malformed response: {0}")]
Malformed(serde_json::Error),

#[error("Received empty response")]
Empty,
}

// TryClone is used because JSON-RPC responses are not followed by EOF. We must read bytes
// from the stream until a complete object is detected, and the simplest way of doing that
// with available APIs is to give ownership of a Read to a serde_json::Deserializer. If we
// gave it exclusive ownership that would require us to open a new connection for every
// command we wanted to send! By making a clone (or, by trying to) we can have our cake
// and eat it too.
impl<'a, S> TrinClient<S>
where
S: std::io::Read + std::io::Write + TryClone,
{
fn build_request(&mut self, method: &'a str) -> jsonrpc::Request<'a> {
let result = build_request(method, self.request_id);
self.request_id += 1;

result
}

fn make_request(&mut self, req: jsonrpc::Request) -> Result<serde_json::Value, JsonRpcError> {
let data = serde_json::to_vec(&req).unwrap();

self.stream.write_all(&data).unwrap();
self.stream.flush().unwrap();

let clone = self.stream.try_clone().unwrap();
let deser = serde_json::Deserializer::from_reader(clone);
for obj in deser.into_iter::<serde_json::Value>() {
return obj.map_err(|err| JsonRpcError::Malformed(err));
}

// this should only happen when they immediately send EOF
Err(JsonRpcError::Empty)
}
}

0 comments on commit 57259ef

Please # to comment.