diff --git a/src/lib.rs b/src/lib.rs index aaca38ad0b..3fe00be7a7 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -53,6 +53,7 @@ pub mod request_stage; pub mod result; pub mod retransmit_stage; pub mod rpc; +pub mod rpc_request; pub mod rpu; pub mod service; pub mod signature; diff --git a/src/rpc_request.rs b/src/rpc_request.rs new file mode 100644 index 0000000000..48e8d8f06d --- /dev/null +++ b/src/rpc_request.rs @@ -0,0 +1,189 @@ +use reqwest; +use reqwest::header::CONTENT_TYPE; +use serde_json::{self, Value}; +use std::{error, fmt}; + +pub enum RpcRequest { + ConfirmTransaction, + GetAccountInfo, + GetBalance, + GetFinality, + GetLastId, + GetTransactionCount, + RequestAirdrop, + SendTransaction, +} +impl RpcRequest { + pub fn make_rpc_request( + &self, + rpc_addr: &str, + id: u64, + params: Option, + ) -> Result> { + let request = self.build_request_json(id, params); + let client = reqwest::Client::new(); + let mut response = client + .post(rpc_addr) + .header(CONTENT_TYPE, "application/json") + .body(request.to_string()) + .send()?; + let json: Value = serde_json::from_str(&response.text()?)?; + if json["error"].is_object() { + Err(RpcError::RpcRequestError(format!( + "RPC Error response: {}", + serde_json::to_string(&json["error"]).unwrap() + )))? + } + Ok(json["result"].clone()) + } + fn build_request_json(&self, id: u64, params: Option) -> Value { + let jsonrpc = "2.0"; + let method = match self { + RpcRequest::ConfirmTransaction => "confirmTransaction", + RpcRequest::GetAccountInfo => "getAccountInfo", + RpcRequest::GetBalance => "getBalance", + RpcRequest::GetFinality => "getFinality", + RpcRequest::GetLastId => "getLastId", + RpcRequest::GetTransactionCount => "getTransactionCount", + RpcRequest::RequestAirdrop => "requestAirdrop", + RpcRequest::SendTransaction => "sendTransaction", + }; + let mut request = json!({ + "jsonrpc": jsonrpc, + "id": id, + "method": method, + }); + if let Some(param_string) = params { + request["params"] = json!(vec![param_string]); + } + request + } +} + +#[derive(Debug, Clone, PartialEq)] +pub enum RpcError { + RpcRequestError(String), +} + +impl fmt::Display for RpcError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "invalid") + } +} + +impl error::Error for RpcError { + fn description(&self) -> &str { + "invalid" + } + + fn cause(&self) -> Option<&error::Error> { + // Generic error, underlying cause isn't tracked. + None + } +} + +#[cfg(test)] +mod tests { + use super::*; + use jsonrpc_core::*; + use jsonrpc_http_server::*; + use serde_json::Number; + use std::net::{Ipv4Addr, SocketAddr}; + use std::sync::mpsc::channel; + use std::thread; + + #[test] + fn test_build_request_json() { + let test_request = RpcRequest::GetAccountInfo; + let request = test_request.build_request_json( + 1, + Some(json!("deadbeefXjn8o3yroDHxUtKsZZgoy4GPkPPXfouKNHhx")), + ); + assert_eq!(request["method"], "getAccountInfo"); + assert_eq!( + request["params"], + json!(vec!["deadbeefXjn8o3yroDHxUtKsZZgoy4GPkPPXfouKNHhx"]) + ); + + let test_request = RpcRequest::GetBalance; + let request = test_request.build_request_json( + 1, + Some(json!("deadbeefXjn8o3yroDHxUtKsZZgoy4GPkPPXfouKNHhx")), + ); + assert_eq!(request["method"], "getBalance"); + + let test_request = RpcRequest::GetFinality; + let request = test_request.build_request_json(1, None); + assert_eq!(request["method"], "getFinality"); + assert_eq!(request["params"], json!(null)); + + let test_request = RpcRequest::GetLastId; + let request = test_request.build_request_json(1, None); + assert_eq!(request["method"], "getLastId"); + + let test_request = RpcRequest::GetTransactionCount; + let request = test_request.build_request_json(1, None); + assert_eq!(request["method"], "getTransactionCount"); + + let test_request = RpcRequest::RequestAirdrop; + let request = test_request.build_request_json(1, None); + assert_eq!(request["method"], "requestAirdrop"); + + let test_request = RpcRequest::SendTransaction; + let request = test_request.build_request_json(1, None); + assert_eq!(request["method"], "sendTransaction"); + } + #[test] + fn test_make_rpc_request() { + let (sender, receiver) = channel(); + thread::spawn(move || { + let rpc_addr = socketaddr!(0, 0); + let mut io = IoHandler::default(); + // Successful request + io.add_method("getBalance", |_params: Params| { + Ok(Value::Number(Number::from(50))) + }); + // Failed request + io.add_method("getLastId", |params: Params| { + if params != Params::None { + Err(Error::invalid_request()) + } else { + Ok(Value::String( + "deadbeefXjn8o3yroDHxUtKsZZgoy4GPkPPXfouKNHhx".to_string(), + )) + } + }); + + let server = ServerBuilder::new(io) + .threads(1) + .cors(DomainsValidation::AllowOnly(vec![ + AccessControlAllowOrigin::Any, + ])).start_http(&rpc_addr) + .expect("Unable to start RPC server"); + sender.send(*server.address()).unwrap(); + server.wait(); + }); + + let rpc_addr = receiver.recv().unwrap(); + let rpc_addr = format!("http://{}", rpc_addr.to_string()); + + let balance = RpcRequest::GetBalance.make_rpc_request( + &rpc_addr, + 1, + Some(json!("deadbeefXjn8o3yroDHxUtKsZZgoy4GPkPPXfouKNHhx")), + ); + assert!(balance.is_ok()); + assert_eq!(balance.unwrap().as_i64().unwrap(), 50); + + let last_id = RpcRequest::GetLastId.make_rpc_request(&rpc_addr, 2, None); + assert!(last_id.is_ok()); + assert_eq!( + last_id.unwrap().as_str().unwrap(), + "deadbeefXjn8o3yroDHxUtKsZZgoy4GPkPPXfouKNHhx" + ); + + // Send erroneous parameter + let last_id = RpcRequest::GetLastId.make_rpc_request(&rpc_addr, 3, Some(json!("paramter"))); + assert_eq!(last_id.is_err(), true); + } +} diff --git a/src/wallet.rs b/src/wallet.rs index 1bb5de83be..529ee8605f 100644 --- a/src/wallet.rs +++ b/src/wallet.rs @@ -8,11 +8,10 @@ use crdt::NodeInfo; use drone::DroneRequest; use fullnode::Config; use hash::Hash; -use reqwest; -use reqwest::header::CONTENT_TYPE; use ring::rand::SystemRandom; use ring::signature::Ed25519KeyPair; -use serde_json::{self, Value}; +use rpc_request::RpcRequest; +use serde_json; use signature::{Keypair, KeypairUtil, Signature}; use solana_program_interface::pubkey::Pubkey; use std::fs::{self, File}; @@ -289,7 +288,7 @@ pub fn process_command(config: &WalletConfig) -> Result Result Result { println!("Balance requested..."); let params = json!(format!("{}", config.id.pubkey())); - let balance = WalletRpcRequest::GetBalance + let balance = RpcRequest::GetBalance .make_rpc_request(&config.rpc_addr, 1, Some(params))? .as_i64(); match balance { @@ -349,7 +348,7 @@ pub fn process_command(config: &WalletConfig) -> Result { let params = json!(format!("{}", signature)); - let confirmation = WalletRpcRequest::ConfirmTransaction + let confirmation = RpcRequest::ConfirmTransaction .make_rpc_request(&config.rpc_addr, 1, Some(params))? .as_bool(); match confirmation { @@ -487,7 +486,7 @@ pub fn process_command(config: &WalletConfig) -> Result { let params = json!(format!("{}", config.id.pubkey())); - let balance = WalletRpcRequest::GetBalance + let balance = RpcRequest::GetBalance .make_rpc_request(&config.rpc_addr, 1, Some(params))? .as_i64(); if let Some(0) = balance { @@ -506,7 +505,7 @@ pub fn process_command(config: &WalletConfig) -> Result Result> { Ok(serialized) } -pub enum WalletRpcRequest { - ConfirmTransaction, - GetAccountInfo, - GetBalance, - GetFinality, - GetLastId, - GetTransactionCount, - RequestAirdrop, - SendTransaction, -} -impl WalletRpcRequest { - fn make_rpc_request( - &self, - rpc_addr: &str, - id: u64, - params: Option, - ) -> Result> { - let jsonrpc = "2.0"; - let method = match self { - WalletRpcRequest::ConfirmTransaction => "confirmTransaction", - WalletRpcRequest::GetAccountInfo => "getAccountInfo", - WalletRpcRequest::GetBalance => "getBalance", - WalletRpcRequest::GetFinality => "getFinality", - WalletRpcRequest::GetLastId => "getLastId", - WalletRpcRequest::GetTransactionCount => "getTransactionCount", - WalletRpcRequest::RequestAirdrop => "requestAirdrop", - WalletRpcRequest::SendTransaction => "sendTransaction", - }; - let client = reqwest::Client::new(); - let mut request = json!({ - "jsonrpc": jsonrpc, - "id": id, - "method": method, - }); - if let Some(param_string) = params { - request["params"] = json!(vec![param_string]); - } - let mut response = client - .post(rpc_addr) - .header(CONTENT_TYPE, "application/json") - .body(request.to_string()) - .send()?; - let json: Value = serde_json::from_str(&response.text()?)?; - if json["error"].is_object() { - Err(WalletError::RpcRequestError(format!( - "RPC Error response: {}", - serde_json::to_string(&json["error"]).unwrap() - )))? - } - Ok(json["result"].clone()) - } -} - fn get_last_id(config: &WalletConfig) -> Result> { - let result = WalletRpcRequest::GetLastId.make_rpc_request(&config.rpc_addr, 1, None)?; + let result = RpcRequest::GetLastId.make_rpc_request(&config.rpc_addr, 1, None)?; if result.as_str().is_none() { Err(WalletError::RpcRequestError( "Received bad last_id".to_string(), @@ -653,7 +599,7 @@ fn serialize_and_send_tx( let serialized = serialize(tx).unwrap(); let params = json!(serialized); let signature = - WalletRpcRequest::SendTransaction.make_rpc_request(&config.rpc_addr, 2, Some(params))?; + RpcRequest::SendTransaction.make_rpc_request(&config.rpc_addr, 2, Some(params))?; if signature.as_str().is_none() { Err(WalletError::RpcRequestError( "Received result of an unexpected type".to_string(), @@ -672,6 +618,7 @@ mod tests { use fullnode::Fullnode; use ledger::LedgerWriter; use mint::Mint; + use serde_json::Value; use signature::{read_keypair, read_pkcs8, Keypair, KeypairUtil}; use std::fs::remove_dir_all; use std::sync::mpsc::channel; @@ -1094,7 +1041,7 @@ mod tests { let signature = request_airdrop(&drone_addr, &bob_pubkey, 50); assert!(signature.is_ok()); let params = json!(format!("{}", signature.unwrap())); - let confirmation = WalletRpcRequest::ConfirmTransaction + let confirmation = RpcRequest::ConfirmTransaction .make_rpc_request(&rpc_addr, 1, Some(params)) .unwrap() .as_bool() @@ -1189,21 +1136,21 @@ mod tests { let process_id = Pubkey::new(&process_id_vec); let params = json!(format!("{}", config_payer.id.pubkey())); - let config_payer_balance = WalletRpcRequest::GetBalance + let config_payer_balance = RpcRequest::GetBalance .make_rpc_request(&config_payer.rpc_addr, 1, Some(params)) .unwrap() .as_i64() .unwrap(); assert_eq!(config_payer_balance, 39); let params = json!(format!("{}", process_id)); - let contract_balance = WalletRpcRequest::GetBalance + let contract_balance = RpcRequest::GetBalance .make_rpc_request(&config_payer.rpc_addr, 1, Some(params)) .unwrap() .as_i64() .unwrap(); assert_eq!(contract_balance, 11); let params = json!(format!("{}", bob_pubkey)); - let recipient_balance = WalletRpcRequest::GetBalance + let recipient_balance = RpcRequest::GetBalance .make_rpc_request(&config_payer.rpc_addr, 1, Some(params)) .unwrap() .as_i64() @@ -1216,21 +1163,21 @@ mod tests { assert!(sig_response.is_ok()); let params = json!(format!("{}", config_payer.id.pubkey())); - let config_payer_balance = WalletRpcRequest::GetBalance + let config_payer_balance = RpcRequest::GetBalance .make_rpc_request(&config_payer.rpc_addr, 1, Some(params)) .unwrap() .as_i64() .unwrap(); assert_eq!(config_payer_balance, 39); let params = json!(format!("{}", process_id)); - let contract_balance = WalletRpcRequest::GetBalance + let contract_balance = RpcRequest::GetBalance .make_rpc_request(&config_payer.rpc_addr, 1, Some(params)) .unwrap() .as_i64() .unwrap(); assert_eq!(contract_balance, 1); let params = json!(format!("{}", bob_pubkey)); - let recipient_balance = WalletRpcRequest::GetBalance + let recipient_balance = RpcRequest::GetBalance .make_rpc_request(&config_payer.rpc_addr, 1, Some(params)) .unwrap() .as_i64() @@ -1308,21 +1255,21 @@ mod tests { let process_id = Pubkey::new(&process_id_vec); let params = json!(format!("{}", config_payer.id.pubkey())); - let config_payer_balance = WalletRpcRequest::GetBalance + let config_payer_balance = RpcRequest::GetBalance .make_rpc_request(&config_payer.rpc_addr, 1, Some(params)) .unwrap() .as_i64() .unwrap(); assert_eq!(config_payer_balance, 39); let params = json!(format!("{}", process_id)); - let contract_balance = WalletRpcRequest::GetBalance + let contract_balance = RpcRequest::GetBalance .make_rpc_request(&config_payer.rpc_addr, 1, Some(params)) .unwrap() .as_i64() .unwrap(); assert_eq!(contract_balance, 11); let params = json!(format!("{}", bob_pubkey)); - let recipient_balance = WalletRpcRequest::GetBalance + let recipient_balance = RpcRequest::GetBalance .make_rpc_request(&config_payer.rpc_addr, 1, Some(params)) .unwrap() .as_i64() @@ -1335,21 +1282,21 @@ mod tests { assert!(sig_response.is_ok()); let params = json!(format!("{}", config_payer.id.pubkey())); - let config_payer_balance = WalletRpcRequest::GetBalance + let config_payer_balance = RpcRequest::GetBalance .make_rpc_request(&config_payer.rpc_addr, 1, Some(params)) .unwrap() .as_i64() .unwrap(); assert_eq!(config_payer_balance, 39); let params = json!(format!("{}", process_id)); - let contract_balance = WalletRpcRequest::GetBalance + let contract_balance = RpcRequest::GetBalance .make_rpc_request(&config_payer.rpc_addr, 1, Some(params)) .unwrap() .as_i64() .unwrap(); assert_eq!(contract_balance, 1); let params = json!(format!("{}", bob_pubkey)); - let recipient_balance = WalletRpcRequest::GetBalance + let recipient_balance = RpcRequest::GetBalance .make_rpc_request(&config_payer.rpc_addr, 1, Some(params)) .unwrap() .as_i64() @@ -1427,21 +1374,21 @@ mod tests { let process_id = Pubkey::new(&process_id_vec); let params = json!(format!("{}", config_payer.id.pubkey())); - let config_payer_balance = WalletRpcRequest::GetBalance + let config_payer_balance = RpcRequest::GetBalance .make_rpc_request(&config_payer.rpc_addr, 1, Some(params)) .unwrap() .as_i64() .unwrap(); assert_eq!(config_payer_balance, 39); let params = json!(format!("{}", process_id)); - let contract_balance = WalletRpcRequest::GetBalance + let contract_balance = RpcRequest::GetBalance .make_rpc_request(&config_payer.rpc_addr, 1, Some(params)) .unwrap() .as_i64() .unwrap(); assert_eq!(contract_balance, 11); let params = json!(format!("{}", bob_pubkey)); - let recipient_balance = WalletRpcRequest::GetBalance + let recipient_balance = RpcRequest::GetBalance .make_rpc_request(&config_payer.rpc_addr, 1, Some(params)) .unwrap() .as_i64() @@ -1454,21 +1401,21 @@ mod tests { assert!(sig_response.is_ok()); let params = json!(format!("{}", config_payer.id.pubkey())); - let config_payer_balance = WalletRpcRequest::GetBalance + let config_payer_balance = RpcRequest::GetBalance .make_rpc_request(&config_payer.rpc_addr, 1, Some(params)) .unwrap() .as_i64() .unwrap(); assert_eq!(config_payer_balance, 49); let params = json!(format!("{}", process_id)); - let contract_balance = WalletRpcRequest::GetBalance + let contract_balance = RpcRequest::GetBalance .make_rpc_request(&config_payer.rpc_addr, 1, Some(params)) .unwrap() .as_i64() .unwrap(); assert_eq!(contract_balance, 1); let params = json!(format!("{}", bob_pubkey)); - let recipient_balance = WalletRpcRequest::GetBalance + let recipient_balance = RpcRequest::GetBalance .make_rpc_request(&config_payer.rpc_addr, 1, Some(params)) .unwrap() .as_i64()