Skip to content

Commit

Permalink
feature(rpc): add an rpc server to Zebra (#3589)
Browse files Browse the repository at this point in the history
* feature(rpc): add an rpc component

* feat(rpc): add a stub for getblockchaininfo

This is the first RPC used by lightwalletd, so we need it for testing.

* fix(rpc): remove non-standard "jsonrpc: 1.0" from lightwalletd

* fix(rpc): re-enable default RPC security checks

* deps(rpc): remove not needed dependency

* fix(rpc): check if RPC task has stopped

* fix(rpc): reduce config by using Option

* fix(rpc): use tokio executor

* security(rpc): turn off rpc by default

* docs(rpc): update a TODO comment

Co-authored-by: teor <[email protected]>

* fix(rpc): blocking tasks

Co-authored-by: teor <[email protected]>

* rename(rpc): rpc.rs to methods.rs

* refactor(rpc): move the server to the zebra-rpc crate

* fix(rpc): clippy derive Default for RPC Config

* fix(dependencies): remove unused dependency features in zebra-rpc

We expect to use all the listed tokio features
to implement and test RPC methods.

* doc(rpc): fix testnet port, add security note

* fix(rpc): change Rust function names and update method doc TODOs

* fix(rpc): add "TODO" to fake RPC responses

* doc(rpc): update module docs

* fix(rpc): simplify server struct derives

* fix(rpc): simplify server code

* doc(rpc): explain how request fixes securely handle user-supplied data

* refactor(rpc): move the compatibility fix to a separate module

* fix(rpc): move the open log inside the spawn, and instrument it

* doc(rpc): fix toml format and provide a config example

Co-authored-by: teor <[email protected]>
  • Loading branch information
oxarbitrage and teor2345 authored Feb 22, 2022
1 parent 7e585b0 commit 8e36686
Show file tree
Hide file tree
Showing 10 changed files with 401 additions and 1 deletion.
115 changes: 115 additions & 0 deletions Cargo.lock

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

16 changes: 16 additions & 0 deletions zebra-rpc/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,19 @@ edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]

futures = "0.3"

# lightwalletd sends JSON-RPC requests over HTTP 1.1
hyper = { version = "0.14.17", features = ["http1", "server"] }

jsonrpc-core = "18.0.0"
jsonrpc-derive = "18.0.0"
jsonrpc-http-server = "18.0.0"

tokio = { version = "1.16.1", features = ["time", "rt-multi-thread", "macros", "tracing"] }

tracing = "0.1"
tracing-futures = "0.2"

serde = { version = "1", features = ["serde_derive"] }
30 changes: 30 additions & 0 deletions zebra-rpc/src/config.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
//! User-configurable RPC settings.
use std::net::SocketAddr;

use serde::{Deserialize, Serialize};

/// RPC configuration section.
#[derive(Clone, Debug, Default, Deserialize, Serialize)]
#[serde(deny_unknown_fields, default)]
pub struct Config {
/// IP address and port for the RPC server.
///
/// Note: The RPC server is disabled by default.
/// To enable the RPC server, set a listen address in the config:
/// ```toml
/// [rpc]
/// listen_addr = '127.0.0.1:8232'
/// ```
///
/// The recommended ports for the RPC server are:
/// - Mainnet: 127.0.0.1:8232
/// - Testnet: 127.0.0.1:18232
///
/// # Security
///
/// If you bind Zebra's RPC port to a public IP address,
/// anyone on the internet can send transactions via your node.
/// They can also query your node's state.
pub listen_addr: Option<SocketAddr>,
}
6 changes: 5 additions & 1 deletion zebra-rpc/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
//! A Zebra remote procedure call interface
//! A Zebra Remote Procedure Call (RPC) interface
#![doc(html_favicon_url = "https://www.zfnd.org/images/zebra-favicon-128.png")]
#![doc(html_logo_url = "https://www.zfnd.org/images/zebra-icon.png")]
#![doc(html_root_url = "https://doc.zebra.zfnd.org/zebra_rpc")]

