diff --git a/Cargo.lock b/Cargo.lock index dc4c44ad7a..2dc077f08c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3883,6 +3883,7 @@ dependencies = [ "assert_matches 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)", "bincode 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)", "bs58 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", + "indicatif 0.14.0 (registry+https://github.com/rust-lang/crates.io-index)", "jsonrpc-core 14.0.5 (registry+https://github.com/rust-lang/crates.io-index)", "jsonrpc-http-server 14.0.6 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", @@ -3894,6 +3895,7 @@ dependencies = [ "solana-logger 1.1.0", "solana-net-utils 1.1.0", "solana-sdk 1.1.0", + "solana-vote-program 1.1.0", "thiserror 1.0.11 (registry+https://github.com/rust-lang/crates.io-index)", "tungstenite 0.10.1 (registry+https://github.com/rust-lang/crates.io-index)", "url 2.1.1 (registry+https://github.com/rust-lang/crates.io-index)", diff --git a/cli/src/cli.rs b/cli/src/cli.rs index e4c094a9a9..b8599ca720 100644 --- a/cli/src/cli.rs +++ b/cli/src/cli.rs @@ -1255,7 +1255,8 @@ fn process_deploy( )?; trace!("Creating program account"); - let result = rpc_client.send_and_confirm_transaction(&mut create_account_tx, &signers); + let result = + rpc_client.send_and_confirm_transaction_with_spinner(&mut create_account_tx, &signers); log_instruction_custom_error::(result) .map_err(|_| CliError::DynamicProgramError("Program allocate space failed".to_string()))?; @@ -1264,7 +1265,7 @@ fn process_deploy( trace!("Finalizing program account"); rpc_client - .send_and_confirm_transaction(&mut finalize_tx, &signers) + .send_and_confirm_transaction_with_spinner(&mut finalize_tx, &signers) .map_err(|_| { CliError::DynamicProgramError("Program finalize transaction failed".to_string()) })?; @@ -1328,7 +1329,8 @@ fn process_pay( &fee_calculator, &tx.message, )?; - let result = rpc_client.send_and_confirm_transaction(&mut tx, &config.signers); + let result = + rpc_client.send_and_confirm_transaction_with_spinner(&mut tx, &config.signers); log_instruction_custom_error::(result) } } else if *witnesses == None { @@ -1362,8 +1364,10 @@ fn process_pay( &fee_calculator, &tx.message, )?; - let result = rpc_client - .send_and_confirm_transaction(&mut tx, &[config.signers[0], &contract_state]); + let result = rpc_client.send_and_confirm_transaction_with_spinner( + &mut tx, + &[config.signers[0], &contract_state], + ); let signature_str = log_instruction_custom_error::(result)?; Ok(json!({ @@ -1399,8 +1403,10 @@ fn process_pay( if sign_only { return_signers(&tx) } else { - let result = rpc_client - .send_and_confirm_transaction(&mut tx, &[config.signers[0], &contract_state]); + let result = rpc_client.send_and_confirm_transaction_with_spinner( + &mut tx, + &[config.signers[0], &contract_state], + ); check_account_for_fee( rpc_client, &config.signers[0].pubkey(), @@ -1436,7 +1442,8 @@ fn process_cancel(rpc_client: &RpcClient, config: &CliConfig, pubkey: &Pubkey) - &fee_calculator, &tx.message, )?; - let result = rpc_client.send_and_confirm_transaction(&mut tx, &[config.signers[0]]); + let result = + rpc_client.send_and_confirm_transaction_with_spinner(&mut tx, &[config.signers[0]]); log_instruction_custom_error::(result) } @@ -1459,7 +1466,8 @@ fn process_time_elapsed( &fee_calculator, &tx.message, )?; - let result = rpc_client.send_and_confirm_transaction(&mut tx, &[config.signers[0]]); + let result = + rpc_client.send_and_confirm_transaction_with_spinner(&mut tx, &[config.signers[0]]); log_instruction_custom_error::(result) } @@ -1516,7 +1524,7 @@ fn process_transfer( &fee_calculator, &tx.message, )?; - let result = rpc_client.send_and_confirm_transaction(&mut tx, &config.signers); + let result = rpc_client.send_and_confirm_transaction_with_spinner(&mut tx, &config.signers); log_instruction_custom_error::(result) } } @@ -1539,7 +1547,8 @@ fn process_witness( &fee_calculator, &tx.message, )?; - let result = rpc_client.send_and_confirm_transaction(&mut tx, &[config.signers[0]]); + let result = + rpc_client.send_and_confirm_transaction_with_spinner(&mut tx, &[config.signers[0]]); log_instruction_custom_error::(result) } @@ -2146,7 +2155,7 @@ pub fn request_and_confirm_airdrop( } }?; let mut tx = keypair.airdrop_transaction(); - let result = rpc_client.send_and_confirm_transaction(&mut tx, &[&keypair]); + let result = rpc_client.send_and_confirm_transaction_with_spinner(&mut tx, &[&keypair]); log_instruction_custom_error::(result) } diff --git a/cli/src/nonce.rs b/cli/src/nonce.rs index 566f1f55de..97e1c465df 100644 --- a/cli/src/nonce.rs +++ b/cli/src/nonce.rs @@ -460,7 +460,7 @@ pub fn process_authorize_nonce_account( &fee_calculator, &tx.message, )?; - let result = rpc_client.send_and_confirm_transaction(&mut tx, &config.signers); + let result = rpc_client.send_and_confirm_transaction_with_spinner(&mut tx, &config.signers); log_instruction_custom_error::(result) } @@ -537,7 +537,7 @@ pub fn process_create_nonce_account( &fee_calculator, &tx.message, )?; - let result = rpc_client.send_and_confirm_transaction(&mut tx, &config.signers); + let result = rpc_client.send_and_confirm_transaction_with_spinner(&mut tx, &config.signers); log_instruction_custom_error::(result) } @@ -578,8 +578,8 @@ pub fn process_new_nonce( &fee_calculator, &tx.message, )?; - let result = - rpc_client.send_and_confirm_transaction(&mut tx, &[config.signers[0], nonce_authority]); + let result = rpc_client + .send_and_confirm_transaction_with_spinner(&mut tx, &[config.signers[0], nonce_authority]); log_instruction_custom_error::(result) } @@ -651,7 +651,7 @@ pub fn process_withdraw_from_nonce_account( &fee_calculator, &tx.message, )?; - let result = rpc_client.send_and_confirm_transaction(&mut tx, &config.signers); + let result = rpc_client.send_and_confirm_transaction_with_spinner(&mut tx, &config.signers); log_instruction_custom_error::(result) } diff --git a/cli/src/stake.rs b/cli/src/stake.rs index 9abbda47f0..1680310fac 100644 --- a/cli/src/stake.rs +++ b/cli/src/stake.rs @@ -860,7 +860,7 @@ pub fn process_create_stake_account( &fee_calculator, &tx.message, )?; - let result = rpc_client.send_and_confirm_transaction(&mut tx, &config.signers); + let result = rpc_client.send_and_confirm_transaction_with_spinner(&mut tx, &config.signers); log_instruction_custom_error::(result) } } @@ -922,7 +922,7 @@ pub fn process_stake_authorize( &fee_calculator, &tx.message, )?; - let result = rpc_client.send_and_confirm_transaction(&mut tx, &config.signers); + let result = rpc_client.send_and_confirm_transaction_with_spinner(&mut tx, &config.signers); log_instruction_custom_error::(result) } } @@ -975,7 +975,7 @@ pub fn process_deactivate_stake_account( &fee_calculator, &tx.message, )?; - let result = rpc_client.send_and_confirm_transaction(&mut tx, &config.signers); + let result = rpc_client.send_and_confirm_transaction_with_spinner(&mut tx, &config.signers); log_instruction_custom_error::(result) } } @@ -1034,7 +1034,7 @@ pub fn process_withdraw_stake( &fee_calculator, &tx.message, )?; - let result = rpc_client.send_and_confirm_transaction(&mut tx, &config.signers); + let result = rpc_client.send_and_confirm_transaction_with_spinner(&mut tx, &config.signers); log_instruction_custom_error::(result) } } @@ -1167,7 +1167,7 @@ pub fn process_split_stake( &fee_calculator, &tx.message, )?; - let result = rpc_client.send_and_confirm_transaction(&mut tx, &config.signers); + let result = rpc_client.send_and_confirm_transaction_with_spinner(&mut tx, &config.signers); log_instruction_custom_error::(result) } } @@ -1223,7 +1223,7 @@ pub fn process_stake_set_lockup( &fee_calculator, &tx.message, )?; - let result = rpc_client.send_and_confirm_transaction(&mut tx, &config.signers); + let result = rpc_client.send_and_confirm_transaction_with_spinner(&mut tx, &config.signers); log_instruction_custom_error::(result) } } @@ -1457,7 +1457,7 @@ pub fn process_delegate_stake( &fee_calculator, &tx.message, )?; - let result = rpc_client.send_and_confirm_transaction(&mut tx, &config.signers); + let result = rpc_client.send_and_confirm_transaction_with_spinner(&mut tx, &config.signers); log_instruction_custom_error::(result) } } diff --git a/cli/src/storage.rs b/cli/src/storage.rs index 3a20cb6a4a..7f543da3c1 100644 --- a/cli/src/storage.rs +++ b/cli/src/storage.rs @@ -242,7 +242,7 @@ pub fn process_create_storage_account( &fee_calculator, &tx.message, )?; - let result = rpc_client.send_and_confirm_transaction(&mut tx, &config.signers); + let result = rpc_client.send_and_confirm_transaction_with_spinner(&mut tx, &config.signers); log_instruction_custom_error::(result) } @@ -266,7 +266,7 @@ pub fn process_claim_storage_reward( &fee_calculator, &tx.message, )?; - let signature_str = rpc_client.send_and_confirm_transaction(&mut tx, &signers)?; + let signature_str = rpc_client.send_and_confirm_transaction_with_spinner(&mut tx, &signers)?; Ok(signature_str) } diff --git a/cli/src/validator_info.rs b/cli/src/validator_info.rs index 672f1bee09..ba92767d5c 100644 --- a/cli/src/validator_info.rs +++ b/cli/src/validator_info.rs @@ -368,7 +368,7 @@ pub fn process_set_validator_info( &fee_calculator, &tx.message, )?; - let signature_str = rpc_client.send_and_confirm_transaction(&mut tx, &signers)?; + let signature_str = rpc_client.send_and_confirm_transaction_with_spinner(&mut tx, &signers)?; println!("Success! Validator info published at: {:?}", info_pubkey); println!("{}", signature_str); diff --git a/cli/src/vote.rs b/cli/src/vote.rs index cb8c96ed78..86f8ac7926 100644 --- a/cli/src/vote.rs +++ b/cli/src/vote.rs @@ -445,7 +445,7 @@ pub fn process_create_vote_account( &fee_calculator, &tx.message, )?; - let result = rpc_client.send_and_confirm_transaction(&mut tx, &config.signers); + let result = rpc_client.send_and_confirm_transaction_with_spinner(&mut tx, &config.signers); log_instruction_custom_error::(result) } @@ -477,7 +477,8 @@ pub fn process_vote_authorize( &fee_calculator, &tx.message, )?; - let result = rpc_client.send_and_confirm_transaction(&mut tx, &[config.signers[0]]); + let result = + rpc_client.send_and_confirm_transaction_with_spinner(&mut tx, &[config.signers[0]]); log_instruction_custom_error::(result) } @@ -508,7 +509,7 @@ pub fn process_vote_update_validator( &fee_calculator, &tx.message, )?; - let result = rpc_client.send_and_confirm_transaction(&mut tx, &config.signers); + let result = rpc_client.send_and_confirm_transaction_with_spinner(&mut tx, &config.signers); log_instruction_custom_error::(result) } @@ -621,7 +622,8 @@ pub fn process_withdraw_from_vote_account( &fee_calculator, &transaction.message, )?; - let result = rpc_client.send_and_confirm_transaction(&mut transaction, &config.signers); + let result = + rpc_client.send_and_confirm_transaction_with_spinner(&mut transaction, &config.signers); log_instruction_custom_error::(result) } diff --git a/client/Cargo.toml b/client/Cargo.toml index 7d2c9aef96..1138cbb165 100644 --- a/client/Cargo.toml +++ b/client/Cargo.toml @@ -11,6 +11,7 @@ edition = "2018" [dependencies] bincode = "1.2.1" bs58 = "0.3.0" +indicatif = "0.14.0" jsonrpc-core = "14.0.5" log = "0.4.8" rayon = "1.3.0" @@ -20,6 +21,7 @@ serde_derive = "1.0.103" serde_json = "1.0.48" solana-net-utils = { path = "../net-utils", version = "1.1.0" } solana-sdk = { path = "../sdk", version = "1.1.0" } +solana-vote-program = { path = "../programs/vote", version = "1.1.0" } thiserror = "1.0" tungstenite = "0.10.1" url = "2.1.1" diff --git a/client/src/rpc_client.rs b/client/src/rpc_client.rs index 96732fe0be..61c7561025 100644 --- a/client/src/rpc_client.rs +++ b/client/src/rpc_client.rs @@ -1,5 +1,5 @@ use crate::{ - client_error::{ClientError, Result as ClientResult}, + client_error::{ClientError, ClientErrorKind, Result as ClientResult}, generic_rpc_client_request::GenericRpcClientRequest, mock_rpc_client_request::{MockRpcClientRequest, Mocks}, rpc_client_request::RpcClientRequest, @@ -11,6 +11,7 @@ use crate::{ }, }; use bincode::serialize; +use indicatif::{ProgressBar, ProgressStyle}; use log::*; use serde_json::{json, Value}; use solana_sdk::{ @@ -26,9 +27,11 @@ use solana_sdk::{ signers::Signers, transaction::{self, Transaction, TransactionError}, }; +use solana_vote_program::vote_state::MAX_LOCKOUT_HISTORY; use std::{ error, net::SocketAddr, + str::FromStr, thread::sleep, time::{Duration, Instant}, }; @@ -946,6 +949,93 @@ impl RpcClient { }) } + pub fn send_and_confirm_transaction_with_spinner( + &self, + transaction: &mut Transaction, + signer_keys: &T, + ) -> ClientResult { + let mut send_retries = 20; + let signature_str = loop { + let mut status_retries = 15; + let (signature_str, status) = loop { + let signature_str = self.send_transaction(transaction)?; + + // Get recent commitment in order to count confirmations for successful transactions + let status = self.get_signature_status_with_commitment( + &signature_str, + CommitmentConfig::recent(), + )?; + if status.is_none() { + status_retries -= 1; + if status_retries == 0 { + break (signature_str, status); + } + } else { + break (signature_str, status); + } + + if cfg!(not(test)) { + sleep(Duration::from_millis(500)); + } + }; + send_retries = if let Some(result) = status.clone() { + match result { + Ok(_) => 0, + Err(TransactionError::AccountInUse) => { + // Fetch a new blockhash and re-sign the transaction before sending it again + self.resign_transaction(transaction, signer_keys)?; + send_retries - 1 + } + // If transaction errors, return right away; no point in counting confirmations + Err(_) => 0, + } + } else { + send_retries - 1 + }; + if send_retries == 0 { + if let Some(result) = status { + match result { + Ok(_) => { + break signature_str; + } + Err(err) => { + return Err(err.into()); + } + } + } else { + return Err( + RpcError::ForUser("unable to confirm transaction. This can happen in situations such as transaction expiration and insufficient fee-payer funds".to_string()).into(), + ); + } + } + }; + + let progress_bar = new_spinner_progress_bar(); + progress_bar.set_message("Confirming..."); + let mut confirmations = 0; + let signature = Signature::from_str(&signature_str).map_err(|_| { + ClientError::from(ClientErrorKind::Custom(format!( + "Returned string {} cannot be parsed as a signature", + signature_str + ))) + })?; + loop { + // Return when default (max) commitment is reached + // Failed transactions have already been eliminated, `is_some` check is sufficient + if self.get_signature_status(&signature_str)?.is_some() { + progress_bar.set_message("Transaction confirmed"); + progress_bar.finish_and_clear(); + return Ok(signature_str); + } + progress_bar.set_message(&format!( + "[{}/{}] Waiting for confirmations", + confirmations, MAX_LOCKOUT_HISTORY, + )); + sleep(Duration::from_millis(500)); + confirmations = self.get_num_blocks_since_signature_confirmation(&signature)?; + } + } + pub fn validator_exit(&self) -> ClientResult { let response = self .client @@ -961,6 +1051,14 @@ impl RpcClient { } } +fn new_spinner_progress_bar() -> ProgressBar { + let progress_bar = ProgressBar::new(42); + progress_bar + .set_style(ProgressStyle::default_spinner().template("{spinner:.green} {wide_msg}")); + progress_bar.enable_steady_tick(100); + progress_bar +} + pub fn get_rpc_request_str(rpc_addr: SocketAddr, tls: bool) -> String { if tls { format!("https://{}", rpc_addr)