Skip to content

Commit

Permalink
Add preflight checks to sendTransaction RPC method (bp #10338) (#10362)
Browse files Browse the repository at this point in the history
automerge
  • Loading branch information
mergify[bot] authored Jun 2, 2020
1 parent a278f74 commit e023719
Show file tree
Hide file tree
Showing 13 changed files with 734 additions and 248 deletions.
119 changes: 112 additions & 7 deletions cli/src/cli.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use crate::{
cli_output::{CliAccount, CliSignOnlyData, CliSignature, OutputFormat},
cluster_query::*,
display::println_name_value,
display::{new_spinner_progress_bar, println_name_value, println_transaction},
nonce::{self, *},
offline::{blockhash_query::BlockhashQuery, *},
stake::*,
Expand All @@ -26,7 +26,7 @@ use solana_clap_utils::{
use solana_client::{
client_error::{ClientErrorKind, Result as ClientResult},
rpc_client::RpcClient,
rpc_config::RpcLargestAccountsFilter,
rpc_config::{RpcLargestAccountsFilter, RpcSendTransactionConfig},
rpc_response::{RpcAccount, RpcKeyedAccount},
};
#[cfg(not(test))]
Expand All @@ -36,7 +36,7 @@ use solana_faucet::faucet_mock::request_airdrop_transaction;
use solana_remote_wallet::remote_wallet::RemoteWalletManager;
use solana_sdk::{
bpf_loader,
clock::{Epoch, Slot},
clock::{Epoch, Slot, DEFAULT_TICKS_PER_SECOND},
commitment_config::CommitmentConfig,
fee_calculator::FeeCalculator,
hash::Hash,
Expand All @@ -47,6 +47,7 @@ use solana_sdk::{
program_utils::DecodeError,
pubkey::{Pubkey, MAX_SEED_LEN},
signature::{Keypair, Signature, Signer, SignerError},
signers::Signers,
system_instruction::{self, SystemError},
system_program,
transaction::{Transaction, TransactionError},
Expand Down Expand Up @@ -1231,7 +1232,7 @@ fn process_confirm(
"\nTransaction executed in slot {}:",
confirmed_transaction.slot
);
crate::display::println_transaction(
println_transaction(
&confirmed_transaction
.transaction
.transaction
Expand Down Expand Up @@ -1261,7 +1262,7 @@ fn process_confirm(
}

fn process_decode_transaction(transaction: &Transaction) -> ProcessResult {
crate::display::println_transaction(transaction, &None, "");
println_transaction(transaction, &None, "");
Ok("".to_string())
}

Expand Down Expand Up @@ -1299,6 +1300,103 @@ fn process_show_account(
Ok(account_string)
}

fn send_and_confirm_transactions_with_spinner<T: Signers>(
rpc_client: &RpcClient,
mut transactions: Vec<Transaction>,
signer_keys: &T,
) -> Result<(), Box<dyn error::Error>> {
let progress_bar = new_spinner_progress_bar();
let mut send_retries = 5;
loop {
let mut status_retries = 15;

// Send all transactions
let mut transactions_signatures = vec![];
let num_transactions = transactions.len();
for transaction in transactions {
if cfg!(not(test)) {
// Delay ~1 tick between write transactions in an attempt to reduce AccountInUse errors
// when all the write transactions modify the same program account (eg, deploying a
// new program)
sleep(Duration::from_millis(1000 / DEFAULT_TICKS_PER_SECOND));
}

let signature = rpc_client
.send_transaction_with_config(
&transaction,
RpcSendTransactionConfig {
skip_preflight: true,
},
)
.ok();
transactions_signatures.push((transaction, signature));

progress_bar.set_message(&format!(
"[{}/{}] Transactions sent",
transactions_signatures.len(),
num_transactions
));
}

// Collect statuses for all the transactions, drop those that are confirmed
while status_retries > 0 {
status_retries -= 1;

progress_bar.set_message(&format!(
"[{}/{}] Transactions confirmed",
num_transactions - transactions_signatures.len(),
num_transactions
));

if cfg!(not(test)) {
// Retry twice a second
sleep(Duration::from_millis(500));
}

transactions_signatures = transactions_signatures
.into_iter()
.filter(|(_transaction, signature)| {
if let Some(signature) = signature {
if let Ok(status) = rpc_client.get_signature_status(&signature) {
if rpc_client
.get_num_blocks_since_signature_confirmation(&signature)
.unwrap_or(0)
> 1
{
return false;
} else {
return match status {
None => true,
Some(result) => result.is_err(),
};
}
}
}
true
})
.collect();

if transactions_signatures.is_empty() {
return Ok(());
}
}

if send_retries == 0 {
return Err("Transactions failed".into());
}
send_retries -= 1;

// Re-sign any failed transactions with a new blockhash and retry
let (blockhash, _fee_calculator) = rpc_client
.get_new_blockhash(&transactions_signatures[0].0.message().recent_blockhash)?;
transactions = vec![];
for (mut transaction, _) in transactions_signatures.into_iter() {
transaction.try_sign(signer_keys, blockhash)?;
transactions.push(transaction);
}
}
}

fn process_deploy(
rpc_client: &RpcClient,
config: &CliConfig,
Expand Down Expand Up @@ -1366,11 +1464,18 @@ fn process_deploy(
})?;

trace!("Writing program data");
rpc_client.send_and_confirm_transactions(write_transactions, &signers)?;
send_and_confirm_transactions_with_spinner(&rpc_client, write_transactions, &signers).map_err(
|_| CliError::DynamicProgramError("Data writes to program account failed".to_string()),
)?;

trace!("Finalizing program account");
rpc_client
.send_and_confirm_transaction_with_spinner(&finalize_tx)
.send_and_confirm_transaction_with_spinner_and_config(
&finalize_tx,
RpcSendTransactionConfig {
skip_preflight: true,
},
)
.map_err(|e| {
CliError::DynamicProgramError(format!("Program finalize transaction failed: {}", e))
})?;
Expand Down
12 changes: 1 addition & 11 deletions cli/src/cluster_query.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
use crate::{
cli::{check_account_for_fee, CliCommand, CliCommandInfo, CliConfig, CliError, ProcessResult},
cli_output::*,
display::println_name_value,
display::{new_spinner_progress_bar, println_name_value},
};
use clap::{value_t, value_t_or_exit, App, AppSettings, Arg, ArgMatches, SubCommand};
use console::{style, Emoji};
use indicatif::{ProgressBar, ProgressStyle};
use solana_clap_utils::{
commitment::{commitment_arg, COMMITMENT_ARG},
input_parsers::*,
Expand Down Expand Up @@ -469,15 +468,6 @@ pub fn parse_transaction_history(
})
}

/// Creates a new process bar for processing that will take an unknown amount of time
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 process_catchup(
rpc_client: &RpcClient,
node_pubkey: &Pubkey,
Expand Down
10 changes: 10 additions & 0 deletions cli/src/display.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
use crate::cli::SettingType;
use console::style;
use indicatif::{ProgressBar, ProgressStyle};
use solana_sdk::{
hash::Hash, native_token::lamports_to_sol, program_utils::limited_deserialize,
transaction::Transaction,
Expand Down Expand Up @@ -200,3 +201,12 @@ pub fn println_transaction(
}
}
}

/// Creates a new process bar for processing that will take an unknown amount of time
pub 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
}
104 changes: 26 additions & 78 deletions client/src/rpc_client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ use crate::{
generic_rpc_client_request::GenericRpcClientRequest,
mock_rpc_client_request::{MockRpcClientRequest, Mocks},
rpc_client_request::RpcClientRequest,
rpc_config::RpcLargestAccountsConfig,
rpc_config::{RpcLargestAccountsConfig, RpcSendTransactionConfig},
rpc_request::{RpcError, RpcRequest},
rpc_response::*,
};
Expand Down Expand Up @@ -31,7 +31,6 @@ use solana_transaction_status::{
};
use solana_vote_program::vote_state::MAX_LOCKOUT_HISTORY;
use std::{
error,
net::SocketAddr,
thread::sleep,
time::{Duration, Instant},
Expand Down Expand Up @@ -95,10 +94,20 @@ impl RpcClient {
}

pub fn send_transaction(&self, transaction: &Transaction) -> ClientResult<Signature> {
self.send_transaction_with_config(transaction, RpcSendTransactionConfig::default())
}

pub fn send_transaction_with_config(
&self,
transaction: &Transaction,
config: RpcSendTransactionConfig,
) -> ClientResult<Signature> {
let serialized_encoded = bs58::encode(serialize(transaction).unwrap()).into_string();

let signature_base58_str: String =
self.send(RpcRequest::SendTransaction, json!([serialized_encoded]))?;
let signature_base58_str: String = self.send(
RpcRequest::SendTransaction,
json!([serialized_encoded, config]),
)?;

let signature = signature_base58_str
.parse::<Signature>()
Expand Down Expand Up @@ -407,74 +416,6 @@ impl RpcClient {
}
}

pub fn send_and_confirm_transactions<T: Signers>(
&self,
mut transactions: Vec<Transaction>,
signer_keys: &T,
) -> Result<(), Box<dyn error::Error>> {
let mut send_retries = 5;
loop {
let mut status_retries = 15;

// Send all transactions
let mut transactions_signatures = vec![];
for transaction in transactions {
if cfg!(not(test)) {
// Delay ~1 tick between write transactions in an attempt to reduce AccountInUse errors
// when all the write transactions modify the same program account (eg, deploying a
// new program)
sleep(Duration::from_millis(1000 / DEFAULT_TICKS_PER_SECOND));
}

let signature = self.send_transaction(&transaction).ok();
transactions_signatures.push((transaction, signature))
}

// Collect statuses for all the transactions, drop those that are confirmed
while status_retries > 0 {
status_retries -= 1;

if cfg!(not(test)) {
// Retry twice a second
sleep(Duration::from_millis(500));
}

transactions_signatures = transactions_signatures
.into_iter()
.filter(|(_transaction, signature)| {
if let Some(signature) = signature {
if let Ok(status) = self.get_signature_status(&signature) {
if status.is_none() {
return false;
}
return status.unwrap().is_err();
}
}
true
})
.collect();

if transactions_signatures.is_empty() {
return Ok(());
}
}

if send_retries == 0 {
return Err(RpcError::ForUser("Transactions failed".to_string()).into());
}
send_retries -= 1;

// Re-sign any failed transactions with a new blockhash and retry
let (blockhash, _fee_calculator) =
self.get_new_blockhash(&transactions_signatures[0].0.message().recent_blockhash)?;
transactions = vec![];
for (mut transaction, _) in transactions_signatures.into_iter() {
transaction.try_sign(signer_keys, blockhash)?;
transactions.push(transaction);
}
}
}

pub fn resign_transaction<T: Signers>(
&self,
tx: &mut Transaction,
Expand All @@ -486,11 +427,7 @@ impl RpcClient {
Ok(())
}

pub fn retry_get_balance(
&self,
pubkey: &Pubkey,
_retries: usize,
) -> Result<Option<u64>, Box<dyn error::Error>> {
pub fn retry_get_balance(&self, pubkey: &Pubkey, _retries: usize) -> ClientResult<Option<u64>> {
let request = RpcRequest::GetBalance;
let balance_json = self
.client
Expand Down Expand Up @@ -949,6 +886,17 @@ impl RpcClient {
pub fn send_and_confirm_transaction_with_spinner(
&self,
transaction: &Transaction,
) -> ClientResult<Signature> {
self.send_and_confirm_transaction_with_spinner_and_config(
transaction,
RpcSendTransactionConfig::default(),
)
}

pub fn send_and_confirm_transaction_with_spinner_and_config(
&self,
transaction: &Transaction,
config: RpcSendTransactionConfig,
) -> ClientResult<Signature> {
let mut confirmations = 0;

Expand All @@ -964,7 +912,7 @@ impl RpcClient {
));
let mut status_retries = 15;
let (signature, status) = loop {
let signature = self.send_transaction(transaction)?;
let signature = self.send_transaction_with_config(transaction, config.clone())?;

// Get recent commitment in order to count confirmations for successful transactions
let status = self
Expand Down
8 changes: 7 additions & 1 deletion client/src/rpc_config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,13 @@ pub struct RpcSignatureStatusConfig {
pub commitment: Option<CommitmentConfig>,
}

#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[derive(Debug, Default, Clone, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct RpcSendTransactionConfig {
pub skip_preflight: bool,
}

#[derive(Debug, Default, Clone, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct RpcSimulateTransactionConfig {
pub sig_verify: bool,
Expand Down
Loading

0 comments on commit e023719

Please sign in to comment.