Skip to content

Commit

Permalink
Handle differences in CLI & direct parameter and response formats
Browse files Browse the repository at this point in the history
  • Loading branch information
teor2345 committed Apr 14, 2023
1 parent 9daf7d8 commit ba63bac
Showing 1 changed file with 33 additions and 18 deletions.
51 changes: 33 additions & 18 deletions zebra-utils/src/bin/zebra-checkpoints/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,8 @@ pub mod args;

use args::{Args, Backend, Transport};

/// Make an RPC call based on `our_args` and `rpc_command`, and return the response as a string.
async fn rpc_output<M, I>(our_args: &Args, method: M, params: I) -> Result<String>
/// Make an RPC call based on `our_args` and `rpc_command`, and return the response as a [`Value`].
async fn rpc_output<M, I>(our_args: &Args, method: M, params: I) -> Result<Value>
where
M: AsRef<str>,
I: IntoIterator<Item = String>,
Expand All @@ -48,10 +48,10 @@ where
}
}

/// Connect to the node with `our_args` and `rpc_command`, and return the response as a string.
/// Connect to the node with `our_args` and `rpc_command`, and return the response as a [`Value`].
///
/// Only used if the transport is [`Direct`](Transport::Direct).
async fn direct_output<M, I>(our_args: &Args, method: M, params: I) -> Result<String>
async fn direct_output<M, I>(our_args: &Args, method: M, params: I) -> Result<Value>
where
M: AsRef<str>,
I: IntoIterator<Item = String>,
Expand All @@ -63,16 +63,23 @@ where
let client = RpcRequestClient::new(addr);

// Launch a request with the RPC method and arguments
//
// The params are a JSON array with typed arguments.
// TODO: accept JSON value arguments, and do this formatting using serde_json
let params = format!("[{}]", params.into_iter().join(", "));
let response = client.text_from_call(method, params).await?;

// Extract the "result" field from the RPC response
let mut response: Value = serde_json::from_str(&response)?;
let response = response["result"].take();

Ok(response)
}

/// Run `cmd` with `our_args` and `rpc_command`, and return its output as a string.
/// Run `cmd` with `our_args` and `rpc_command`, and return its output as a [`Value`].
///
/// Only used if the transport is [`Cli`](Transport::Cli).
fn cli_output<M, I>(our_args: &Args, method: M, params: I) -> Result<String>
fn cli_output<M, I>(our_args: &Args, method: M, params: I) -> Result<Value>
where
M: AsRef<str>,
I: IntoIterator<Item = String>,
Expand All @@ -93,6 +100,9 @@ where
cmd.arg(method);

for param in params {
// Remove JSON string/int type formatting, because zcash-cli will add it anyway
// TODO: accept JSON value arguments, and do this formatting using serde_json?
let param = param.trim_matches('"');
let param: OsString = param.into();
cmd.arg(param);
}
Expand All @@ -115,9 +125,14 @@ where
output.status.code()
);

// Make sure the output is valid UTF-8
let s = String::from_utf8(output.stdout)?;
Ok(s)
// Make sure the output is valid UTF-8 JSON
let response = String::from_utf8(output.stdout)?;
// zcash-cli returns raw strings without JSON type info.
// As a workaround, assume that invalid responses are strings.
let response: Value = serde_json::from_str(&response)
.unwrap_or_else(|_error| Value::String(response.trim().to_string()));

Ok(response)
}

/// Process entry point for `zebra-checkpoints`
Expand All @@ -131,9 +146,9 @@ async fn main() -> Result<()> {
let args = args::Args::from_args();

// get the current block count
let get_block_chain_info = rpc_output(&args, "getblockchaininfo", None).await?;
let get_block_chain_info: Value =
serde_json::from_str(&get_block_chain_info).with_suggestion(|| {
let get_block_chain_info = rpc_output(&args, "getblockchaininfo", None)
.await
.with_suggestion(|| {
"Is the RPC server address and port correct? Is authentication configured correctly?"
})?;

Expand Down Expand Up @@ -179,13 +194,10 @@ async fn main() -> Result<()> {
let get_block = rpc_output(
&args,
"getblock",
[request_height.to_string(), 1.to_string()],
[format!(r#""{request_height}""#), 1.to_string()],
)
.await?;

// parse json
let get_block: Value = serde_json::from_str(&get_block)?;

// get the values we are interested in
let hash: block::Hash = get_block["hash"]
.as_str()
Expand All @@ -206,11 +218,14 @@ async fn main() -> Result<()> {
let block_bytes = rpc_output(
&args,
"getblock",
[request_height.to_string(), 0.to_string()],
[format!(r#""{request_height}""#), 0.to_string()],
)
.await?;
let block_bytes = block_bytes
.as_str()
.expect("block bytes: unexpected missing field or field type");

let block_bytes: Vec<u8> = hex::decode(block_bytes.trim_end_matches('\n'))?;
let block_bytes: Vec<u8> = hex::decode(block_bytes)?;

// TODO: is it faster to call both `getblock height 0` and `getblock height 1`,
// rather than deserializing the block and calculating its hash?
Expand Down

0 comments on commit ba63bac

Please sign in to comment.