diff --git a/ci/docker-solana/entrypoint.sh b/ci/docker-solana/entrypoint.sh index 4b72220671..84b15967f3 100755 --- a/ci/docker-solana/entrypoint.sh +++ b/ci/docker-solana/entrypoint.sh @@ -10,7 +10,7 @@ solana-keygen -o /config/drone-keypair.json solana-fullnode-config --keypair=/config/leader-keypair.json -l > /config/leader-config.json solana-genesis --num_tokens 1000000000 --mint /config/drone-keypair.json --bootstrap_leader /config/leader-config.json --ledger /ledger -solana-drone --keypair /config/drone-keypair.json --network 127.0.0.1:8001 & +solana-drone --keypair /config/drone-keypair.json & drone=$! solana-fullnode --identity /config/leader-config.json --ledger /ledger/ --rpc 8899 & fullnode=$! diff --git a/multinode-demo/drone.sh b/multinode-demo/drone.sh index ad111b5789..ad865b0302 100755 --- a/multinode-demo/drone.sh +++ b/multinode-demo/drone.sh @@ -12,16 +12,13 @@ usage() { echo "$*" echo fi - echo "usage: $0 [network entry point]" + echo "usage: $0]" echo - echo " Run an airdrop drone for the specified network" + echo " Run an airdrop drone" echo exit 1 } -read -r _ leader_address shift < <(find_leader "${@:1:1}") -shift "$shift" - [[ -f "$SOLANA_CONFIG_PRIVATE_DIR"/mint.json ]] || { echo "$SOLANA_CONFIG_PRIVATE_DIR/mint.json not found, create it by running:" echo @@ -34,7 +31,6 @@ set -ex trap 'kill "$pid" && wait "$pid"' INT TERM $solana_drone \ --keypair "$SOLANA_CONFIG_PRIVATE_DIR"/mint.json \ - --network "$leader_address" \ > >($drone_logger) 2>&1 & pid=$! wait "$pid" diff --git a/src/bin/bench-tps.rs b/src/bin/bench-tps.rs index 7f16e8d544..804cd63c49 100644 --- a/src/bin/bench-tps.rs +++ b/src/bin/bench-tps.rs @@ -16,7 +16,7 @@ use rand::{thread_rng, Rng}; use rayon::prelude::*; use solana::client::mk_client; use solana::cluster_info::{ClusterInfo, NodeInfo}; -use solana::drone::DRONE_PORT; +use solana::drone::{request_airdrop_transaction, DRONE_PORT}; use solana::hash::Hash; use solana::logger; use solana::metrics; @@ -28,7 +28,6 @@ use solana::thin_client::{poll_gossip_for_leader, ThinClient}; use solana::timing::timestamp; use solana::timing::{duration_as_ms, duration_as_s}; use solana::transaction::Transaction; -use solana::wallet::request_airdrop; use solana::window::default_window; use std::cmp; use std::collections::VecDeque; @@ -393,27 +392,26 @@ fn airdrop_tokens(client: &mut ThinClient, leader: &NodeInfo, id: &Keypair, tx_c id.pubkey(), ); - if let Err(e) = request_airdrop(&drone_addr, &id.pubkey(), airdrop_amount as u64) { - panic!( - "Error requesting airdrop: {:?} to addr: {:?} amount: {}", - e, drone_addr, airdrop_amount - ); - } - - // TODO: return airdrop Result from Drone instead of polling the - // network - let mut current_balance = starting_balance; - for _ in 0..20 { - sleep(Duration::from_millis(500)); - current_balance = client.poll_get_balance(&id.pubkey()).unwrap_or_else(|e| { - println!("airdrop error {}", e); - starting_balance - }); - if starting_balance != current_balance { - break; + let last_id = client.get_last_id(); + match request_airdrop_transaction(&drone_addr, &id.pubkey(), airdrop_amount, last_id) { + Ok(transaction) => { + let signature = client.transfer_signed(&transaction).unwrap(); + client.poll_for_signature(&signature).unwrap(); } - println!("current balance {}...", current_balance); - } + Err(err) => { + panic!( + "Error requesting airdrop: {:?} to addr: {:?} amount: {}", + err, drone_addr, airdrop_amount + ); + } + }; + + let current_balance = client.poll_get_balance(&id.pubkey()).unwrap_or_else(|e| { + println!("airdrop error {}", e); + starting_balance + }); + println!("current balance {}...", current_balance); + metrics_submit_token_balance(current_balance); if current_balance - starting_balance != airdrop_amount { println!( diff --git a/src/bin/drone.rs b/src/bin/drone.rs index 1bd7a9f37d..290886adb6 100644 --- a/src/bin/drone.rs +++ b/src/bin/drone.rs @@ -1,4 +1,5 @@ extern crate bincode; +extern crate byteorder; extern crate bytes; #[macro_use] extern crate clap; @@ -9,6 +10,7 @@ extern crate tokio; extern crate tokio_codec; use bincode::{deserialize, serialize}; +use byteorder::{ByteOrder, LittleEndian}; use bytes::Bytes; use clap::{App, Arg}; use solana::drone::{Drone, DroneRequest, DRONE_PORT}; @@ -18,7 +20,6 @@ use solana::signature::read_keypair; use std::error; use std::io; use std::net::{Ipv4Addr, SocketAddr}; -use std::process::exit; use std::sync::{Arc, Mutex}; use std::thread; use tokio::net::TcpListener; @@ -41,14 +42,6 @@ fn main() -> Result<(), Box> { let matches = App::new("drone") .version(crate_version!()) .arg( - Arg::with_name("network") - .short("n") - .long("network") - .value_name("HOST:PORT") - .takes_value(true) - .required(true) - .help("Rendezvous with the network at this gossip entry point"), - ).arg( Arg::with_name("keypair") .short("k") .long("keypair") @@ -70,15 +63,6 @@ fn main() -> Result<(), Box> { .help("Request limit for time slice"), ).get_matches(); - let network = matches - .value_of("network") - .unwrap() - .parse() - .unwrap_or_else(|e| { - eprintln!("failed to parse network: {}", e); - exit(1) - }); - let mint_keypair = read_keypair(matches.value_of("keypair").unwrap()).expect("failed to read client keypair"); @@ -99,8 +83,6 @@ fn main() -> Result<(), Box> { let drone = Arc::new(Mutex::new(Drone::new( mint_keypair, - drone_addr, - network, time_slice, request_cap, ))); @@ -131,23 +113,34 @@ fn main() -> Result<(), Box> { )) })?; - println!("Airdrop requested..."); + println!("Airdrop transaction requested...{:?}", req); // let res = drone2.lock().unwrap().check_rate_limit(client_ip); - let res1 = drone2.lock().unwrap().send_airdrop(req); - match res1 { - Ok(_) => println!("Airdrop sent!"), - Err(_) => println!("Request limit reached for this time slice"), + let res = drone2.lock().unwrap().build_airdrop_transaction(req); + match res { + Ok(tx) => { + let response_vec = serialize(&tx).or_else(|err| { + Err(io::Error::new( + io::ErrorKind::Other, + format!("deserialize packet in drone: {:?}", err), + )) + })?; + + let mut response_vec_with_length = vec![0; 2]; + LittleEndian::write_u16( + &mut response_vec_with_length, + response_vec.len() as u16, + ); + response_vec_with_length.extend_from_slice(&response_vec); + + let response_bytes = Bytes::from(response_vec_with_length); + println!("Airdrop transaction granted"); + Ok(response_bytes) + } + Err(err) => { + println!("Airdrop transaction failed: {:?}", err); + Err(err) + } } - let response = res1?; - println!("Airdrop tx signature: {:?}", response); - let response_vec = serialize(&response).or_else(|err| { - Err(io::Error::new( - io::ErrorKind::Other, - format!("serialize signature in drone: {:?}", err), - )) - })?; - let response_bytes = Bytes::from(response_vec.clone()); - Ok(response_bytes) }); let server = writer .send_all(processor.or_else(|err| { diff --git a/src/bin/fullnode.rs b/src/bin/fullnode.rs index f385cf4d4e..a9b96c561c 100644 --- a/src/bin/fullnode.rs +++ b/src/bin/fullnode.rs @@ -10,7 +10,7 @@ extern crate solana; use clap::{App, Arg}; use solana::client::mk_client; use solana::cluster_info::{Node, FULLNODE_PORT_RANGE}; -use solana::drone::DRONE_PORT; +use solana::drone::{request_airdrop_transaction, DRONE_PORT}; use solana::fullnode::{Config, Fullnode, FullnodeReturnType}; use solana::leader_scheduler::LeaderScheduler; use solana::logger; @@ -19,7 +19,6 @@ use solana::netutil::find_available_port_in_range; use solana::signature::{Keypair, KeypairUtil}; use solana::thin_client::poll_gossip_for_leader; use solana::vote_program::VoteProgram; -use solana::wallet::request_airdrop; use std::fs::File; use std::net::{Ipv4Addr, SocketAddr}; use std::process::exit; @@ -159,8 +158,13 @@ fn main() { info!("requesting airdrop from {}", drone_addr); loop { - if request_airdrop(&drone_addr, &pubkey, 50).is_ok() { - break; + let last_id = client.get_last_id(); + if let Ok(transaction) = request_airdrop_transaction(&drone_addr, &pubkey, 50, last_id) + { + let signature = client.transfer_signed(&transaction).unwrap(); + if client.poll_for_signature(&signature).is_ok() { + break; + } } info!( "airdrop request, is the drone address correct {:?}, drone running?", diff --git a/src/bin/replicator.rs b/src/bin/replicator.rs index 23aa3fe896..8f55aee42a 100644 --- a/src/bin/replicator.rs +++ b/src/bin/replicator.rs @@ -9,7 +9,7 @@ use clap::{App, Arg}; use solana::chacha::{chacha_cbc_encrypt_file, CHACHA_BLOCK_SIZE}; use solana::client::mk_client; use solana::cluster_info::Node; -use solana::drone::DRONE_PORT; +use solana::drone::{request_airdrop_transaction, DRONE_PORT}; use solana::fullnode::Config; use solana::ledger::LEDGER_DATA_FILE; use solana::logger; @@ -17,7 +17,6 @@ use solana::replicator::{sample_file, Replicator}; use solana::signature::{Keypair, KeypairUtil}; use solana::storage_transaction::StorageTransaction; use solana::transaction::Transaction; -use solana::wallet::request_airdrop; use std::fs::File; use std::net::{Ipv4Addr, SocketAddr}; use std::path::Path; @@ -132,9 +131,12 @@ fn main() { let mut drone_addr = leader_info.tpu; drone_addr.set_port(DRONE_PORT); let airdrop_amount = 5; - if let Err(e) = request_airdrop(&drone_addr, &keypair.pubkey(), airdrop_amount) { - panic!("couldn't get airdrop {}: {}!", airdrop_amount, e); - } + let last_id = client.get_last_id(); + let transaction = + request_airdrop_transaction(&drone_addr, &keypair.pubkey(), airdrop_amount, last_id) + .unwrap(); + let signature = client.transfer_signed(&transaction).unwrap(); + client.poll_for_signature(&signature).unwrap(); match sample_file(&ledger_data_file_encrypted, &sampling_offsets) { Ok(hash) => { diff --git a/src/drone.rs b/src/drone.rs index 5b895845bc..3a72225545 100644 --- a/src/drone.rs +++ b/src/drone.rs @@ -5,20 +5,22 @@ //! and (to come) an IP rate limit. use bincode::{deserialize, serialize}; +use byteorder::{ByteOrder, LittleEndian}; use bytes::Bytes; +use hash::Hash; use influx_db_client as influxdb; use metrics; -use signature::{Keypair, Signature}; +use packet::PACKET_DATA_SIZE; +use signature::Keypair; use solana_sdk::pubkey::Pubkey; use std::io; use std::io::{Error, ErrorKind}; -use std::net::{IpAddr, Ipv4Addr, SocketAddr, UdpSocket}; +use std::net::{IpAddr, Ipv4Addr, SocketAddr, TcpStream}; use std::sync::mpsc::Sender; use std::sync::{Arc, Mutex}; use std::thread; use std::time::Duration; use system_transaction::SystemTransaction; -use thin_client::{poll_gossip_for_leader, ThinClient}; use tokio; use tokio::net::TcpListener; use tokio::prelude::*; @@ -32,16 +34,15 @@ pub const DRONE_PORT: u16 = 9900; #[derive(Serialize, Deserialize, Debug, Clone, Copy)] pub enum DroneRequest { GetAirdrop { - airdrop_request_amount: u64, - client_pubkey: Pubkey, + tokens: u64, + to: Pubkey, + last_id: Hash, }, } pub struct Drone { mint_keypair: Keypair, ip_cache: Vec, - _airdrop_addr: SocketAddr, - network_addr: SocketAddr, pub time_slice: Duration, request_cap: u64, pub request_current: u64, @@ -50,8 +51,6 @@ pub struct Drone { impl Drone { pub fn new( mint_keypair: Keypair, - _airdrop_addr: SocketAddr, - network_addr: SocketAddr, time_input: Option, request_cap_input: Option, ) -> Drone { @@ -66,8 +65,6 @@ impl Drone { Drone { mint_keypair, ip_cache: Vec::new(), - _airdrop_addr, - network_addr, time_slice, request_cap, request_current: 0, @@ -102,50 +99,37 @@ impl Drone { } } - pub fn send_airdrop(&mut self, req: DroneRequest) -> Result { - let request_amount: u64; - let transactions_socket = UdpSocket::bind("0.0.0.0:0").unwrap(); - - let leader = poll_gossip_for_leader(self.network_addr, Some(10)) - .map_err(|e| io::Error::new(io::ErrorKind::Other, e.to_string()))?; - - let mut client = ThinClient::new(leader.rpc, leader.tpu, transactions_socket); - let last_id = client.get_last_id(); - - let mut tx = match req { + pub fn build_airdrop_transaction( + &mut self, + req: DroneRequest, + ) -> Result { + trace!("build_airdrop_transaction: {:?}", req); + match req { DroneRequest::GetAirdrop { - airdrop_request_amount, - client_pubkey, + tokens, + to, + last_id, } => { - info!( - "Requesting airdrop of {} to {:?}", - airdrop_request_amount, client_pubkey - ); - request_amount = airdrop_request_amount; - Transaction::system_new( - &self.mint_keypair, - client_pubkey, - airdrop_request_amount as u64, - last_id, - ) + if self.check_request_limit(tokens) { + self.request_current += tokens; + metrics::submit( + influxdb::Point::new("drone") + .add_tag("op", influxdb::Value::String("airdrop".to_string())) + .add_field("request_amount", influxdb::Value::Integer(tokens as i64)) + .add_field( + "request_current", + influxdb::Value::Integer(self.request_current as i64), + ).to_owned(), + ); + + info!("Requesting airdrop of {} to {:?}", tokens, to); + let mut tx = Transaction::system_new(&self.mint_keypair, to, tokens, last_id); + tx.sign(&[&self.mint_keypair], last_id); + Ok(tx) + } else { + Err(Error::new(ErrorKind::Other, "token limit reached")) + } } - }; - if self.check_request_limit(request_amount) { - self.request_current += request_amount; - metrics::submit( - influxdb::Point::new("drone") - .add_tag("op", influxdb::Value::String("airdrop".to_string())) - .add_field( - "request_amount", - influxdb::Value::Integer(request_amount as i64), - ).add_field( - "request_current", - influxdb::Value::Integer(self.request_current as i64), - ).to_owned(), - ); - client.retry_transfer(&self.mint_keypair, &mut tx, 10) - } else { - Err(Error::new(ErrorKind::Other, "token limit reached")) } } } @@ -156,16 +140,67 @@ impl Drop for Drone { } } -pub fn run_local_drone(mint_keypair: Keypair, network: SocketAddr, sender: Sender) { +pub fn request_airdrop_transaction( + drone_addr: &SocketAddr, + id: &Pubkey, + tokens: u64, + last_id: Hash, +) -> Result { + // TODO: make this async tokio client + let mut stream = TcpStream::connect_timeout(drone_addr, Duration::new(3, 0))?; + stream.set_read_timeout(Some(Duration::new(10, 0)))?; + let req = DroneRequest::GetAirdrop { + tokens, + last_id, + to: *id, + }; + let req = serialize(&req).expect("serialize drone request"); + stream.write_all(&req)?; + + // Read length of transaction + let mut buffer = [0; 2]; + stream.read_exact(&mut buffer).or_else(|err| { + info!( + "request_airdrop_transaction: buffer length read_exact error: {:?}", + err + ); + Err(Error::new(ErrorKind::Other, "Airdrop failed")) + })?; + let transaction_length = LittleEndian::read_u16(&buffer) as usize; + if transaction_length >= PACKET_DATA_SIZE { + Err(Error::new( + ErrorKind::Other, + format!( + "request_airdrop_transaction: invalid transaction_length from drone: {}", + transaction_length + ), + ))?; + } + + // Read the transaction + let mut buffer = Vec::new(); + buffer.resize(transaction_length, 0); + stream.read_exact(&mut buffer).or_else(|err| { + info!( + "request_airdrop_transaction: buffer read_exact error: {:?}", + err + ); + Err(Error::new(ErrorKind::Other, "Airdrop failed")) + })?; + + let transaction: Transaction = deserialize(&buffer).or_else(|err| { + Err(Error::new( + ErrorKind::Other, + format!("request_airdrop_transaction deserialize failure: {:?}", err), + )) + })?; + Ok(transaction) +} + +pub fn run_local_drone(mint_keypair: Keypair, sender: Sender) { thread::spawn(move || { let drone_addr = socketaddr!(0, 0); - let drone = Arc::new(Mutex::new(Drone::new( - mint_keypair, - drone_addr, - network, - None, - None, - ))); + let drone = Arc::new(Mutex::new(Drone::new(mint_keypair, None, None))); let socket = TcpListener::bind(&drone_addr).unwrap(); sender.send(socket.local_addr().unwrap()).unwrap(); info!("Drone started. Listening on: {}", drone_addr); @@ -186,12 +221,13 @@ pub fn run_local_drone(mint_keypair: Keypair, network: SocketAddr, sender: Sende })?; info!("Airdrop requested..."); - let res1 = drone2.lock().unwrap().send_airdrop(req); - match res1 { + let res = drone2.lock().unwrap().build_airdrop_transaction(req); + match res { Ok(_) => info!("Airdrop sent!"), Err(_) => info!("Request limit reached for this time slice"), } - let response = res1?; + let response = res?; + info!("Airdrop tx signature: {:?}", response); let response_vec = serialize(&response).or_else(|err| { Err(io::Error::new( @@ -199,7 +235,20 @@ pub fn run_local_drone(mint_keypair: Keypair, network: SocketAddr, sender: Sende format!("serialize signature in drone: {:?}", err), )) })?; - let response_bytes = Bytes::from(response_vec.clone()); + + let mut response_vec_with_length = vec![0; 2]; + LittleEndian::write_u16( + &mut response_vec_with_length, + response_vec.len() as u16, + ); + info!( + "Airdrop response_vec_with_length: {:?}", + response_vec_with_length + ); + response_vec_with_length.extend_from_slice(&response_vec); + + let response_bytes = Bytes::from(response_vec_with_length.clone()); + info!("Airdrop response_bytes: {:?}", response_bytes); Ok(response_bytes) }); let server = writer @@ -238,8 +287,7 @@ mod tests { let keypair = Keypair::new(); let mut addr: SocketAddr = "0.0.0.0:9900".parse().unwrap(); addr.set_ip(get_ip_addr().unwrap()); - let network_addr = "0.0.0.0:0".parse().unwrap(); - let mut drone = Drone::new(keypair, addr, network_addr, None, Some(3)); + let mut drone = Drone::new(keypair, None, Some(3)); assert!(drone.check_request_limit(1)); drone.request_current = 3; assert!(!drone.check_request_limit(1)); @@ -250,8 +298,7 @@ mod tests { let keypair = Keypair::new(); let mut addr: SocketAddr = "0.0.0.0:9900".parse().unwrap(); addr.set_ip(get_ip_addr().unwrap()); - let network_addr = "0.0.0.0:0".parse().unwrap(); - let mut drone = Drone::new(keypair, addr, network_addr, None, None); + let mut drone = Drone::new(keypair, None, None); drone.request_current = drone.request_current + 256; assert_eq!(drone.request_current, 256); drone.clear_request_count(); @@ -263,8 +310,7 @@ mod tests { let keypair = Keypair::new(); let mut addr: SocketAddr = "0.0.0.0:9900".parse().unwrap(); addr.set_ip(get_ip_addr().unwrap()); - let network_addr = "0.0.0.0:0".parse().unwrap(); - let mut drone = Drone::new(keypair, addr, network_addr, None, None); + let mut drone = Drone::new(keypair, None, None); let ip = "127.0.0.1".parse().expect("create IpAddr from string"); assert_eq!(drone.ip_cache.len(), 0); drone.add_ip_to_cache(ip); @@ -277,8 +323,7 @@ mod tests { let keypair = Keypair::new(); let mut addr: SocketAddr = "0.0.0.0:9900".parse().unwrap(); addr.set_ip(get_ip_addr().unwrap()); - let network_addr = "0.0.0.0:0".parse().unwrap(); - let mut drone = Drone::new(keypair, addr, network_addr, None, None); + let mut drone = Drone::new(keypair, None, None); let ip = "127.0.0.1".parse().expect("create IpAddr from string"); assert_eq!(drone.ip_cache.len(), 0); drone.add_ip_to_cache(ip); @@ -293,10 +338,9 @@ mod tests { let keypair = Keypair::new(); let mut addr: SocketAddr = "0.0.0.0:9900".parse().unwrap(); addr.set_ip(get_ip_addr().unwrap()); - let network_addr = "0.0.0.0:0".parse().unwrap(); let time_slice: Option = None; let request_cap: Option = None; - let drone = Drone::new(keypair, addr, network_addr, time_slice, request_cap); + let drone = Drone::new(keypair, time_slice, request_cap); assert_eq!(drone.time_slice, Duration::new(TIME_SLICE, 0)); assert_eq!(drone.request_cap, REQUEST_CAP); } @@ -339,7 +383,7 @@ mod tests { let mut addr: SocketAddr = "0.0.0.0:9900".parse().expect("bind to drone socket"); addr.set_ip(get_ip_addr().expect("drone get_ip_addr")); - let mut drone = Drone::new(alice.keypair(), addr, leader_data.ncp, None, Some(150_000)); + let mut drone = Drone::new(alice.keypair(), None, Some(150_000)); let transactions_socket = UdpSocket::bind("0.0.0.0:0").expect("drone bind to transactions socket"); @@ -347,10 +391,12 @@ mod tests { let mut client = ThinClient::new(leader_data.rpc, leader_data.tpu, transactions_socket); let bob_req = DroneRequest::GetAirdrop { - airdrop_request_amount: 50, - client_pubkey: bob_pubkey, + tokens: 50, + to: bob_pubkey, + last_id, }; - let bob_sig = drone.send_airdrop(bob_req).unwrap(); + let bob_tx = drone.build_airdrop_transaction(bob_req).unwrap(); + let bob_sig = client.transfer_signed(&bob_tx).unwrap(); assert!(client.poll_for_signature(&bob_sig).is_ok()); // restart the leader, drone should find the new one at the same gossip port @@ -376,12 +422,14 @@ mod tests { let mut client = ThinClient::new(leader_data.rpc, leader_data.tpu, transactions_socket); let carlos_req = DroneRequest::GetAirdrop { - airdrop_request_amount: 5_000_000, - client_pubkey: carlos_pubkey, + tokens: 5_000_000, + to: carlos_pubkey, + last_id, }; // using existing drone, new thin client - let carlos_sig = drone.send_airdrop(carlos_req).unwrap(); + let carlos_tx = drone.build_airdrop_transaction(carlos_req).unwrap(); + let carlos_sig = client.transfer_signed(&carlos_tx).unwrap(); assert!(client.poll_for_signature(&carlos_sig).is_ok()); let bob_balance = client.get_balance(&bob_pubkey); diff --git a/src/rpc.rs b/src/rpc.rs index 0f92d36acd..9b3a74780d 100644 --- a/src/rpc.rs +++ b/src/rpc.rs @@ -1,10 +1,10 @@ //! The `rpc` module implements the Solana RPC interface. use bank::{Bank, BankError}; -use bincode::deserialize; +use bincode::{deserialize, serialize}; use bs58; use cluster_info::ClusterInfo; -use drone::DRONE_PORT; +use drone::{request_airdrop_transaction, DRONE_PORT}; use jsonrpc_core::*; use jsonrpc_http_server::*; use packet::PACKET_DATA_SIZE; @@ -22,7 +22,6 @@ 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; @@ -211,17 +210,35 @@ impl RpcSol for RpcSolImpl { let mut drone_addr = get_leader_addr(&meta.cluster_info)?; drone_addr.set_port(DRONE_PORT); - let signature = request_airdrop(&drone_addr, &pubkey, tokens).map_err(|err| { - info!("request_airdrop failed: {:?}", err); - Error::internal_error() - })?;; + let last_id = meta.request_processor.bank.last_id(); + let transaction = request_airdrop_transaction(&drone_addr, &pubkey, tokens, last_id) + .map_err(|err| { + info!("request_airdrop_transaction failed: {:?}", err); + Error::internal_error() + })?;; + let data = serialize(&transaction).map_err(|err| { + info!("request_airdrop: serialize error: {:?}", err); + Error::internal_error() + })?; + + let transactions_socket = UdpSocket::bind("0.0.0.0:0").unwrap(); + let transactions_addr = get_leader_addr(&meta.cluster_info)?; + transactions_socket + .send_to(&data, transactions_addr) + .map_err(|err| { + info!("request_airdrop: send_to error: {:?}", err); + Error::internal_error() + })?; + + let signature = transaction.signatures[0]; let now = Instant::now(); let mut signature_status; loop { signature_status = meta.request_processor.get_signature_status(signature); if signature_status.is_ok() { + info!("airdrop signature ok"); return Ok(bs58::encode(signature).into_string()); } else if now.elapsed().as_secs() > 5 { info!("airdrop signature timeout"); @@ -404,7 +421,7 @@ mod tests { "jsonrpc": "2.0", "id": 1, "method": "getBalance", - "params": vec![alice.pubkey().to_string()], + "params": [alice.pubkey().to_string()], }); let mut response = client .post(&rpc_string) @@ -639,7 +656,7 @@ mod tests { "jsonrpc": "2.0", "id": 1, "method": "sendTransaction", - "params": json!(vec![serial_tx]) + "params": json!([serial_tx]) }); let rpc_addr = leader_data.rpc; let rpc_string = format!("http://{}", rpc_addr.to_string()); @@ -659,7 +676,7 @@ mod tests { "jsonrpc": "2.0", "id": 1, "method": "confirmTransaction", - "params": vec![signature], + "params": [signature], }); let mut response = client .post(&rpc_string) @@ -667,7 +684,8 @@ mod tests { .body(request.to_string()) .send() .unwrap(); - let json: Value = serde_json::from_str(&response.text().unwrap()).unwrap(); + let response_json_text = response.text().unwrap(); + let json: Value = serde_json::from_str(&response_json_text).unwrap(); assert_eq!(true, json["result"]); diff --git a/src/rpc_request.rs b/src/rpc_request.rs index 1d2260d936..d1b58f9659 100644 --- a/src/rpc_request.rs +++ b/src/rpc_request.rs @@ -56,7 +56,7 @@ impl RpcRequest { "method": method, }); if let Some(param_string) = params { - request["params"] = json!(vec![param_string]); + request["params"] = param_string; } request } @@ -99,18 +99,18 @@ mod tests { let test_request = RpcRequest::GetAccountInfo; let request = test_request.build_request_json( 1, - Some(json!("deadbeefXjn8o3yroDHxUtKsZZgoy4GPkPPXfouKNHhx")), + Some(json!(["deadbeefXjn8o3yroDHxUtKsZZgoy4GPkPPXfouKNHhx"])), ); assert_eq!(request["method"], "getAccountInfo"); assert_eq!( request["params"], - json!(vec!["deadbeefXjn8o3yroDHxUtKsZZgoy4GPkPPXfouKNHhx"]) + json!(["deadbeefXjn8o3yroDHxUtKsZZgoy4GPkPPXfouKNHhx"]) ); let test_request = RpcRequest::GetBalance; let request = test_request.build_request_json( 1, - Some(json!("deadbeefXjn8o3yroDHxUtKsZZgoy4GPkPPXfouKNHhx")), + Some(json!(["deadbeefXjn8o3yroDHxUtKsZZgoy4GPkPPXfouKNHhx"])), ); assert_eq!(request["method"], "getBalance"); @@ -172,7 +172,7 @@ mod tests { let balance = RpcRequest::GetBalance.make_rpc_request( &rpc_addr, 1, - Some(json!("deadbeefXjn8o3yroDHxUtKsZZgoy4GPkPPXfouKNHhx")), + Some(json!(["deadbeefXjn8o3yroDHxUtKsZZgoy4GPkPPXfouKNHhx"])), ); assert!(balance.is_ok()); assert_eq!(balance.unwrap().as_u64().unwrap(), 50); diff --git a/src/thin_client.rs b/src/thin_client.rs index 433e952a95..3be0e2db20 100644 --- a/src/thin_client.rs +++ b/src/thin_client.rs @@ -143,7 +143,7 @@ impl ThinClient { } pub fn get_account_userdata(&mut self, pubkey: &Pubkey) -> io::Result>> { - let params = json!(format!("{}", pubkey)); + let params = json!([format!("{}", pubkey)]); let rpc_string = format!("http://{}", self.rpc_addr.to_string()); let resp = RpcRequest::GetAccountInfo.make_rpc_request(&rpc_string, 1, Some(params)); if let Ok(account_json) = resp { @@ -162,7 +162,7 @@ impl ThinClient { /// by the network, this method will hang indefinitely. pub fn get_balance(&mut self, pubkey: &Pubkey) -> io::Result { trace!("get_balance sending request to {}", self.rpc_addr); - let params = json!(format!("{}", pubkey)); + let params = json!([format!("{}", pubkey)]); let rpc_string = format!("http://{}", self.rpc_addr.to_string()); let resp = RpcRequest::GetAccountInfo.make_rpc_request(&rpc_string, 1, Some(params)); if let Ok(account_json) = resp { @@ -303,7 +303,7 @@ impl ThinClient { /// until the server sends a response. pub fn check_signature(&mut self, signature: &Signature) -> bool { trace!("check_signature"); - let params = json!(format!("{}", signature)); + let params = json!([format!("{}", signature)]); let now = Instant::now(); let rpc_string = format!("http://{}", self.rpc_addr.to_string()); let mut done = false; diff --git a/src/wallet.rs b/src/wallet.rs index 2d9cd93b61..bd11d91c64 100644 --- a/src/wallet.rs +++ b/src/wallet.rs @@ -1,11 +1,11 @@ -use bincode::{deserialize, serialize}; +use bincode::serialize; use bpf_loader; use bs58; use budget_program::BudgetState; use budget_transaction::BudgetTransaction; use chrono::prelude::*; use clap::ArgMatches; -use drone::{DroneRequest, DRONE_PORT}; +use drone::{request_airdrop_transaction, DRONE_PORT}; use elf; use fullnode::Config; use hash::Hash; @@ -18,14 +18,10 @@ use serde_json; use signature::{Keypair, KeypairUtil, Signature}; use solana_sdk::pubkey::Pubkey; use std::fs::{self, File}; -use std::io::prelude::*; -use std::io::{Error, ErrorKind, Write}; -use std::mem::size_of; -use std::net::{Ipv4Addr, SocketAddr, TcpStream}; +use std::io::Write; +use std::net::{Ipv4Addr, SocketAddr}; use std::path::Path; use std::str::FromStr; -use std::thread::sleep; -use std::time::Duration; use std::{error, fmt, mem}; use system_transaction::SystemTransaction; use thin_client::poll_gossip_for_leader; @@ -330,7 +326,7 @@ pub fn process_command(config: &WalletConfig) -> Result Result Result { println!("Balance requested..."); - let params = json!(format!("{}", config.id.pubkey())); + let params = json!([format!("{}", config.id.pubkey())]); let balance = RpcRequest::GetBalance .make_rpc_request(&rpc_addr, 1, Some(params))? .as_u64(); @@ -388,7 +378,7 @@ pub fn process_command(config: &WalletConfig) -> Result { - let params = json!(format!("{}", signature)); + let params = json!([format!("{}", signature)]); let confirmation = RpcRequest::ConfirmTransaction .make_rpc_request(&rpc_addr, 1, Some(params))? .as_bool(); @@ -407,7 +397,7 @@ pub fn process_command(config: &WalletConfig) -> Result { - let params = json!(format!("{}", config.id.pubkey())); + let params = json!([format!("{}", config.id.pubkey())]); let balance = RpcRequest::GetBalance .make_rpc_request(&rpc_addr, 1, Some(params))? .as_u64(); @@ -615,8 +605,12 @@ pub fn process_command(config: &WalletConfig) -> Result Result { - let last_id = get_last_id(&rpc_addr)?; - - let params = json!(format!("{}", config.id.pubkey())); + let params = json!([format!("{}", config.id.pubkey())]); let balance = RpcRequest::GetBalance .make_rpc_request(&rpc_addr, 1, Some(params))? .as_u64(); + if let Some(0) = balance { - request_airdrop(&drone_addr, &config.id.pubkey(), 1)?; + let params = json!([format!("{}", config.id.pubkey()), 1]); + RpcRequest::RequestAirdrop + .make_rpc_request(&rpc_addr, 1, Some(params)) + .unwrap(); } + let last_id = get_last_id(&rpc_addr)?; let tx = Transaction::budget_new_signature(&config.id, pubkey, to, last_id); let signature_str = send_tx(&rpc_addr, &tx)?; @@ -662,34 +659,6 @@ pub fn read_leader(path: &str) -> Result { }) } -pub fn request_airdrop( - drone_addr: &SocketAddr, - id: &Pubkey, - tokens: u64, -) -> Result { - // TODO: make this async tokio client - let mut stream = TcpStream::connect_timeout(drone_addr, Duration::new(3, 0))?; - stream.set_read_timeout(Some(Duration::new(10, 0)))?; - let req = DroneRequest::GetAirdrop { - airdrop_request_amount: tokens, - client_pubkey: *id, - }; - let tx = serialize(&req).expect("serialize drone request"); - stream.write_all(&tx)?; - let mut buffer = [0; size_of::()]; - stream.read_exact(&mut buffer).or_else(|err| { - info!("request_airdrop: read_exact error: {:?}", err); - Err(Error::new(ErrorKind::Other, "Airdrop failed")) - })?; - let signature: Signature = deserialize(&buffer).or_else(|err| { - Err(Error::new( - ErrorKind::Other, - format!("deserialize signature in request_airdrop: {:?}", err), - )) - })?; - Ok(signature) -} - pub fn gen_keypair_file(outfile: String) -> Result> { let rnd = SystemRandom::new(); let pkcs8_bytes = Ed25519KeyPair::generate_pkcs8(&rnd)?; @@ -721,7 +690,7 @@ fn get_last_id(rpc_addr: &str) -> Result> { fn send_tx(rpc_addr: &str, tx: &Transaction) -> Result> { let serialized = serialize(tx).unwrap(); - let params = json!(serialized); + let params = json!([serialized]); let signature = RpcRequest::SendTransaction.make_rpc_request(&rpc_addr, 2, Some(params))?; if signature.as_str().is_none() { Err(WalletError::RpcRequestError( @@ -732,7 +701,7 @@ fn send_tx(rpc_addr: &str, tx: &Transaction) -> Result } fn confirm_tx(rpc_addr: &str, signature: &str) -> Result> { - let params = json!(signature.to_string()); + let params = json!([signature.to_string()]); let signature_status = RpcRequest::GetSignatureStatus.make_rpc_request(&rpc_addr, 1, Some(params))?; if let Some(status) = signature_status.as_str() { @@ -799,6 +768,8 @@ mod tests { use std::fs::remove_dir_all; use std::sync::mpsc::channel; use std::sync::{Arc, RwLock}; + use std::thread::sleep; + use std::time::Duration; #[test] fn test_wallet_parse_command() { @@ -1114,7 +1085,6 @@ mod tests { assert!(parse_command(pubkey, &test_bad_timestamp).is_err()); } #[test] - #[ignore] fn test_wallet_process_command() { let bob_pubkey = Keypair::new().pubkey(); @@ -1147,7 +1117,7 @@ mod tests { sleep(Duration::from_millis(900)); let (sender, receiver) = channel(); - run_local_drone(alice.keypair(), leader_data.ncp, sender); + run_local_drone(alice.keypair(), sender); let drone_addr = receiver.recv().unwrap(); let mut config = WalletConfig::default(); @@ -1189,8 +1159,6 @@ mod tests { } #[test] fn test_wallet_request_airdrop() { - let bob_pubkey = Keypair::new().pubkey(); - let leader_keypair = Arc::new(Keypair::new()); let leader = Node::new_localhost_with_pubkey(leader_keypair.pubkey()); let leader_data = leader.info.clone(); @@ -1220,20 +1188,25 @@ mod tests { sleep(Duration::from_millis(900)); let (sender, receiver) = channel(); - run_local_drone(alice.keypair(), leader_data.ncp, sender); + run_local_drone(alice.keypair(), sender); let drone_addr = receiver.recv().unwrap(); - let rpc_addr = format!("http://{}", leader_data.rpc.to_string()); + let mut bob_config = WalletConfig::default(); + bob_config.network = leader_data.ncp; + bob_config.drone_port = Some(drone_addr.port()); + bob_config.command = WalletCommand::AirDrop(50); - let signature = request_airdrop(&drone_addr, &bob_pubkey, 50); - assert!(signature.is_ok()); - let params = json!(format!("{}", signature.unwrap())); - let confirmation = RpcRequest::ConfirmTransaction + let sig_response = process_command(&bob_config); + assert!(sig_response.is_ok()); + + let rpc_addr = format!("http://{}", leader_data.rpc.to_string()); + let params = json!([format!("{}", bob_config.id.pubkey())]); + let balance = RpcRequest::GetBalance .make_rpc_request(&rpc_addr, 1, Some(params)) .unwrap() - .as_bool() + .as_u64() .unwrap(); - assert!(confirmation); + assert_eq!(balance, 50); server.close().unwrap(); remove_dir_all(ledger_path).unwrap(); @@ -1295,7 +1268,7 @@ mod tests { sleep(Duration::from_millis(900)); let (sender, receiver) = channel(); - run_local_drone(alice.keypair(), leader_data.ncp, sender); + run_local_drone(alice.keypair(), sender); let drone_addr = receiver.recv().unwrap(); let rpc_addr = format!("http://{}", leader_data.rpc.to_string()); @@ -1310,7 +1283,11 @@ mod tests { assert_ne!(config_payer.id.pubkey(), config_witness.id.pubkey()); - let _signature = request_airdrop(&drone_addr, &config_payer.id.pubkey(), 50); + let last_id = get_last_id(&rpc_addr).unwrap(); + let transaction = + request_airdrop_transaction(&drone_addr, &config_payer.id.pubkey(), 50, last_id) + .unwrap(); + send_and_confirm_tx(&rpc_addr, &transaction).unwrap(); // Make transaction (from config_payer to bob_pubkey) requiring timestamp from config_witness let date_string = "\"2018-09-19T17:30:59Z\""; @@ -1333,21 +1310,21 @@ mod tests { .expect("base58-encoded public key"); let process_id = Pubkey::new(&process_id_vec); - let params = json!(format!("{}", config_payer.id.pubkey())); + let params = json!([format!("{}", config_payer.id.pubkey())]); let config_payer_balance = RpcRequest::GetBalance .make_rpc_request(&rpc_addr, 1, Some(params)) .unwrap() .as_u64() .unwrap(); assert_eq!(config_payer_balance, 39); - let params = json!(format!("{}", process_id)); + let params = json!([format!("{}", process_id)]); let contract_balance = RpcRequest::GetBalance .make_rpc_request(&rpc_addr, 1, Some(params)) .unwrap() .as_u64() .unwrap(); assert_eq!(contract_balance, 11); - let params = json!(format!("{}", bob_pubkey)); + let params = json!([format!("{}", bob_pubkey)]); let recipient_balance = RpcRequest::GetBalance .make_rpc_request(&rpc_addr, 1, Some(params)) .unwrap() @@ -1360,21 +1337,21 @@ mod tests { let sig_response = process_command(&config_witness); assert!(sig_response.is_ok()); - let params = json!(format!("{}", config_payer.id.pubkey())); + let params = json!([format!("{}", config_payer.id.pubkey())]); let config_payer_balance = RpcRequest::GetBalance .make_rpc_request(&rpc_addr, 1, Some(params)) .unwrap() .as_u64() .unwrap(); assert_eq!(config_payer_balance, 39); - let params = json!(format!("{}", process_id)); + let params = json!([format!("{}", process_id)]); let contract_balance = RpcRequest::GetBalance .make_rpc_request(&rpc_addr, 1, Some(params)) .unwrap() .as_u64() .unwrap(); assert_eq!(contract_balance, 1); - let params = json!(format!("{}", bob_pubkey)); + let params = json!([format!("{}", bob_pubkey)]); let recipient_balance = RpcRequest::GetBalance .make_rpc_request(&rpc_addr, 1, Some(params)) .unwrap() @@ -1419,14 +1396,18 @@ mod tests { sleep(Duration::from_millis(900)); let (sender, receiver) = channel(); - run_local_drone(alice.keypair(), leader_data.ncp, sender); + run_local_drone(alice.keypair(), sender); let drone_addr = receiver.recv().unwrap(); let rpc_addr = format!("http://{}", leader_data.rpc.to_string()); assert_ne!(config_payer.id.pubkey(), config_witness.id.pubkey()); - let _signature = request_airdrop(&drone_addr, &config_payer.id.pubkey(), 50); + let last_id = get_last_id(&rpc_addr).unwrap(); + let transaction = + request_airdrop_transaction(&drone_addr, &config_payer.id.pubkey(), 50, last_id) + .unwrap(); + send_and_confirm_tx(&rpc_addr, &transaction).unwrap(); // Make transaction (from config_payer to bob_pubkey) requiring witness signature from config_witness config_payer.command = WalletCommand::Pay( @@ -1447,21 +1428,21 @@ mod tests { .expect("base58-encoded public key"); let process_id = Pubkey::new(&process_id_vec); - let params = json!(format!("{}", config_payer.id.pubkey())); + let params = json!([format!("{}", config_payer.id.pubkey())]); let config_payer_balance = RpcRequest::GetBalance .make_rpc_request(&rpc_addr, 1, Some(params)) .unwrap() .as_u64() .unwrap(); assert_eq!(config_payer_balance, 39); - let params = json!(format!("{}", process_id)); + let params = json!([format!("{}", process_id)]); let contract_balance = RpcRequest::GetBalance .make_rpc_request(&rpc_addr, 1, Some(params)) .unwrap() .as_u64() .unwrap(); assert_eq!(contract_balance, 11); - let params = json!(format!("{}", bob_pubkey)); + let params = json!([format!("{}", bob_pubkey)]); let recipient_balance = RpcRequest::GetBalance .make_rpc_request(&rpc_addr, 1, Some(params)) .unwrap() @@ -1474,21 +1455,21 @@ mod tests { let sig_response = process_command(&config_witness); assert!(sig_response.is_ok()); - let params = json!(format!("{}", config_payer.id.pubkey())); + let params = json!([format!("{}", config_payer.id.pubkey())]); let config_payer_balance = RpcRequest::GetBalance .make_rpc_request(&rpc_addr, 1, Some(params)) .unwrap() .as_u64() .unwrap(); assert_eq!(config_payer_balance, 39); - let params = json!(format!("{}", process_id)); + let params = json!([format!("{}", process_id)]); let contract_balance = RpcRequest::GetBalance .make_rpc_request(&rpc_addr, 1, Some(params)) .unwrap() .as_u64() .unwrap(); assert_eq!(contract_balance, 1); - let params = json!(format!("{}", bob_pubkey)); + let params = json!([format!("{}", bob_pubkey)]); let recipient_balance = RpcRequest::GetBalance .make_rpc_request(&rpc_addr, 1, Some(params)) .unwrap() @@ -1532,7 +1513,7 @@ mod tests { sleep(Duration::from_millis(900)); let (sender, receiver) = channel(); - run_local_drone(alice.keypair(), leader_data.ncp, sender); + run_local_drone(alice.keypair(), sender); let drone_addr = receiver.recv().unwrap(); let rpc_addr = format!("http://{}", leader_data.rpc.to_string()); @@ -1547,7 +1528,11 @@ mod tests { assert_ne!(config_payer.id.pubkey(), config_witness.id.pubkey()); - let _signature = request_airdrop(&drone_addr, &config_payer.id.pubkey(), 50); + let last_id = get_last_id(&rpc_addr).unwrap(); + let transaction = + request_airdrop_transaction(&drone_addr, &config_payer.id.pubkey(), 50, last_id) + .unwrap(); + send_and_confirm_tx(&rpc_addr, &transaction).unwrap(); // Make transaction (from config_payer to bob_pubkey) requiring witness signature from config_witness config_payer.command = WalletCommand::Pay( @@ -1568,21 +1553,21 @@ mod tests { .expect("base58-encoded public key"); let process_id = Pubkey::new(&process_id_vec); - let params = json!(format!("{}", config_payer.id.pubkey())); + let params = json!([format!("{}", config_payer.id.pubkey())]); let config_payer_balance = RpcRequest::GetBalance .make_rpc_request(&rpc_addr, 1, Some(params)) .unwrap() .as_u64() .unwrap(); assert_eq!(config_payer_balance, 39); - let params = json!(format!("{}", process_id)); + let params = json!([format!("{}", process_id)]); let contract_balance = RpcRequest::GetBalance .make_rpc_request(&rpc_addr, 1, Some(params)) .unwrap() .as_u64() .unwrap(); assert_eq!(contract_balance, 11); - let params = json!(format!("{}", bob_pubkey)); + let params = json!([format!("{}", bob_pubkey)]); let recipient_balance = RpcRequest::GetBalance .make_rpc_request(&rpc_addr, 1, Some(params)) .unwrap() @@ -1595,21 +1580,21 @@ mod tests { let sig_response = process_command(&config_payer); assert!(sig_response.is_ok()); - let params = json!(format!("{}", config_payer.id.pubkey())); + let params = json!([format!("{}", config_payer.id.pubkey())]); let config_payer_balance = RpcRequest::GetBalance .make_rpc_request(&rpc_addr, 1, Some(params)) .unwrap() .as_u64() .unwrap(); assert_eq!(config_payer_balance, 49); - let params = json!(format!("{}", process_id)); + let params = json!([format!("{}", process_id)]); let contract_balance = RpcRequest::GetBalance .make_rpc_request(&rpc_addr, 1, Some(params)) .unwrap() .as_u64() .unwrap(); assert_eq!(contract_balance, 1); - let params = json!(format!("{}", bob_pubkey)); + let params = json!([format!("{}", bob_pubkey)]); let recipient_balance = RpcRequest::GetBalance .make_rpc_request(&rpc_addr, 1, Some(params)) .unwrap()