From 549bfe74121f016ef6e0d23bc62b42dd28c2a3d3 Mon Sep 17 00:00:00 2001 From: Pankaj Garg Date: Fri, 30 Nov 2018 15:07:08 -0800 Subject: [PATCH] Vote signing JSON RPC service (#1965) * Vote signing JSON RPC service - barebone service that listens for RPC requests * Daemon for vote signer service * Add request APIs for JSON RPC * Cleanup of cargo dependencies * Fix compiler error --- Cargo.lock | 15 ++++ Cargo.toml | 2 + src/rpc_request.rs | 6 ++ vote-signer/Cargo.toml | 25 ++++++ vote-signer/src/bin/main.rs | 42 +++++++++++ vote-signer/src/lib.rs | 10 +++ vote-signer/src/rpc.rs | 147 ++++++++++++++++++++++++++++++++++++ 7 files changed, 247 insertions(+) create mode 100644 vote-signer/Cargo.toml create mode 100644 vote-signer/src/bin/main.rs create mode 100644 vote-signer/src/lib.rs create mode 100644 vote-signer/src/rpc.rs diff --git a/Cargo.lock b/Cargo.lock index e654652dc8..d187be0af1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1723,6 +1723,7 @@ dependencies = [ "solana-metrics 0.11.0", "solana-noop 0.11.0", "solana-sdk 0.11.0", + "solana-vote-signer 0.0.1", "sys-info 0.5.6 (registry+https://github.com/rust-lang/crates.io-index)", "tokio 0.1.13 (registry+https://github.com/rust-lang/crates.io-index)", "tokio-codec 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", @@ -1904,6 +1905,20 @@ dependencies = [ "untrusted 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "solana-vote-signer" +version = "0.0.1" +dependencies = [ + "bs58 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", + "clap 2.32.0 (registry+https://github.com/rust-lang/crates.io-index)", + "log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)", + "solana-jsonrpc-core 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", + "solana-jsonrpc-http-server 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", + "solana-jsonrpc-macros 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", + "solana-metrics 0.11.0", + "solana-sdk 0.11.0", +] + [[package]] name = "solana-ws" version = "0.1.1" diff --git a/Cargo.toml b/Cargo.toml index 1d0a311b0a..156b83c8ce 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -109,6 +109,7 @@ solana-lualoader = { path = "programs/native/lua_loader", version = "0.11.0" } solana-metrics = { path = "metrics", version = "0.11.0" } solana-noop = { path = "programs/native/noop", version = "0.11.0" } solana-sdk = { path = "sdk", version = "0.11.0" } +solana-vote-signer = { path = "vote-signer", version = "0.0.1" } sys-info = "0.5.6" tokio = "0.1" tokio-codec = "0.1" @@ -147,4 +148,5 @@ members = [ "programs/native/erc20", "programs/native/lua_loader", "programs/native/noop", + "vote-signer", ] diff --git a/src/rpc_request.rs b/src/rpc_request.rs index eff3d50a70..f86121e5dc 100644 --- a/src/rpc_request.rs +++ b/src/rpc_request.rs @@ -13,6 +13,9 @@ pub enum RpcRequest { GetTransactionCount, RequestAirdrop, SendTransaction, + RegisterNode, + SignVote, + DeregisterNode, } pub type RpcClient = reqwest::Client; @@ -53,6 +56,9 @@ impl RpcRequest { RpcRequest::GetTransactionCount => "getTransactionCount", RpcRequest::RequestAirdrop => "requestAirdrop", RpcRequest::SendTransaction => "sendTransaction", + RpcRequest::RegisterNode => "registerNode", + RpcRequest::SignVote => "signVote", + RpcRequest::DeregisterNode => "deregisterNode", }; let mut request = json!({ "jsonrpc": jsonrpc, diff --git a/vote-signer/Cargo.toml b/vote-signer/Cargo.toml new file mode 100644 index 0000000000..cfe122647f --- /dev/null +++ b/vote-signer/Cargo.toml @@ -0,0 +1,25 @@ +[package] +name = "solana-vote-signer" +version = "0.0.1" +description = "Solana Vote Signing Service" +authors = ["Solana Maintainers "] +repository = "https://github.com/solana-labs/solana" +license = "Apache-2.0" + +[dependencies] +bs58 = "0.2.0" +clap = "2.31" +log = "0.4.2" +solana-sdk = { path = "../sdk", version = "0.11.0" } +solana-metrics = { path = "../metrics", version = "0.11.0" } +solana-jsonrpc-core = "0.3.0" +solana-jsonrpc-http-server = "0.3.0" +solana-jsonrpc-macros = "0.3.0" + +[lib] +name = "solana_vote_signer" +crate-type = ["lib"] + +[[bin]] +name = "solana-vote-signer" +path = "src/bin/main.rs" diff --git a/vote-signer/src/bin/main.rs b/vote-signer/src/bin/main.rs new file mode 100644 index 0000000000..83fc03a35b --- /dev/null +++ b/vote-signer/src/bin/main.rs @@ -0,0 +1,42 @@ +#[macro_use] +extern crate clap; +extern crate log; +extern crate solana_metrics; +extern crate solana_sdk; +extern crate solana_vote_signer; + +use clap::{App, Arg}; +use solana_vote_signer::rpc::VoteSignerRpcService; +use std::error; +use std::net::{IpAddr, Ipv4Addr, SocketAddr}; + +pub const RPC_PORT: u16 = 8989; + +fn main() -> Result<(), Box> { + solana_metrics::set_panic_hook("vote-signer"); + + let matches = App::new("vote-signer") + .version(crate_version!()) + .arg( + Arg::with_name("port") + .long("port") + .value_name("NUM") + .takes_value(true) + .help("JSON RPC listener port"), + ).get_matches(); + + let port = if let Some(p) = matches.value_of("port") { + p.to_string() + .parse() + .expect("Failed to parse JSON RPC Port") + } else { + RPC_PORT + }; + + let service = + VoteSignerRpcService::new(SocketAddr::new(IpAddr::V4(Ipv4Addr::new(0, 0, 0, 0)), port)); + + service.join().unwrap(); + + Ok(()) +} diff --git a/vote-signer/src/lib.rs b/vote-signer/src/lib.rs new file mode 100644 index 0000000000..6e075b8238 --- /dev/null +++ b/vote-signer/src/lib.rs @@ -0,0 +1,10 @@ +pub mod rpc; + +extern crate bs58; +#[macro_use] +extern crate log; +extern crate solana_jsonrpc_core as jsonrpc_core; +extern crate solana_jsonrpc_http_server as jsonrpc_http_server; +extern crate solana_sdk; +#[macro_use] +extern crate solana_jsonrpc_macros as jsonrpc_macros; diff --git a/vote-signer/src/rpc.rs b/vote-signer/src/rpc.rs new file mode 100644 index 0000000000..f264cc8669 --- /dev/null +++ b/vote-signer/src/rpc.rs @@ -0,0 +1,147 @@ +//! The `rpc` module implements the Vote signing service RPC interface. + +use bs58; +use jsonrpc_core::*; +use jsonrpc_http_server::*; +use solana_sdk::pubkey::Pubkey; +use solana_sdk::signature::Signature; +use std::mem; +use std::net::SocketAddr; +use std::sync::atomic::{AtomicBool, Ordering}; +use std::sync::Arc; +use std::thread::{self, sleep, Builder, JoinHandle}; +use std::time::Duration; + +pub struct VoteSignerRpcService { + thread_hdl: JoinHandle<()>, + exit: Arc, +} + +impl VoteSignerRpcService { + pub fn new(rpc_addr: SocketAddr) -> Self { + let request_processor = VoteSignRequestProcessor::default(); + let exit = Arc::new(AtomicBool::new(false)); + let exit_ = exit.clone(); + let thread_hdl = Builder::new() + .name("solana-vote-signer-jsonrpc".to_string()) + .spawn(move || { + let mut io = MetaIoHandler::default(); + let rpc = VoteSignerRpcImpl; + io.extend_with(rpc.to_delegate()); + + let server = + ServerBuilder::with_meta_extractor(io, move |_req: &hyper::Request| Meta { + request_processor: request_processor.clone(), + rpc_addr, + }).threads(4) + .cors(DomainsValidation::AllowOnly(vec![ + AccessControlAllowOrigin::Any, + ])) + .start_http(&rpc_addr); + if server.is_err() { + warn!("JSON RPC service unavailable: unable to bind to RPC port {}. \nMake sure this port is not already in use by another application", rpc_addr.port()); + return; + } + while !exit_.load(Ordering::Relaxed) { + sleep(Duration::from_millis(100)); + } + server.unwrap().close(); + () + }) + .unwrap(); + VoteSignerRpcService { thread_hdl, exit } + } + + pub fn exit(&self) { + self.exit.store(true, Ordering::Relaxed); + } + + pub fn close(self) -> thread::Result<()> { + self.exit(); + self.join() + } + + pub fn join(self) -> thread::Result<()> { + self.thread_hdl.join() + } +} + +#[derive(Clone)] +pub struct Meta { + pub request_processor: VoteSignRequestProcessor, + pub rpc_addr: SocketAddr, +} +impl Metadata for Meta {} + +build_rpc_trait! { + pub trait VoteSignerRpc { + type Metadata; + + #[rpc(meta, name = "registerNode")] + fn register(&self, Self::Metadata, String) -> Result; + + #[rpc(meta, name = "signVote")] + fn sign(&self, Self::Metadata, String) -> Result; + + #[rpc(meta, name = "deregisterNode")] + fn deregister(&self, Self::Metadata, String) -> Result<()>; + } +} + +pub struct VoteSignerRpcImpl; +impl VoteSignerRpc for VoteSignerRpcImpl { + type Metadata = Meta; + + fn register(&self, meta: Self::Metadata, id: String) -> Result { + info!("register rpc request received: {:?}", id); + let pubkey = get_pubkey(id)?; + meta.request_processor.register(pubkey) + } + + fn sign(&self, meta: Self::Metadata, id: String) -> Result { + info!("sign rpc request received: {:?}", id); + let pubkey = get_pubkey(id)?; + meta.request_processor.sign(pubkey) + } + + fn deregister(&self, meta: Self::Metadata, id: String) -> Result<()> { + info!("deregister rpc request received: {:?}", id); + let pubkey = get_pubkey(id)?; + meta.request_processor.deregister(pubkey) + } +} + +#[derive(Clone, Default)] +pub struct VoteSignRequestProcessor {} +impl VoteSignRequestProcessor { + /// Process JSON-RPC request items sent via JSON-RPC. + pub fn register(&self, pubkey: Pubkey) -> Result { + Ok(pubkey) + } + pub fn sign(&self, _pubkey: Pubkey) -> Result { + let signature = [0u8; 16]; + Ok(Signature::new(&signature)) + } + pub fn deregister(&self, _pubkey: Pubkey) -> Result<()> { + Ok(()) + } +} + +fn get_pubkey(input: String) -> Result { + let pubkey_vec = bs58::decode(input).into_vec().map_err(|err| { + info!("get_pubkey: invalid input: {:?}", err); + Error::invalid_request() + })?; + if pubkey_vec.len() != mem::size_of::() { + info!( + "get_pubkey: invalid pubkey_vec length: {}", + pubkey_vec.len() + ); + Err(Error::invalid_request()) + } else { + Ok(Pubkey::new(&pubkey_vec)) + } +} + +#[cfg(test)] +mod tests {}