-
Notifications
You must be signed in to change notification settings - Fork 4.4k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add requestAirdrop and sendTransaction endpoints to json-rpc api #1041
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,16 +1,21 @@ | ||
//! The `rpc` module implements the Solana RPC interface. | ||
|
||
use bank::Bank; | ||
use bincode::deserialize; | ||
use bs58; | ||
use jsonrpc_core::*; | ||
use jsonrpc_http_server::*; | ||
use service::Service; | ||
use signature::{Pubkey, Signature}; | ||
use std::mem; | ||
use std::net::SocketAddr; | ||
use std::net::{SocketAddr, UdpSocket}; | ||
use std::sync::atomic::{AtomicBool, Ordering}; | ||
use std::sync::Arc; | ||
use std::thread::{self, Builder, JoinHandle}; | ||
use std::thread::{self, sleep, Builder, JoinHandle}; | ||
use std::time::Duration; | ||
use std::time::Instant; | ||
use transaction::Transaction; | ||
use wallet::request_airdrop; | ||
|
||
pub const RPC_PORT: u16 = 8899; | ||
|
||
|
@@ -19,8 +24,14 @@ pub struct JsonRpcService { | |
} | ||
|
||
impl JsonRpcService { | ||
pub fn new(bank: Arc<Bank>, rpc_addr: SocketAddr, exit: Arc<AtomicBool>) -> Self { | ||
let request_processor = JsonRpcRequestProcessor::new(bank); | ||
pub fn new( | ||
bank: &Arc<Bank>, | ||
transactions_addr: SocketAddr, | ||
drone_addr: SocketAddr, | ||
rpc_addr: SocketAddr, | ||
exit: Arc<AtomicBool>, | ||
) -> Self { | ||
let request_processor = JsonRpcRequestProcessor::new(bank.clone()); | ||
let thread_hdl = Builder::new() | ||
.name("solana-jsonrpc".to_string()) | ||
.spawn(move || { | ||
|
@@ -31,6 +42,8 @@ impl JsonRpcService { | |
let server = | ||
ServerBuilder::with_meta_extractor(io, move |_req: &hyper::Request| Meta { | ||
request_processor: request_processor.clone(), | ||
transactions_addr, | ||
drone_addr, | ||
}).threads(4) | ||
.cors(DomainsValidation::AllowOnly(vec![ | ||
AccessControlAllowOrigin::Any, | ||
|
@@ -66,6 +79,8 @@ impl Service for JsonRpcService { | |
#[derive(Clone)] | ||
pub struct Meta { | ||
pub request_processor: JsonRpcRequestProcessor, | ||
pub transactions_addr: SocketAddr, | ||
pub drone_addr: SocketAddr, | ||
} | ||
impl Metadata for Meta {} | ||
|
||
|
@@ -88,8 +103,11 @@ build_rpc_trait! { | |
#[rpc(meta, name = "getTransactionCount")] | ||
fn get_transaction_count(&self, Self::Metadata) -> Result<u64>; | ||
|
||
// #[rpc(meta, name = "sendTransaction")] | ||
// fn send_transaction(&self, Self::Metadata, String, i64) -> Result<String>; | ||
#[rpc(meta, name= "requestAirdrop")] | ||
fn request_airdrop(&self, Self::Metadata, String, u64) -> Result<bool>; | ||
|
||
#[rpc(meta, name = "sendTransaction")] | ||
fn send_transaction(&self, Self::Metadata, Vec<u8>) -> Result<String>; | ||
} | ||
} | ||
|
||
|
@@ -126,24 +144,42 @@ impl RpcSol for RpcSolImpl { | |
fn get_transaction_count(&self, meta: Self::Metadata) -> Result<u64> { | ||
meta.request_processor.get_transaction_count() | ||
} | ||
// fn send_transaction(&self, meta: Self::Metadata, to: String, tokens: i64) -> Result<String> { | ||
// 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::<Pubkey>() { | ||
// 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()) | ||
// } | ||
// } | ||
fn request_airdrop(&self, meta: Self::Metadata, id: String, tokens: u64) -> Result<bool> { | ||
let pubkey_vec = bs58::decode(id) | ||
.into_vec() | ||
.map_err(|_| Error::invalid_request())?; | ||
if pubkey_vec.len() != mem::size_of::<Pubkey>() { | ||
return Err(Error::invalid_request()); | ||
} | ||
let pubkey = Pubkey::new(&pubkey_vec); | ||
let previous_balance = meta | ||
.request_processor | ||
.get_balance(pubkey) | ||
.map_err(|_| Error::internal_error())?; | ||
request_airdrop(&meta.drone_addr, &pubkey, tokens).map_err(|_| Error::internal_error())?; | ||
let now = Instant::now(); | ||
let mut balance; | ||
loop { | ||
balance = meta | ||
.request_processor | ||
.get_balance(pubkey) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This seems error prone. Can you poll for the signature instead? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. How about we just make the caller of the RPC API poll their balance? But yeah, if we could get the tx signature back from the drone then passing it back here would be super There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yeah, I was pretty ambivalent about putting this in the request_airdrop handler vs. polling on the client side. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is something important blocked by this PR? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. /me is not blocked There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm not in a hurry to push this through, but reworking the drone to send data responses seems like a distinct chunk of work to me. Will have implications for the wallet CLI and fullnode instantiation as well. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Okay, no problem. Let's get this one merged. |
||
.map_err(|_| Error::internal_error())?; | ||
if balance > previous_balance { | ||
return Ok(true); | ||
} else if now.elapsed().as_secs() > 5 { | ||
return Err(Error::internal_error()); | ||
} | ||
sleep(Duration::from_millis(100)); | ||
} | ||
} | ||
fn send_transaction(&self, meta: Self::Metadata, data: Vec<u8>) -> Result<String> { | ||
let tx: Transaction = deserialize(&data).map_err(|_| Error::invalid_request())?; | ||
let transactions_socket = UdpSocket::bind("0.0.0.0:0").unwrap(); | ||
transactions_socket | ||
.send_to(&data, &meta.transactions_addr) | ||
.map_err(|_| Error::internal_error())?; | ||
Ok(bs58::encode(tx.signature).into_string()) | ||
} | ||
} | ||
#[derive(Clone)] | ||
pub struct JsonRpcRequestProcessor { | ||
|
@@ -182,6 +218,7 @@ mod tests { | |
use jsonrpc_core::Response; | ||
use mint::Mint; | ||
use signature::{Keypair, KeypairUtil}; | ||
use std::net::{IpAddr, Ipv4Addr, SocketAddr}; | ||
use std::sync::Arc; | ||
use transaction::Transaction; | ||
|
||
|
@@ -196,11 +233,17 @@ mod tests { | |
bank.process_transaction(&tx).expect("process transaction"); | ||
|
||
let request_processor = JsonRpcRequestProcessor::new(Arc::new(bank)); | ||
let transactions_addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(0, 0, 0, 0)), 0); | ||
let drone_addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(0, 0, 0, 0)), 0); | ||
|
||
let mut io = MetaIoHandler::default(); | ||
let rpc = RpcSolImpl; | ||
io.extend_with(rpc.to_delegate()); | ||
let meta = Meta { request_processor }; | ||
let meta = Meta { | ||
request_processor, | ||
transactions_addr, | ||
drone_addr, | ||
}; | ||
|
||
let req = format!( | ||
r#"{{"jsonrpc":"2.0","id":1,"method":"getBalance","params":["{}"]}}"#, | ||
|
@@ -236,6 +279,8 @@ mod tests { | |
let req = r#"{"jsonrpc":"2.0","id":1,"method":"confirmTransaction","params":[1234567890]}"#; | ||
let meta = Meta { | ||
request_processor: JsonRpcRequestProcessor::new(Arc::new(bank)), | ||
transactions_addr: SocketAddr::new(IpAddr::V4(Ipv4Addr::new(0, 0, 0, 0)), 0), | ||
drone_addr: SocketAddr::new(IpAddr::V4(Ipv4Addr::new(0, 0, 0, 0)), 0), | ||
}; | ||
|
||
let res = io.handle_request_sync(req, meta); | ||
|
@@ -259,6 +304,8 @@ mod tests { | |
r#"{"jsonrpc":"2.0","id":1,"method":"confirmTransaction","params":["a1b2c3d4e5"]}"#; | ||
let meta = Meta { | ||
request_processor: JsonRpcRequestProcessor::new(Arc::new(bank)), | ||
transactions_addr: SocketAddr::new(IpAddr::V4(Ipv4Addr::new(0, 0, 0, 0)), 0), | ||
drone_addr: SocketAddr::new(IpAddr::V4(Ipv4Addr::new(0, 0, 0, 0)), 0), | ||
}; | ||
|
||
let res = io.handle_request_sync(req, meta); | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Use
Result<()>
ifreturn Ok(false)
is meaningless and unused.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If I make that change, the RPC response will look like this:
{"jsonrpc":"2.0","result":null,"id":1}
That null seems wonky to me.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
hmm, yeah
"result": true
would be nicer. "I successfully requested an airdrop for you"There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sure,
Result<bool>
then.