From fc1dbddd93475b80956b04261e5e21961013479f Mon Sep 17 00:00:00 2001 From: Tyera Eulberg Date: Fri, 10 Aug 2018 17:05:23 -0600 Subject: [PATCH] Implement json-rpc functionality --- Cargo.toml | 6 +++ src/bin/json-rpc.rs | 80 +++++++++++++++++++++++++++++++ src/lib.rs | 4 ++ src/rpc.rs | 114 ++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 204 insertions(+) create mode 100644 src/bin/json-rpc.rs create mode 100644 src/rpc.rs diff --git a/Cargo.toml b/Cargo.toml index a178ee014..894224baf 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -53,6 +53,10 @@ path = "src/bin/keygen.rs" name = "solana-wallet" path = "src/bin/wallet.rs" +[[bin]] +name = "solana-json-rpc" +path = "src/bin/json-rpc.rs" + [badges] codecov = { repository = "solana-labs/solana", branch = "master", service = "github" } @@ -75,6 +79,8 @@ futures = "0.1.21" generic-array = { version = "0.11.1", default-features = false, features = ["serde"] } getopts = "0.2" influx_db_client = "0.3.4" +jsonrpc-http-server = { git = "https://github.com/paritytech/jsonrpc" } +jsonrpc-macros = { git = "https://github.com/paritytech/jsonrpc" } itertools = "0.7.8" libc = "0.2.1" log = "0.4.2" diff --git a/src/bin/json-rpc.rs b/src/bin/json-rpc.rs new file mode 100644 index 000000000..8d42d4cf7 --- /dev/null +++ b/src/bin/json-rpc.rs @@ -0,0 +1,80 @@ +//! The `json-rpc` service launches an HTTP server to listen for +//! Solana rpc requests. + +#[macro_use] +extern crate clap; +extern crate dirs; +extern crate jsonrpc_http_server; +extern crate solana; + +use clap::{App, Arg}; +use jsonrpc_http_server::jsonrpc_core::*; +use jsonrpc_http_server::*; +use solana::crdt::NodeInfo; +use solana::fullnode::Config; +use solana::rpc::*; +use std::fs::File; +use std::net::{IpAddr, Ipv4Addr, SocketAddr}; + +fn main() { + let matches = App::new("json-rpc") + .version(crate_version!()) + .arg( + Arg::with_name("leader") + .short("l") + .long("leader") + .value_name("PATH") + .takes_value(true) + .help("/path/to/leader.json"), + ) + .arg( + Arg::with_name("keypair") + .short("k") + .long("keypair") + .value_name("PATH") + .takes_value(true) + .required(true) + .help("/path/to/id.json"), + ) + .get_matches(); + let leader: NodeInfo; + if let Some(l) = matches.value_of("leader") { + leader = read_leader(l).node_info; + } else { + let server_addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(0, 0, 0, 0)), 8000); + leader = NodeInfo::new_leader(&server_addr); + }; + + let id_path: String = matches + .value_of("keypair") + .expect("Unable to parse keypair path") + .to_string(); + + let mut io = MetaIoHandler::default(); + let rpc = RpcSolImpl; + io.extend_with(rpc.to_delegate()); + + let rpc_addr = format!("0.0.0.0:{}", RPC_PORT); + let server = ServerBuilder::new(io) + .meta_extractor(move |_req: &hyper::Request| Meta { + leader: Some(leader.clone()), + keypair_location: Some(id_path.clone()), + }) + .threads(4) + .cors(DomainsValidation::AllowOnly(vec![ + AccessControlAllowOrigin::Any, + ])) + .start_http( + &rpc_addr + .parse() + .expect("Unable to parse RPC server address"), + ) + .expect("Unable to start RPC server"); + + server.wait(); +} + +fn read_leader(path: &str) -> Config { + let file = File::open(path).unwrap_or_else(|_| panic!("file not found: {}", path)); + serde_json::from_reader(file).unwrap_or_else(|_| panic!("failed to parse {}", path)) +} diff --git a/src/lib.rs b/src/lib.rs index 1dd4b3053..29512d32b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -41,6 +41,7 @@ pub mod request_processor; pub mod request_stage; pub mod result; pub mod retransmit_stage; +pub mod rpc; pub mod rpu; pub mod service; pub mod signature; @@ -63,6 +64,9 @@ extern crate byteorder; extern crate chrono; extern crate generic_array; extern crate itertools; +#[macro_use] +extern crate jsonrpc_macros; +extern crate jsonrpc_http_server; extern crate libc; #[macro_use] extern crate log; diff --git a/src/rpc.rs b/src/rpc.rs new file mode 100644 index 000000000..bde792581 --- /dev/null +++ b/src/rpc.rs @@ -0,0 +1,114 @@ +//! The `rpc` module implements the Solana rpc interface. + +use bs58; +use client::mk_client; +use crdt::NodeInfo; +use jsonrpc_http_server::jsonrpc_core::*; +use signature::{read_keypair, KeypairUtil, Pubkey, Signature}; +use std::mem; + +pub const RPC_PORT: u16 = 8899; + +#[derive(Clone)] +pub struct Meta { + pub leader: Option, + pub keypair_location: Option, +} +impl Metadata for Meta {} +impl Default for Meta { + fn default() -> Self { + Meta { + leader: None, + keypair_location: None, + } + } +} + +build_rpc_trait! { + pub trait RpcSol { + type Metadata; + + #[rpc(meta, name = "solana_getAddress")] + fn address(&self, Self::Metadata) -> Result; + + #[rpc(meta, name = "solana_confirmTransaction")] + fn confirm_transaction(&self, Self::Metadata, String) -> Result; + + #[rpc(meta, name = "solana_getBalance")] + fn get_balance(&self, Self::Metadata, String) -> Result; + + #[rpc(meta, name = "solana_getTransactionCount")] + fn get_transaction_count(&self, Self::Metadata) -> Result; + + #[rpc(meta, name = "solana_sendTransaction")] + fn send_transaction(&self, Self::Metadata, String, i64) -> Result; + } +} + +pub struct RpcSolImpl; +impl RpcSol for RpcSolImpl { + type Metadata = Meta; + + fn address(&self, meta: Self::Metadata) -> Result { + let client_keypair = read_keypair(&meta.keypair_location.unwrap()); + Ok(bs58::encode(client_keypair.unwrap().pubkey()).into_string()) + } + fn confirm_transaction(&self, meta: Self::Metadata, id: String) -> Result { + let signature_vec = bs58::decode(id) + .into_vec() + .expect("base58-encoded public key"); + + if signature_vec.len() != mem::size_of::() { + Err(Error::invalid_request()) + } else { + let signature = Signature::new(&signature_vec); + + let mut client = mk_client(&meta.leader.unwrap()); + + let confirmation = client.check_signature(&signature); + Ok(confirmation) + } + } + fn get_balance(&self, meta: Self::Metadata, id: String) -> Result { + let pubkey_vec = bs58::decode(id) + .into_vec() + .expect("base58-encoded public key"); + + if pubkey_vec.len() != mem::size_of::() { + Err(Error::invalid_request()) + } else { + let pubkey = Pubkey::new(&pubkey_vec); + + let mut client = mk_client(&meta.leader.unwrap()); + + let balance = client.poll_get_balance(&pubkey); + match balance { + Ok(balance) => Ok(balance), + Err(_) => Err(Error::new(ErrorCode::ServerError(-32001))), + } + } + } + fn get_transaction_count(&self, meta: Self::Metadata) -> Result { + let mut client = mk_client(&meta.leader.unwrap()); + let tx_count = client.transaction_count(); + Ok(tx_count) + } + fn send_transaction(&self, meta: Self::Metadata, to: String, tokens: i64) -> Result { + let client_keypair = read_keypair(&meta.keypair_location.unwrap()).unwrap(); + let mut client = mk_client(&meta.leader.unwrap()); + let last_id = client.get_last_id(); + let to_pubkey_vec = bs58::decode(to) + .into_vec() + .expect("base58-encoded public key"); + + if to_pubkey_vec.len() != mem::size_of::() { + Err(Error::invalid_request()) + } else { + let to_pubkey = Pubkey::new(&to_pubkey_vec); + let signature = client + .transfer(tokens, &client_keypair, to_pubkey, &last_id) + .unwrap(); + Ok(bs58::encode(signature).into_string()) + } + } +}