diff --git a/Cargo.lock b/Cargo.lock index befb6e394..a48cc8653 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -367,6 +367,11 @@ name = "c_linked_list" version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "cast" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "cbindgen" version = "0.9.0" @@ -628,6 +633,18 @@ dependencies = [ "cfg-if 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "criterion-stats" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "cast 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", + "num-traits 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", + "num_cpus 1.10.1 (registry+https://github.com/rust-lang/crates.io-index)", + "rand 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)", + "thread-scoped 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "crossbeam" version = "0.2.12" @@ -3687,7 +3704,7 @@ dependencies = [ "bincode 1.1.4 (registry+https://github.com/rust-lang/crates.io-index)", "bs58 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", "cbindgen 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.60 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.61 (registry+https://github.com/rust-lang/crates.io-index)", "rand_chacha 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", "rand_core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", "solana-ed25519-dalek 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", @@ -3878,6 +3895,8 @@ dependencies = [ "chrono 0.4.7 (registry+https://github.com/rust-lang/crates.io-index)", "clap 2.33.0 (registry+https://github.com/rust-lang/crates.io-index)", "console 0.7.7 (registry+https://github.com/rust-lang/crates.io-index)", + "criterion-stats 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", + "ctrlc 3.1.3 (registry+https://github.com/rust-lang/crates.io-index)", "dirs 2.0.2 (registry+https://github.com/rust-lang/crates.io-index)", "lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", @@ -4590,6 +4609,11 @@ dependencies = [ "winapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "thread-scoped" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "thread_local" version = "0.2.7" @@ -5234,6 +5258,7 @@ dependencies = [ "checksum bzip2-sys 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)" = "6584aa36f5ad4c9247f5323b0a42f37802b37a836f0ad87084d7a33961abe25f" "checksum c2-chacha 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7d64d04786e0f528460fc884753cf8dddcc466be308f6026f8e355c41a0e4101" "checksum c_linked_list 1.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "4964518bd3b4a8190e832886cdc0da9794f12e8e6c1613a9e90ff331c4c8724b" +"checksum cast 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "926013f2860c46252efceabb19f4a6b308197505082c609025aa6706c011d427" "checksum cbindgen 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "0e7e19db9a3892c88c74cbbdcd218196068a928f1b60e736c448b13a1e81f277" "checksum cc 1.0.38 (registry+https://github.com/rust-lang/crates.io-index)" = "ce400c638d48ee0e9ab75aef7997609ec57367ccfe1463f21bf53c3eca67bf46" "checksum cexpr 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)" = "a7fa24eb00d5ffab90eaeaf1092ac85c04c64aaf358ea6f84505b8116d24c6af" @@ -5261,6 +5286,7 @@ dependencies = [ "checksum core_affinity 0.5.9 (registry+https://github.com/rust-lang/crates.io-index)" = "6d162c6e463c31dbf78fefa99d042156c1c74d404e299cfe3df2923cb857595b" "checksum crc 1.8.1 (registry+https://github.com/rust-lang/crates.io-index)" = "d663548de7f5cca343f1e0a48d14dcfb0e9eb4e079ec58883b7251539fa10aeb" "checksum crc32fast 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ba125de2af0df55319f41944744ad91c71113bf74a4646efff39afe1f6842db1" +"checksum criterion-stats 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "387df94cb74ada1b33e10ce034bb0d9360cc73edb5063e7d7d4120a40ee1c9d2" "checksum crossbeam 0.2.12 (registry+https://github.com/rust-lang/crates.io-index)" = "bd66663db5a988098a89599d4857919b3acf7f61402e61365acfd3919857b9be" "checksum crossbeam 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ad4c7ea749d9fb09e23c5cb17e3b70650860553a0e2744e38446b1803bf7db94" "checksum crossbeam 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)" = "2d818a4990769aac0c7ff1360e233ef3a41adcb009ebb2036bf6915eb0f6b23c" @@ -5585,6 +5611,7 @@ dependencies = [ "checksum textwrap 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060" "checksum thread-id 2.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "a9539db560102d1cef46b8b78ce737ff0bb64e7e18d35b2a5688f7d097d0ff03" "checksum thread-id 3.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "c7fbf4c9d56b320106cd64fd024dadfa0be7cb4706725fc44a7d7ce952d820c1" +"checksum thread-scoped 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "bcbb6aa301e5d3b0b5ef639c9a9c7e2f1c944f177b460c04dc24c69b1fa2bd99" "checksum thread_local 0.2.7 (registry+https://github.com/rust-lang/crates.io-index)" = "8576dbbfcaef9641452d5cf0df9b0e7eeab7694956dd33bb61515fb8f18cfdd5" "checksum thread_local 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)" = "c6b53e329000edc2b34dbe8545fd20e55a333362d0a321909685a19bd28c3f1b" "checksum threshold_crypto 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "95be1032c63011f20b01c5edb64930e2b51512782b43b458b1e3449613d70f87" diff --git a/wallet/Cargo.toml b/wallet/Cargo.toml index 09292cc40..a9f93c1c4 100644 --- a/wallet/Cargo.toml +++ b/wallet/Cargo.toml @@ -13,6 +13,8 @@ bincode = "1.1.4" bs58 = "0.2.0" chrono = { version = "0.4.7", features = ["serde"] } clap = "2.33.0" +criterion-stats = "0.3.0" +ctrlc = { version = "3.1.3", features = ["termination"] } console = "0.7.7" dirs = "2.0.2" lazy_static = "1.3.0" diff --git a/wallet/src/wallet.rs b/wallet/src/wallet.rs index 95263d2b4..be681014d 100644 --- a/wallet/src/wallet.rs +++ b/wallet/src/wallet.rs @@ -1,7 +1,7 @@ use crate::display::println_name_value; use chrono::prelude::*; -use clap::{App, AppSettings, Arg, ArgMatches, SubCommand}; -use console::style; +use clap::{value_t_or_exit, App, AppSettings, Arg, ArgMatches, SubCommand}; +use console::{style, Emoji}; use log::*; use num_traits::FromPrimitive; use serde_json; @@ -33,15 +33,19 @@ use solana_stake_api::stake_instruction; use solana_storage_api::storage_instruction; use solana_vote_api::vote_instruction; use solana_vote_api::vote_state::VoteState; +use std::collections::VecDeque; use std::fs::File; use std::io::{Read, Write}; use std::net::{IpAddr, SocketAddr}; use std::thread::sleep; -use std::time::Duration; +use std::time::{Duration, Instant}; use std::{error, fmt}; const USERDATA_CHUNK_SIZE: usize = 229; // Keep program chunks under PACKET_DATA_SIZE +static CHECK_MARK: Emoji = Emoji("✅ ", ""); +static CROSS_MARK: Emoji = Emoji("❌ ", ""); + #[derive(Debug, PartialEq)] #[allow(clippy::large_enum_variant)] pub enum WalletCommand { @@ -77,6 +81,11 @@ pub enum WalletCommand { Option>, Option, ), + Ping { + interval: Duration, + count: Option, + timeout: Duration, + }, // TimeElapsed(to, process_id, timestamp) TimeElapsed(Pubkey, Pubkey, DateTime), // Witness(to, process_id) @@ -367,6 +376,20 @@ pub fn parse_command( cancelable, )) } + ("ping", Some(ping_matches)) => { + let interval = Duration::from_secs(value_t_or_exit!(ping_matches, "interval", u64)); + let count = if ping_matches.is_present("count") { + Some(value_t_or_exit!(ping_matches, "count", u64)) + } else { + None + }; + let timeout = Duration::from_secs(value_t_or_exit!(ping_matches, "timeout", u64)); + Ok(WalletCommand::Ping { + interval, + count, + timeout, + }) + } ("send-signature", Some(sig_matches)) => { let to = value_of(&sig_matches, "to").unwrap(); let process_id = value_of(&sig_matches, "process_id").unwrap(); @@ -1180,6 +1203,122 @@ fn process_get_version(rpc_client: &RpcClient, config: &WalletConfig) -> Process Ok("".to_string()) } +fn process_ping( + rpc_client: &RpcClient, + config: &WalletConfig, + interval: &Duration, + count: &Option, + timeout: &Duration, +) -> ProcessResult { + let to = Keypair::new().pubkey(); + + println_name_value("Source account:", &config.keypair.pubkey().to_string()); + println_name_value("Destination account:", &to.to_string()); + println!(); + + let (signal_sender, signal_receiver) = std::sync::mpsc::channel(); + ctrlc::set_handler(move || { + let _ = signal_sender.send(()); + }) + .expect("Error setting Ctrl-C handler"); + + let mut last_blockhash = Hash::default(); + let mut submit_count = 0; + let mut confirmed_count = 0; + let mut confirmation_time: VecDeque = VecDeque::with_capacity(1024); + + 'mainloop: for seq in 0..count.unwrap_or(std::u64::MAX) { + let (recent_blockhash, fee_calculator) = rpc_client.get_new_blockhash(&last_blockhash)?; + last_blockhash = recent_blockhash; + + let transaction = system_transaction::transfer(&config.keypair, &to, 1, recent_blockhash); + check_account_for_fee(rpc_client, config, &fee_calculator, &transaction.message)?; + + match rpc_client.send_transaction(&transaction) { + Ok(signature) => { + let transaction_sent = Instant::now(); + loop { + let signature_status = rpc_client.get_signature_status(&signature)?; + let elapsed_time = Instant::now().duration_since(transaction_sent); + if let Some(transaction_status) = signature_status { + match transaction_status { + Ok(()) => { + let elapsed_time_millis = elapsed_time.as_millis() as u64; + confirmation_time.push_back(elapsed_time_millis); + println!( + "{}1 lamport transferred: seq={:<3} time={:>4}ms signature={}", + CHECK_MARK, seq, elapsed_time_millis, signature + ); + confirmed_count += 1; + } + Err(err) => { + println!( + "{}Transaction failed: seq={:<3} error={:?} signature={}", + CROSS_MARK, seq, err, signature + ); + } + } + break; + } + + if elapsed_time >= *timeout { + println!( + "{}Confirmation timeout: seq={:<3} signature={}", + CROSS_MARK, seq, signature + ); + break; + } + + // Sleep for half a slot + if signal_receiver + .recv_timeout(Duration::from_millis( + 500 * solana_sdk::timing::DEFAULT_TICKS_PER_SLOT + / solana_sdk::timing::DEFAULT_NUM_TICKS_PER_SECOND, + )) + .is_ok() + { + break 'mainloop; + } + } + } + Err(err) => { + println!( + "{}Submit failed: seq={:<3} error={:?}", + CROSS_MARK, seq, err + ); + } + } + submit_count += 1; + + if signal_receiver.recv_timeout(*interval).is_ok() { + break 'mainloop; + } + } + + println!(); + println!("--- transaction statistics ---"); + println!( + "{} transactions submitted, {} transactions confirmed, {:.1}% transaction loss", + submit_count, + confirmed_count, + (100. - f64::from(confirmed_count) / f64::from(submit_count) * 100.) + ); + if !confirmation_time.is_empty() { + let samples: Vec = confirmation_time.iter().map(|t| *t as f64).collect(); + let dist = criterion_stats::Distribution::from(samples.into_boxed_slice()); + let mean = dist.mean(); + println!( + "confirmation min/mean/max/stddev = {:.0}/{:.0}/{:.0}/{:.0} ms", + dist.min(), + mean, + dist.max(), + dist.std_dev(Some(mean)) + ); + } + + Ok("".to_string()) +} + pub fn process_command(config: &WalletConfig) -> ProcessResult { if let WalletCommand::Address = config.command { // Get address of this client @@ -1361,6 +1500,12 @@ pub fn process_command(config: &WalletConfig) -> ProcessResult { *cancelable, ), + WalletCommand::Ping { + interval, + count, + timeout, + } => process_ping(&rpc_client, config, interval, count, timeout), + // Apply time elapsed to contract WalletCommand::TimeElapsed(to, pubkey, dt) => { process_time_elapsed(&rpc_client, config, &to, &pubkey, *dt) @@ -1927,6 +2072,36 @@ pub fn app<'ab, 'v>(name: &str, about: &'ab str, version: &'v str) -> App<'ab, ' .takes_value(false), ), ) + .subcommand( + SubCommand::with_name("ping") + .about("Submit transactions sequentially") + .arg( + Arg::with_name("interval") + .short("i") + .long("interval") + .value_name("SECONDS") + .takes_value(true) + .default_value("2") + .help("Wait interval seconds between submitting the next transaction"), + ) + .arg( + Arg::with_name("count") + .short("c") + .long("count") + .value_name("NUMBER") + .takes_value(true) + .help("Stop after submitting count transactions"), + ) + .arg( + Arg::with_name("timeout") + .short("t") + .long("timeout") + .value_name("SECONDS") + .takes_value(true) + .default_value("10") + .help("Wait up to timeout seconds for transaction confirmation"), + ) + ) .subcommand( SubCommand::with_name("send-signature") .about("Send a signature to authorize a transfer")