diff --git a/cli/src/cli.rs b/cli/src/cli.rs index 8c1c9d992e..dd64614822 100644 --- a/cli/src/cli.rs +++ b/cli/src/cli.rs @@ -61,6 +61,7 @@ use std::{ use thiserror::Error; pub const DEFAULT_RPC_TIMEOUT_SECONDS: &str = "30"; +pub const DEFAULT_CONFIRM_TX_TIMEOUT_SECONDS: &str = "5"; #[derive(Debug, PartialEq)] #[allow(clippy::large_enum_variant)] @@ -450,6 +451,7 @@ pub struct CliConfig<'a> { pub output_format: OutputFormat, pub commitment: CommitmentConfig, pub send_transaction_config: RpcSendTransactionConfig, + pub confirm_transaction_initial_timeout: Duration, pub address_labels: HashMap, } @@ -594,6 +596,9 @@ impl Default for CliConfig<'_> { output_format: OutputFormat::Display, commitment: CommitmentConfig::confirmed(), send_transaction_config: RpcSendTransactionConfig::default(), + confirm_transaction_initial_timeout: Duration::from_secs( + u64::from_str(DEFAULT_CONFIRM_TX_TIMEOUT_SECONDS).unwrap(), + ), address_labels: HashMap::new(), } } @@ -1285,10 +1290,11 @@ pub fn process_command(config: &CliConfig) -> ProcessResult { } let rpc_client = if config.rpc_client.is_none() { - Arc::new(RpcClient::new_with_timeout_and_commitment( + Arc::new(RpcClient::new_with_timeouts_and_commitment( config.json_rpc_url.to_string(), config.rpc_timeout, config.commitment, + config.confirm_transaction_initial_timeout, )) } else { // Primarily for testing diff --git a/cli/src/main.rs b/cli/src/main.rs index 164a684dc9..e1749aece8 100644 --- a/cli/src/main.rs +++ b/cli/src/main.rs @@ -10,7 +10,7 @@ use solana_clap_utils::{ }; use solana_cli::cli::{ app, parse_command, process_command, CliCommandInfo, CliConfig, SettingType, - DEFAULT_RPC_TIMEOUT_SECONDS, + DEFAULT_CONFIRM_TX_TIMEOUT_SECONDS, DEFAULT_RPC_TIMEOUT_SECONDS, }; use solana_cli_config::{Config, CONFIG_FILE}; use solana_cli_output::{display::println_name_value, OutputFormat}; @@ -167,6 +167,11 @@ pub fn parse_args<'a>( let rpc_timeout = value_t_or_exit!(matches, "rpc_timeout", u64); let rpc_timeout = Duration::from_secs(rpc_timeout); + let confirm_transaction_initial_timeout = + value_t_or_exit!(matches, "confirm_transaction_initial_timeout", u64); + let confirm_transaction_initial_timeout = + Duration::from_secs(confirm_transaction_initial_timeout); + let (_, websocket_url) = CliConfig::compute_websocket_url_setting( matches.value_of("websocket_url").unwrap_or(""), &config.websocket_url, @@ -235,6 +240,7 @@ pub fn parse_args<'a>( preflight_commitment: Some(commitment.commitment), ..RpcSendTransactionConfig::default() }, + confirm_transaction_initial_timeout, address_labels, }, signers, @@ -350,6 +356,16 @@ fn main() -> Result<(), Box> { .hidden(true) .help("Timeout value for RPC requests"), ) + .arg( + Arg::with_name("confirm_transaction_initial_timeout") + .long("confirm-timeout") + .value_name("SECONDS") + .takes_value(true) + .default_value(DEFAULT_CONFIRM_TX_TIMEOUT_SECONDS) + .global(true) + .hidden(true) + .help("Timeout value for initial transaction status"), + ) .subcommand( SubCommand::with_name("config") .about("Solana command-line tool configuration settings") diff --git a/client/src/rpc_client.rs b/client/src/rpc_client.rs index bab867edb8..81580039b0 100644 --- a/client/src/rpc_client.rs +++ b/client/src/rpc_client.rs @@ -44,41 +44,36 @@ use { }, }; -pub struct RpcClient { - sender: Box, +#[derive(Default)] +pub struct RpcClientConfig { commitment_config: CommitmentConfig, - node_version: RwLock>, + confirm_transaction_initial_timeout: Option, } -fn serialize_encode_transaction( - transaction: &Transaction, - encoding: UiTransactionEncoding, -) -> ClientResult { - let serialized = serialize(transaction) - .map_err(|e| ClientErrorKind::Custom(format!("transaction serialization failed: {}", e)))?; - let encoded = match encoding { - UiTransactionEncoding::Base58 => bs58::encode(serialized).into_string(), - UiTransactionEncoding::Base64 => base64::encode(serialized), - _ => { - return Err(ClientErrorKind::Custom(format!( - "unsupported transaction encoding: {}. Supported encodings: base58, base64", - encoding - )) - .into()) +impl RpcClientConfig { + fn with_commitment(commitment_config: CommitmentConfig) -> Self { + RpcClientConfig { + commitment_config, + ..Self::default() } - }; - Ok(encoded) + } +} + +pub struct RpcClient { + sender: Box, + config: RpcClientConfig, + node_version: RwLock>, } impl RpcClient { fn new_sender( sender: T, - commitment_config: CommitmentConfig, + config: RpcClientConfig, ) -> Self { Self { sender: Box::new(sender), node_version: RwLock::new(None), - commitment_config, + config, } } @@ -87,13 +82,16 @@ impl RpcClient { } pub fn new_with_commitment(url: String, commitment_config: CommitmentConfig) -> Self { - Self::new_sender(HttpSender::new(url), commitment_config) + Self::new_sender( + HttpSender::new(url), + RpcClientConfig::with_commitment(commitment_config), + ) } pub fn new_with_timeout(url: String, timeout: Duration) -> Self { Self::new_sender( HttpSender::new_with_timeout(url, timeout), - CommitmentConfig::default(), + RpcClientConfig::with_commitment(CommitmentConfig::default()), ) } @@ -104,18 +102,36 @@ impl RpcClient { ) -> Self { Self::new_sender( HttpSender::new_with_timeout(url, timeout), - commitment_config, + RpcClientConfig::with_commitment(commitment_config), + ) + } + + pub fn new_with_timeouts_and_commitment( + url: String, + timeout: Duration, + commitment_config: CommitmentConfig, + confirm_transaction_initial_timeout: Duration, + ) -> Self { + Self::new_sender( + HttpSender::new_with_timeout(url, timeout), + RpcClientConfig { + commitment_config, + confirm_transaction_initial_timeout: Some(confirm_transaction_initial_timeout), + }, ) } pub fn new_mock(url: String) -> Self { - Self::new_sender(MockSender::new(url), CommitmentConfig::default()) + Self::new_sender( + MockSender::new(url), + RpcClientConfig::with_commitment(CommitmentConfig::default()), + ) } pub fn new_mock_with_mocks(url: String, mocks: Mocks) -> Self { Self::new_sender( MockSender::new_with_mocks(url, mocks), - CommitmentConfig::default(), + RpcClientConfig::with_commitment(CommitmentConfig::default()), ) } @@ -154,7 +170,7 @@ impl RpcClient { } pub fn commitment(&self) -> CommitmentConfig { - self.commitment_config + self.config.commitment_config } fn use_deprecated_commitment(&self) -> Result { @@ -179,7 +195,7 @@ impl RpcClient { pub fn confirm_transaction(&self, signature: &Signature) -> ClientResult { Ok(self - .confirm_transaction_with_commitment(signature, self.commitment_config)? + .confirm_transaction_with_commitment(signature, self.commitment())? .value) } @@ -205,8 +221,7 @@ impl RpcClient { transaction, RpcSendTransactionConfig { preflight_commitment: Some( - self.maybe_map_commitment(self.commitment_config)? - .commitment, + self.maybe_map_commitment(self.commitment())?.commitment, ), ..RpcSendTransactionConfig::default() }, @@ -295,7 +310,7 @@ impl RpcClient { self.simulate_transaction_with_config( transaction, RpcSimulateTransactionConfig { - commitment: Some(self.commitment_config), + commitment: Some(self.commitment()), ..RpcSimulateTransactionConfig::default() }, ) @@ -333,7 +348,7 @@ impl RpcClient { &self, signature: &Signature, ) -> ClientResult>> { - self.get_signature_status_with_commitment(signature, self.commitment_config) + self.get_signature_status_with_commitment(signature, self.commitment()) } pub fn get_signature_statuses( @@ -391,7 +406,7 @@ impl RpcClient { } pub fn get_slot(&self) -> ClientResult { - self.get_slot_with_commitment(self.commitment_config) + self.get_slot_with_commitment(self.commitment()) } pub fn get_slot_with_commitment( @@ -405,7 +420,7 @@ impl RpcClient { } pub fn get_block_height(&self) -> ClientResult { - self.get_block_height_with_commitment(self.commitment_config) + self.get_block_height_with_commitment(self.commitment()) } pub fn get_block_height_with_commitment( @@ -459,14 +474,14 @@ impl RpcClient { stake_account.to_string(), RpcEpochConfig { epoch, - commitment: Some(self.commitment_config), + commitment: Some(self.commitment()), } ]), ) } pub fn supply(&self) -> RpcResult { - self.supply_with_commitment(self.commitment_config) + self.supply_with_commitment(self.commitment()) } pub fn supply_with_commitment( @@ -482,7 +497,7 @@ impl RpcClient { #[deprecated(since = "1.5.19", note = "Please use RpcClient::supply() instead")] #[allow(deprecated)] pub fn total_supply(&self) -> ClientResult { - self.total_supply_with_commitment(self.commitment_config) + self.total_supply_with_commitment(self.commitment()) } #[deprecated( @@ -514,7 +529,7 @@ impl RpcClient { } pub fn get_vote_accounts(&self) -> ClientResult { - self.get_vote_accounts_with_commitment(self.commitment_config) + self.get_vote_accounts_with_commitment(self.commitment()) } pub fn get_vote_accounts_with_commitment( @@ -743,7 +758,7 @@ impl RpcClient { } pub fn get_epoch_info(&self) -> ClientResult { - self.get_epoch_info_with_commitment(self.commitment_config) + self.get_epoch_info_with_commitment(self.commitment()) } pub fn get_epoch_info_with_commitment( @@ -760,7 +775,7 @@ impl RpcClient { &self, slot: Option, ) -> ClientResult> { - self.get_leader_schedule_with_commitment(slot, self.commitment_config) + self.get_leader_schedule_with_commitment(slot, self.commitment()) } pub fn get_leader_schedule_with_commitment( @@ -830,7 +845,7 @@ impl RpcClient { addresses, RpcEpochConfig { epoch, - commitment: Some(self.commitment_config), + commitment: Some(self.commitment()), } ]), ) @@ -896,7 +911,7 @@ impl RpcClient { /// Note that `get_account` returns `Err(..)` if the account does not exist whereas /// `get_account_with_commitment` returns `Ok(None)` if the account does not exist. pub fn get_account(&self, pubkey: &Pubkey) -> ClientResult { - self.get_account_with_commitment(pubkey, self.commitment_config)? + self.get_account_with_commitment(pubkey, self.commitment())? .value .ok_or_else(|| RpcError::ForUser(format!("AccountNotFound: pubkey={}", pubkey)).into()) } @@ -952,7 +967,7 @@ impl RpcClient { pub fn get_multiple_accounts(&self, pubkeys: &[Pubkey]) -> ClientResult>> { Ok(self - .get_multiple_accounts_with_commitment(pubkeys, self.commitment_config)? + .get_multiple_accounts_with_commitment(pubkeys, self.commitment())? .value) } @@ -1006,7 +1021,7 @@ impl RpcClient { /// Request the balance of the account `pubkey`. pub fn get_balance(&self, pubkey: &Pubkey) -> ClientResult { Ok(self - .get_balance_with_commitment(pubkey, self.commitment_config)? + .get_balance_with_commitment(pubkey, self.commitment())? .value) } @@ -1064,7 +1079,7 @@ impl RpcClient { /// Request the transaction count. pub fn get_transaction_count(&self) -> ClientResult { - self.get_transaction_count_with_commitment(self.commitment_config) + self.get_transaction_count_with_commitment(self.commitment()) } pub fn get_transaction_count_with_commitment( @@ -1079,7 +1094,7 @@ impl RpcClient { pub fn get_recent_blockhash(&self) -> ClientResult<(Hash, FeeCalculator)> { let (blockhash, fee_calculator, _last_valid_slot) = self - .get_recent_blockhash_with_commitment(self.commitment_config)? + .get_recent_blockhash_with_commitment(self.commitment())? .value; Ok((blockhash, fee_calculator)) } @@ -1152,7 +1167,7 @@ impl RpcClient { blockhash: &Hash, ) -> ClientResult> { Ok(self - .get_fee_calculator_for_blockhash_with_commitment(blockhash, self.commitment_config)? + .get_fee_calculator_for_blockhash_with_commitment(blockhash, self.commitment())? .value) } @@ -1234,7 +1249,7 @@ impl RpcClient { pub fn get_token_account(&self, pubkey: &Pubkey) -> ClientResult> { Ok(self - .get_token_account_with_commitment(pubkey, self.commitment_config)? + .get_token_account_with_commitment(pubkey, self.commitment())? .value) } @@ -1295,7 +1310,7 @@ impl RpcClient { pub fn get_token_account_balance(&self, pubkey: &Pubkey) -> ClientResult { Ok(self - .get_token_account_balance_with_commitment(pubkey, self.commitment_config)? + .get_token_account_balance_with_commitment(pubkey, self.commitment())? .value) } @@ -1322,7 +1337,7 @@ impl RpcClient { .get_token_accounts_by_delegate_with_commitment( delegate, token_account_filter, - self.commitment_config, + self.commitment(), )? .value) } @@ -1361,7 +1376,7 @@ impl RpcClient { .get_token_accounts_by_owner_with_commitment( owner, token_account_filter, - self.commitment_config, + self.commitment(), )? .value) } @@ -1393,7 +1408,7 @@ impl RpcClient { pub fn get_token_supply(&self, mint: &Pubkey) -> ClientResult { Ok(self - .get_token_supply_with_commitment(mint, self.commitment_config)? + .get_token_supply_with_commitment(mint, self.commitment())? .value) } @@ -1416,7 +1431,7 @@ impl RpcClient { pubkey, lamports, RpcRequestAirdropConfig { - commitment: Some(self.commitment_config), + commitment: Some(self.commitment()), ..RpcRequestAirdropConfig::default() }, ) @@ -1432,7 +1447,7 @@ impl RpcClient { pubkey, lamports, RpcRequestAirdropConfig { - commitment: Some(self.commitment_config), + commitment: Some(self.commitment()), recent_blockhash: Some(recent_blockhash.to_string()), }, ) @@ -1535,7 +1550,7 @@ impl RpcClient { /// Poll the server to confirm a transaction. pub fn poll_for_signature(&self, signature: &Signature) -> ClientResult<()> { - self.poll_for_signature_with_commitment(signature, self.commitment_config) + self.poll_for_signature_with_commitment(signature, self.commitment()) } /// Poll the server to confirm a transaction. @@ -1645,7 +1660,7 @@ impl RpcClient { ) -> ClientResult { self.send_and_confirm_transaction_with_spinner_and_commitment( transaction, - self.commitment_config, + self.commitment(), ) } @@ -1701,19 +1716,25 @@ impl RpcClient { "[{}/{}] Finalizing transaction {}", confirmations, desired_confirmations, signature, )); + + let now = Instant::now(); + let confirm_transaction_initial_timeout = self + .config + .confirm_transaction_initial_timeout + .unwrap_or_default(); let (signature, status) = loop { // Get recent commitment in order to count confirmations for successful transactions let status = self .get_signature_status_with_commitment(&signature, CommitmentConfig::processed())?; if status.is_none() { - if self + let blockhash_not_found = self .get_fee_calculator_for_blockhash_with_commitment( &recent_blockhash, CommitmentConfig::processed(), )? .value - .is_none() - { + .is_none(); + if blockhash_not_found && now.elapsed() >= confirm_transaction_initial_timeout { break (signature, status); } } else { @@ -1784,6 +1805,26 @@ impl RpcClient { } } +fn serialize_encode_transaction( + transaction: &Transaction, + encoding: UiTransactionEncoding, +) -> ClientResult { + let serialized = serialize(transaction) + .map_err(|e| ClientErrorKind::Custom(format!("transaction serialization failed: {}", e)))?; + let encoded = match encoding { + UiTransactionEncoding::Base58 => bs58::encode(serialized).into_string(), + UiTransactionEncoding::Base64 => base64::encode(serialized), + _ => { + return Err(ClientErrorKind::Custom(format!( + "unsupported transaction encoding: {}. Supported encodings: base58, base64", + encoding + )) + .into()) + } + }; + Ok(encoded) +} + #[derive(Debug, Default)] pub struct GetConfirmedSignaturesForAddress2Config { pub before: Option,