Use timeout to allow RpcClient to retry initial transaction confirmation (backport #18311) (#18315)

* Use timeout to allow RpcClient to retry initial transaction confirmation (#18311)

* Tidying: relocate function

* Use proper helper method for RpcClient commitment

* Add RpcClientConfig

* Add configurable confirm_transaction_initial_timeout

* Use default 5s timeout for initial tx confirmation

(cherry picked from commit 9d4428d3d8)

* Fixup deprecated method

Co-authored-by: Tyera Eulberg <teulberg@gmail.com>
Co-authored-by: Tyera Eulberg <tyera@solana.com>
This commit is contained in:
mergify[bot]
2021-06-30 04:51:11 +00:00
committed by GitHub
parent 5380623f32
commit 56a4fc3dd2
3 changed files with 125 additions and 62 deletions

View File

@ -61,6 +61,7 @@ use std::{
use thiserror::Error; use thiserror::Error;
pub const DEFAULT_RPC_TIMEOUT_SECONDS: &str = "30"; pub const DEFAULT_RPC_TIMEOUT_SECONDS: &str = "30";
pub const DEFAULT_CONFIRM_TX_TIMEOUT_SECONDS: &str = "5";
#[derive(Debug, PartialEq)] #[derive(Debug, PartialEq)]
#[allow(clippy::large_enum_variant)] #[allow(clippy::large_enum_variant)]
@ -450,6 +451,7 @@ pub struct CliConfig<'a> {
pub output_format: OutputFormat, pub output_format: OutputFormat,
pub commitment: CommitmentConfig, pub commitment: CommitmentConfig,
pub send_transaction_config: RpcSendTransactionConfig, pub send_transaction_config: RpcSendTransactionConfig,
pub confirm_transaction_initial_timeout: Duration,
pub address_labels: HashMap<String, String>, pub address_labels: HashMap<String, String>,
} }
@ -594,6 +596,9 @@ impl Default for CliConfig<'_> {
output_format: OutputFormat::Display, output_format: OutputFormat::Display,
commitment: CommitmentConfig::confirmed(), commitment: CommitmentConfig::confirmed(),
send_transaction_config: RpcSendTransactionConfig::default(), 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(), address_labels: HashMap::new(),
} }
} }
@ -1285,10 +1290,11 @@ pub fn process_command(config: &CliConfig) -> ProcessResult {
} }
let rpc_client = if config.rpc_client.is_none() { 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.json_rpc_url.to_string(),
config.rpc_timeout, config.rpc_timeout,
config.commitment, config.commitment,
config.confirm_transaction_initial_timeout,
)) ))
} else { } else {
// Primarily for testing // Primarily for testing

View File

@ -10,7 +10,7 @@ use solana_clap_utils::{
}; };
use solana_cli::cli::{ use solana_cli::cli::{
app, parse_command, process_command, CliCommandInfo, CliConfig, SettingType, 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_config::{Config, CONFIG_FILE};
use solana_cli_output::{display::println_name_value, OutputFormat}; 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 = value_t_or_exit!(matches, "rpc_timeout", u64);
let rpc_timeout = Duration::from_secs(rpc_timeout); 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( let (_, websocket_url) = CliConfig::compute_websocket_url_setting(
matches.value_of("websocket_url").unwrap_or(""), matches.value_of("websocket_url").unwrap_or(""),
&config.websocket_url, &config.websocket_url,
@ -235,6 +240,7 @@ pub fn parse_args<'a>(
preflight_commitment: Some(commitment.commitment), preflight_commitment: Some(commitment.commitment),
..RpcSendTransactionConfig::default() ..RpcSendTransactionConfig::default()
}, },
confirm_transaction_initial_timeout,
address_labels, address_labels,
}, },
signers, signers,
@ -350,6 +356,16 @@ fn main() -> Result<(), Box<dyn error::Error>> {
.hidden(true) .hidden(true)
.help("Timeout value for RPC requests"), .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(
SubCommand::with_name("config") SubCommand::with_name("config")
.about("Solana command-line tool configuration settings") .about("Solana command-line tool configuration settings")

View File

@ -44,41 +44,36 @@ use {
}, },
}; };
pub struct RpcClient { #[derive(Default)]
sender: Box<dyn RpcSender + Send + Sync + 'static>, pub struct RpcClientConfig {
commitment_config: CommitmentConfig, commitment_config: CommitmentConfig,
node_version: RwLock<Option<semver::Version>>, confirm_transaction_initial_timeout: Option<Duration>,
} }
fn serialize_encode_transaction( impl RpcClientConfig {
transaction: &Transaction, fn with_commitment(commitment_config: CommitmentConfig) -> Self {
encoding: UiTransactionEncoding, RpcClientConfig {
) -> ClientResult<String> { commitment_config,
let serialized = serialize(transaction) ..Self::default()
.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) }
pub struct RpcClient {
sender: Box<dyn RpcSender + Send + Sync + 'static>,
config: RpcClientConfig,
node_version: RwLock<Option<semver::Version>>,
} }
impl RpcClient { impl RpcClient {
fn new_sender<T: RpcSender + Send + Sync + 'static>( fn new_sender<T: RpcSender + Send + Sync + 'static>(
sender: T, sender: T,
commitment_config: CommitmentConfig, config: RpcClientConfig,
) -> Self { ) -> Self {
Self { Self {
sender: Box::new(sender), sender: Box::new(sender),
node_version: RwLock::new(None), 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 { 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 { pub fn new_with_timeout(url: String, timeout: Duration) -> Self {
Self::new_sender( Self::new_sender(
HttpSender::new_with_timeout(url, timeout), HttpSender::new_with_timeout(url, timeout),
CommitmentConfig::default(), RpcClientConfig::with_commitment(CommitmentConfig::default()),
) )
} }
@ -104,18 +102,36 @@ impl RpcClient {
) -> Self { ) -> Self {
Self::new_sender( Self::new_sender(
HttpSender::new_with_timeout(url, timeout), 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 { 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 { pub fn new_mock_with_mocks(url: String, mocks: Mocks) -> Self {
Self::new_sender( Self::new_sender(
MockSender::new_with_mocks(url, mocks), MockSender::new_with_mocks(url, mocks),
CommitmentConfig::default(), RpcClientConfig::with_commitment(CommitmentConfig::default()),
) )
} }
@ -154,7 +170,7 @@ impl RpcClient {
} }
pub fn commitment(&self) -> CommitmentConfig { pub fn commitment(&self) -> CommitmentConfig {
self.commitment_config self.config.commitment_config
} }
fn use_deprecated_commitment(&self) -> Result<bool, RpcError> { fn use_deprecated_commitment(&self) -> Result<bool, RpcError> {
@ -179,7 +195,7 @@ impl RpcClient {
pub fn confirm_transaction(&self, signature: &Signature) -> ClientResult<bool> { pub fn confirm_transaction(&self, signature: &Signature) -> ClientResult<bool> {
Ok(self Ok(self
.confirm_transaction_with_commitment(signature, self.commitment_config)? .confirm_transaction_with_commitment(signature, self.commitment())?
.value) .value)
} }
@ -205,8 +221,7 @@ impl RpcClient {
transaction, transaction,
RpcSendTransactionConfig { RpcSendTransactionConfig {
preflight_commitment: Some( preflight_commitment: Some(
self.maybe_map_commitment(self.commitment_config)? self.maybe_map_commitment(self.commitment())?.commitment,
.commitment,
), ),
..RpcSendTransactionConfig::default() ..RpcSendTransactionConfig::default()
}, },
@ -295,7 +310,7 @@ impl RpcClient {
self.simulate_transaction_with_config( self.simulate_transaction_with_config(
transaction, transaction,
RpcSimulateTransactionConfig { RpcSimulateTransactionConfig {
commitment: Some(self.commitment_config), commitment: Some(self.commitment()),
..RpcSimulateTransactionConfig::default() ..RpcSimulateTransactionConfig::default()
}, },
) )
@ -333,7 +348,7 @@ impl RpcClient {
&self, &self,
signature: &Signature, signature: &Signature,
) -> ClientResult<Option<transaction::Result<()>>> { ) -> ClientResult<Option<transaction::Result<()>>> {
self.get_signature_status_with_commitment(signature, self.commitment_config) self.get_signature_status_with_commitment(signature, self.commitment())
} }
pub fn get_signature_statuses( pub fn get_signature_statuses(
@ -391,7 +406,7 @@ impl RpcClient {
} }
pub fn get_slot(&self) -> ClientResult<Slot> { pub fn get_slot(&self) -> ClientResult<Slot> {
self.get_slot_with_commitment(self.commitment_config) self.get_slot_with_commitment(self.commitment())
} }
pub fn get_slot_with_commitment( pub fn get_slot_with_commitment(
@ -405,7 +420,7 @@ impl RpcClient {
} }
pub fn get_block_height(&self) -> ClientResult<u64> { pub fn get_block_height(&self) -> ClientResult<u64> {
self.get_block_height_with_commitment(self.commitment_config) self.get_block_height_with_commitment(self.commitment())
} }
pub fn get_block_height_with_commitment( pub fn get_block_height_with_commitment(
@ -459,14 +474,14 @@ impl RpcClient {
stake_account.to_string(), stake_account.to_string(),
RpcEpochConfig { RpcEpochConfig {
epoch, epoch,
commitment: Some(self.commitment_config), commitment: Some(self.commitment()),
} }
]), ]),
) )
} }
pub fn supply(&self) -> RpcResult<RpcSupply> { pub fn supply(&self) -> RpcResult<RpcSupply> {
self.supply_with_commitment(self.commitment_config) self.supply_with_commitment(self.commitment())
} }
pub fn supply_with_commitment( pub fn supply_with_commitment(
@ -482,7 +497,7 @@ impl RpcClient {
#[deprecated(since = "1.5.19", note = "Please use RpcClient::supply() instead")] #[deprecated(since = "1.5.19", note = "Please use RpcClient::supply() instead")]
#[allow(deprecated)] #[allow(deprecated)]
pub fn total_supply(&self) -> ClientResult<u64> { pub fn total_supply(&self) -> ClientResult<u64> {
self.total_supply_with_commitment(self.commitment_config) self.total_supply_with_commitment(self.commitment())
} }
#[deprecated( #[deprecated(
@ -514,7 +529,7 @@ impl RpcClient {
} }
pub fn get_vote_accounts(&self) -> ClientResult<RpcVoteAccountStatus> { pub fn get_vote_accounts(&self) -> ClientResult<RpcVoteAccountStatus> {
self.get_vote_accounts_with_commitment(self.commitment_config) self.get_vote_accounts_with_commitment(self.commitment())
} }
pub fn get_vote_accounts_with_commitment( pub fn get_vote_accounts_with_commitment(
@ -743,7 +758,7 @@ impl RpcClient {
} }
pub fn get_epoch_info(&self) -> ClientResult<EpochInfo> { pub fn get_epoch_info(&self) -> ClientResult<EpochInfo> {
self.get_epoch_info_with_commitment(self.commitment_config) self.get_epoch_info_with_commitment(self.commitment())
} }
pub fn get_epoch_info_with_commitment( pub fn get_epoch_info_with_commitment(
@ -760,7 +775,7 @@ impl RpcClient {
&self, &self,
slot: Option<Slot>, slot: Option<Slot>,
) -> ClientResult<Option<RpcLeaderSchedule>> { ) -> ClientResult<Option<RpcLeaderSchedule>> {
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( pub fn get_leader_schedule_with_commitment(
@ -830,7 +845,7 @@ impl RpcClient {
addresses, addresses,
RpcEpochConfig { RpcEpochConfig {
epoch, 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 /// 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. /// `get_account_with_commitment` returns `Ok(None)` if the account does not exist.
pub fn get_account(&self, pubkey: &Pubkey) -> ClientResult<Account> { pub fn get_account(&self, pubkey: &Pubkey) -> ClientResult<Account> {
self.get_account_with_commitment(pubkey, self.commitment_config)? self.get_account_with_commitment(pubkey, self.commitment())?
.value .value
.ok_or_else(|| RpcError::ForUser(format!("AccountNotFound: pubkey={}", pubkey)).into()) .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<Vec<Option<Account>>> { pub fn get_multiple_accounts(&self, pubkeys: &[Pubkey]) -> ClientResult<Vec<Option<Account>>> {
Ok(self Ok(self
.get_multiple_accounts_with_commitment(pubkeys, self.commitment_config)? .get_multiple_accounts_with_commitment(pubkeys, self.commitment())?
.value) .value)
} }
@ -1006,7 +1021,7 @@ impl RpcClient {
/// Request the balance of the account `pubkey`. /// Request the balance of the account `pubkey`.
pub fn get_balance(&self, pubkey: &Pubkey) -> ClientResult<u64> { pub fn get_balance(&self, pubkey: &Pubkey) -> ClientResult<u64> {
Ok(self Ok(self
.get_balance_with_commitment(pubkey, self.commitment_config)? .get_balance_with_commitment(pubkey, self.commitment())?
.value) .value)
} }
@ -1064,7 +1079,7 @@ impl RpcClient {
/// Request the transaction count. /// Request the transaction count.
pub fn get_transaction_count(&self) -> ClientResult<u64> { pub fn get_transaction_count(&self) -> ClientResult<u64> {
self.get_transaction_count_with_commitment(self.commitment_config) self.get_transaction_count_with_commitment(self.commitment())
} }
pub fn get_transaction_count_with_commitment( pub fn get_transaction_count_with_commitment(
@ -1079,7 +1094,7 @@ impl RpcClient {
pub fn get_recent_blockhash(&self) -> ClientResult<(Hash, FeeCalculator)> { pub fn get_recent_blockhash(&self) -> ClientResult<(Hash, FeeCalculator)> {
let (blockhash, fee_calculator, _last_valid_slot) = self let (blockhash, fee_calculator, _last_valid_slot) = self
.get_recent_blockhash_with_commitment(self.commitment_config)? .get_recent_blockhash_with_commitment(self.commitment())?
.value; .value;
Ok((blockhash, fee_calculator)) Ok((blockhash, fee_calculator))
} }
@ -1152,7 +1167,7 @@ impl RpcClient {
blockhash: &Hash, blockhash: &Hash,
) -> ClientResult<Option<FeeCalculator>> { ) -> ClientResult<Option<FeeCalculator>> {
Ok(self Ok(self
.get_fee_calculator_for_blockhash_with_commitment(blockhash, self.commitment_config)? .get_fee_calculator_for_blockhash_with_commitment(blockhash, self.commitment())?
.value) .value)
} }
@ -1234,7 +1249,7 @@ impl RpcClient {
pub fn get_token_account(&self, pubkey: &Pubkey) -> ClientResult<Option<UiTokenAccount>> { pub fn get_token_account(&self, pubkey: &Pubkey) -> ClientResult<Option<UiTokenAccount>> {
Ok(self Ok(self
.get_token_account_with_commitment(pubkey, self.commitment_config)? .get_token_account_with_commitment(pubkey, self.commitment())?
.value) .value)
} }
@ -1295,7 +1310,7 @@ impl RpcClient {
pub fn get_token_account_balance(&self, pubkey: &Pubkey) -> ClientResult<UiTokenAmount> { pub fn get_token_account_balance(&self, pubkey: &Pubkey) -> ClientResult<UiTokenAmount> {
Ok(self Ok(self
.get_token_account_balance_with_commitment(pubkey, self.commitment_config)? .get_token_account_balance_with_commitment(pubkey, self.commitment())?
.value) .value)
} }
@ -1322,7 +1337,7 @@ impl RpcClient {
.get_token_accounts_by_delegate_with_commitment( .get_token_accounts_by_delegate_with_commitment(
delegate, delegate,
token_account_filter, token_account_filter,
self.commitment_config, self.commitment(),
)? )?
.value) .value)
} }
@ -1361,7 +1376,7 @@ impl RpcClient {
.get_token_accounts_by_owner_with_commitment( .get_token_accounts_by_owner_with_commitment(
owner, owner,
token_account_filter, token_account_filter,
self.commitment_config, self.commitment(),
)? )?
.value) .value)
} }
@ -1393,7 +1408,7 @@ impl RpcClient {
pub fn get_token_supply(&self, mint: &Pubkey) -> ClientResult<UiTokenAmount> { pub fn get_token_supply(&self, mint: &Pubkey) -> ClientResult<UiTokenAmount> {
Ok(self Ok(self
.get_token_supply_with_commitment(mint, self.commitment_config)? .get_token_supply_with_commitment(mint, self.commitment())?
.value) .value)
} }
@ -1416,7 +1431,7 @@ impl RpcClient {
pubkey, pubkey,
lamports, lamports,
RpcRequestAirdropConfig { RpcRequestAirdropConfig {
commitment: Some(self.commitment_config), commitment: Some(self.commitment()),
..RpcRequestAirdropConfig::default() ..RpcRequestAirdropConfig::default()
}, },
) )
@ -1432,7 +1447,7 @@ impl RpcClient {
pubkey, pubkey,
lamports, lamports,
RpcRequestAirdropConfig { RpcRequestAirdropConfig {
commitment: Some(self.commitment_config), commitment: Some(self.commitment()),
recent_blockhash: Some(recent_blockhash.to_string()), recent_blockhash: Some(recent_blockhash.to_string()),
}, },
) )
@ -1535,7 +1550,7 @@ impl RpcClient {
/// Poll the server to confirm a transaction. /// Poll the server to confirm a transaction.
pub fn poll_for_signature(&self, signature: &Signature) -> ClientResult<()> { 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. /// Poll the server to confirm a transaction.
@ -1645,7 +1660,7 @@ impl RpcClient {
) -> ClientResult<Signature> { ) -> ClientResult<Signature> {
self.send_and_confirm_transaction_with_spinner_and_commitment( self.send_and_confirm_transaction_with_spinner_and_commitment(
transaction, transaction,
self.commitment_config, self.commitment(),
) )
} }
@ -1701,19 +1716,25 @@ impl RpcClient {
"[{}/{}] Finalizing transaction {}", "[{}/{}] Finalizing transaction {}",
confirmations, desired_confirmations, signature, 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 { let (signature, status) = loop {
// Get recent commitment in order to count confirmations for successful transactions // Get recent commitment in order to count confirmations for successful transactions
let status = self let status = self
.get_signature_status_with_commitment(&signature, CommitmentConfig::processed())?; .get_signature_status_with_commitment(&signature, CommitmentConfig::processed())?;
if status.is_none() { if status.is_none() {
if self let blockhash_not_found = self
.get_fee_calculator_for_blockhash_with_commitment( .get_fee_calculator_for_blockhash_with_commitment(
&recent_blockhash, &recent_blockhash,
CommitmentConfig::processed(), CommitmentConfig::processed(),
)? )?
.value .value
.is_none() .is_none();
{ if blockhash_not_found && now.elapsed() >= confirm_transaction_initial_timeout {
break (signature, status); break (signature, status);
} }
} else { } else {
@ -1784,6 +1805,26 @@ impl RpcClient {
} }
} }
fn serialize_encode_transaction(
transaction: &Transaction,
encoding: UiTransactionEncoding,
) -> ClientResult<String> {
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)] #[derive(Debug, Default)]
pub struct GetConfirmedSignaturesForAddress2Config { pub struct GetConfirmedSignaturesForAddress2Config {
pub before: Option<Signature>, pub before: Option<Signature>,