Skip to content
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

Merged
merged 4 commits into from
Aug 23, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Empty file added doc/json-rpc.md
Empty file.
21 changes: 19 additions & 2 deletions src/fullnode.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
use bank::Bank;
use broadcast_stage::BroadcastStage;
use crdt::{Crdt, NodeInfo, TestNode};
use drone::DRONE_PORT;
use entry::Entry;
use ledger::read_ledger;
use ncp::Ncp;
Expand Down Expand Up @@ -202,8 +203,16 @@ impl Fullnode {
);
thread_hdls.extend(rpu.thread_hdls());

let mut drone_addr = node.data.contact_info.tpu;
drone_addr.set_port(DRONE_PORT);
let rpc_addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(0, 0, 0, 0)), RPC_PORT);
let rpc_service = JsonRpcService::new(bank.clone(), rpc_addr, exit.clone());
let rpc_service = JsonRpcService::new(
&bank,
node.data.contact_info.tpu,
drone_addr,
rpc_addr,
exit.clone(),
);
thread_hdls.extend(rpc_service.thread_hdls());

let blob_recycler = BlobRecycler::default();
Expand Down Expand Up @@ -297,8 +306,16 @@ impl Fullnode {
);
thread_hdls.extend(rpu.thread_hdls());

let mut drone_addr = entry_point.contact_info.ncp;
drone_addr.set_port(DRONE_PORT);
let rpc_addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(0, 0, 0, 0)), RPC_PORT);
let rpc_service = JsonRpcService::new(bank.clone(), rpc_addr, exit.clone());
let rpc_service = JsonRpcService::new(
&bank,
node.data.contact_info.tpu,
drone_addr,
rpc_addr,
exit.clone(),
);
thread_hdls.extend(rpc_service.thread_hdls());

let blob_recycler = BlobRecycler::default();
Expand Down
97 changes: 72 additions & 25 deletions src/rpc.rs
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;

Expand All @@ -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 || {
Expand All @@ -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,
Expand Down Expand Up @@ -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 {}

Expand All @@ -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>;
}
}

Expand Down Expand Up @@ -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> {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use Result<()> if return Ok(false) is meaningless and unused.

Copy link
Contributor Author

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.

Copy link
Member

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"

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure, Result<bool> then.

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)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This seems error prone. Can you poll for the signature instead?

Copy link
Member

Choose a reason for hiding this comment

The 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

Copy link
Contributor Author

Choose a reason for hiding this comment

The 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.
Returning the tx signature from the drone is a better solution, but maybe a separate PR?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is something important blocked by this PR?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

/me is not blocked

Copy link
Contributor Author

@CriesofCarrots CriesofCarrots Aug 22, 2018

Choose a reason for hiding this comment

The 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.

Copy link
Contributor

Choose a reason for hiding this comment

The 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 {
Expand Down Expand Up @@ -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;

Expand All @@ -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":["{}"]}}"#,
Expand Down Expand Up @@ -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);
Expand All @@ -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);
Expand Down