pub mod config;
pub mod methods;
pub mod server;
69 changes: 69 additions & 0 deletions zebra-rpc/src/methods.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
//! Zebra supported RPC methods.
//!
//! Based on the [`zcashd` RPC methods](https://zcash.github.io/rpc/)
//! as used by `lightwalletd.`
//!
//! Some parts of the `zcashd` RPC documentation are outdated.
//! So this implementation follows the `lightwalletd` client implementation.
use jsonrpc_core::{self, Result};
use jsonrpc_derive::rpc;

#[rpc(server)]
/// RPC method signatures.
pub trait Rpc {
/// getinfo
///
/// TODO: explain what the method does
/// link to the zcashd RPC reference
/// list the arguments and fields that lightwalletd uses
/// note any other lightwalletd changes
#[rpc(name = "getinfo")]
fn get_info(&self) -> Result<GetInfo>;

/// getblockchaininfo
///
/// TODO: explain what the method does
/// link to the zcashd RPC reference
/// list the arguments and fields that lightwalletd uses
/// note any other lightwalletd changes
#[rpc(name = "getblockchaininfo")]
fn get_blockchain_info(&self) -> Result<GetBlockChainInfo>;
}

/// RPC method implementations.
pub struct RpcImpl;
impl Rpc for RpcImpl {
fn get_info(&self) -> Result<GetInfo> {
// TODO: dummy output data, fix in the context of #3142
let response = GetInfo {
build: "TODO: Zebra v1.0.0 ...".into(),
subversion: "TODO: /Zebra:1.0.0-beta.../".into(),
};

Ok(response)
}

fn get_blockchain_info(&self) -> Result<GetBlockChainInfo> {
// TODO: dummy output data, fix in the context of #3143
let response = GetBlockChainInfo {
chain: "TODO: main".to_string(),
};

Ok(response)
}
}

#[derive(serde::Serialize, serde::Deserialize)]
/// Response to a `getinfo` RPC request.
pub struct GetInfo {
build: String,
subversion: String,
}

#[derive(serde::Serialize, serde::Deserialize)]
/// Response to a `getblockchaininfo` RPC request.
pub struct GetBlockChainInfo {
chain: String,
// TODO: add other fields used by lightwalletd (#3143)
}
63 changes: 63 additions & 0 deletions zebra-rpc/src/server.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
//! A JSON-RPC 1.0 & 2.0 endpoint for Zebra.
//!
//! This endpoint is compatible with clients that incorrectly send
//! `"jsonrpc" = 1.0` fields in JSON-RPC 1.0 requests,
//! such as `lightwalletd`.
use tracing::*;
use tracing_futures::Instrument;

use jsonrpc_core;
use jsonrpc_http_server::ServerBuilder;

use crate::{
config::Config,
methods::{Rpc, RpcImpl},
server::compatibility::FixHttpRequestMiddleware,
};

pub mod compatibility;

/// Zebra RPC Server
#[derive(Clone, Debug)]
pub struct RpcServer;

impl RpcServer {
/// Start a new RPC server endpoint
pub fn spawn(config: Config) -> tokio::task::JoinHandle<()> {
if let Some(listen_addr) = config.listen_addr {
info!("Trying to open RPC endpoint at {}...", listen_addr,);

// Create handler compatible with V1 and V2 RPC protocols
let mut io =
jsonrpc_core::IoHandler::with_compatibility(jsonrpc_core::Compatibility::Both);
io.extend_with(RpcImpl.to_delegate());

let server = ServerBuilder::new(io)
// use the same tokio executor as the rest of Zebra
.event_loop_executor(tokio::runtime::Handle::current())
.threads(1)
// TODO: disable this security check if we see errors from lightwalletd.
//.allowed_hosts(DomainsValidation::Disabled)
.request_middleware(FixHttpRequestMiddleware)
.start_http(&listen_addr)
.expect("Unable to start RPC server");

// The server is a blocking task, so we need to spawn it on a blocking thread.
let span = Span::current();
let server = move || {
span.in_scope(|| {
info!("Opened RPC endpoint at {}", server.address());

server.wait();

info!("Stopping RPC endpoint");
})
};
tokio::task::spawn_blocking(server)
} else {
// There is no RPC port, so the RPC task does nothing.
tokio::task::spawn(futures::future::pending().in_current_span())
}
}
}
Loading

0 comments on commit 8e36686

Please sign in to comment.