Use optimistic confirmation in getSignatureStatuses, and various downstream client methods (#14430)

* Add optimistically_confirmed field to TransactionStatus

* Update docs

* Convert new field to confirmation_status

* Update docs to confirmationStatus

* Update variants

* Update docs

* Just Confirmed
This commit is contained in:
Tyera Eulberg
2021-01-15 09:05:05 -07:00
committed by GitHub
parent 299b3eb99d
commit 9a89689ad3
11 changed files with 162 additions and 30 deletions

View File

@ -10,11 +10,19 @@ use solana_sdk::{
transaction::{self, Transaction, TransactionError}, transaction::{self, Transaction, TransactionError},
}; };
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
pub enum TransactionConfirmationStatus {
Processed,
Confirmed,
Finalized,
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
pub struct TransactionStatus { pub struct TransactionStatus {
pub slot: Slot, pub slot: Slot,
pub confirmations: Option<usize>, // None = rooted pub confirmations: Option<usize>, // None = rooted
pub err: Option<TransactionError>, pub err: Option<TransactionError>,
pub confirmation_status: Option<TransactionConfirmationStatus>,
} }
#[tarpc::service] #[tarpc::service]

View File

@ -4,7 +4,9 @@ use futures::{
future, future,
prelude::stream::{self, StreamExt}, prelude::stream::{self, StreamExt},
}; };
use solana_banks_interface::{Banks, BanksRequest, BanksResponse, TransactionStatus}; use solana_banks_interface::{
Banks, BanksRequest, BanksResponse, TransactionConfirmationStatus, TransactionStatus,
};
use solana_runtime::{bank::Bank, bank_forks::BankForks, commitment::BlockCommitmentCache}; use solana_runtime::{bank::Bank, bank_forks::BankForks, commitment::BlockCommitmentCache};
use solana_sdk::{ use solana_sdk::{
account::Account, account::Account,
@ -169,6 +171,10 @@ impl Banks for BanksServer {
let (slot, status) = bank.get_signature_status_slot(&signature)?; let (slot, status) = bank.get_signature_status_slot(&signature)?;
let r_block_commitment_cache = self.block_commitment_cache.read().unwrap(); let r_block_commitment_cache = self.block_commitment_cache.read().unwrap();
let optimistically_confirmed_bank = self.bank(CommitmentLevel::SingleGossip);
let optimistically_confirmed =
optimistically_confirmed_bank.get_signature_status_slot(&signature);
let confirmations = if r_block_commitment_cache.root() >= slot let confirmations = if r_block_commitment_cache.root() >= slot
&& r_block_commitment_cache.highest_confirmed_root() >= slot && r_block_commitment_cache.highest_confirmed_root() >= slot
{ {
@ -182,6 +188,13 @@ impl Banks for BanksServer {
slot, slot,
confirmations, confirmations,
err: status.err(), err: status.err(),
confirmation_status: if confirmations.is_none() {
Some(TransactionConfirmationStatus::Finalized)
} else if optimistically_confirmed.is_some() {
Some(TransactionConfirmationStatus::Confirmed)
} else {
Some(TransactionConfirmationStatus::Processed)
},
}) })
} }

View File

@ -41,6 +41,7 @@ use solana_sdk::{
system_program, system_program,
transaction::Transaction, transaction::Transaction,
}; };
use solana_transaction_status::TransactionConfirmationStatus;
use std::{ use std::{
cmp::min, collections::HashMap, error, fs::File, io::Read, net::UdpSocket, path::PathBuf, cmp::min, collections::HashMap, error, fs::File, io::Read, net::UdpSocket, path::PathBuf,
sync::Arc, thread::sleep, time::Duration, sync::Arc, thread::sleep, time::Duration,
@ -1578,7 +1579,11 @@ fn send_and_confirm_transactions_with_spinner<T: Signers>(
for (signature, status) in pending_signatures.into_iter().zip(statuses.into_iter()) { for (signature, status) in pending_signatures.into_iter().zip(statuses.into_iter()) {
if let Some(status) = status { if let Some(status) = status {
if status.confirmations.is_none() || status.confirmations.unwrap() > 1 { if let Some(confirmation_status) = &status.confirmation_status {
if *confirmation_status != TransactionConfirmationStatus::Processed {
let _ = pending_transactions.remove(&signature);
}
} else if status.confirmations.is_none() || status.confirmations.unwrap() > 1 {
let _ = pending_transactions.remove(&signature); let _ = pending_transactions.remove(&signature);
} }
} }

View File

@ -12,7 +12,7 @@ use solana_sdk::{
signature::Signature, signature::Signature,
transaction::{self, Transaction, TransactionError}, transaction::{self, Transaction, TransactionError},
}; };
use solana_transaction_status::TransactionStatus; use solana_transaction_status::{TransactionConfirmationStatus, TransactionStatus};
use solana_version::Version; use solana_version::Version;
use std::{collections::HashMap, sync::RwLock}; use std::{collections::HashMap, sync::RwLock};
@ -106,6 +106,7 @@ impl RpcSender for MockSender {
slot: 1, slot: 1,
confirmations: None, confirmations: None,
err, err,
confirmation_status: Some(TransactionConfirmationStatus::Finalized),
}) })
}; };
let statuses: Vec<Option<TransactionStatus>> = params.as_array().unwrap()[0] let statuses: Vec<Option<TransactionStatus>> = params.as_array().unwrap()[0]

View File

@ -37,6 +37,7 @@ use solana_transaction_status::{
}; };
use solana_vote_program::vote_state::MAX_LOCKOUT_HISTORY; use solana_vote_program::vote_state::MAX_LOCKOUT_HISTORY;
use std::{ use std::{
cmp::min,
net::SocketAddr, net::SocketAddr,
sync::RwLock, sync::RwLock,
thread::sleep, thread::sleep,
@ -1359,29 +1360,20 @@ impl RpcClient {
} }
let now = Instant::now(); let now = Instant::now();
loop { loop {
match commitment.commitment { // Return when specified commitment is reached
CommitmentLevel::Max | CommitmentLevel::Root => // Failed transactions have already been eliminated, `is_some` check is sufficient
// Return when default (max) commitment is reached if self
// Failed transactions have already been eliminated, `is_some` check is sufficient .get_signature_status_with_commitment(&signature, commitment)?
{ .is_some()
if self.get_signature_status(&signature)?.is_some() { {
progress_bar.set_message("Transaction confirmed"); progress_bar.set_message("Transaction confirmed");
progress_bar.finish_and_clear(); progress_bar.finish_and_clear();
return Ok(signature); return Ok(signature);
}
}
_ => {
// Return when one confirmation has been reached
if confirmations >= desired_confirmations {
progress_bar.set_message("Transaction reached commitment");
progress_bar.finish_and_clear();
return Ok(signature);
}
}
} }
progress_bar.set_message(&format!( progress_bar.set_message(&format!(
"[{}/{}] Finalizing transaction {}", "[{}/{}] Finalizing transaction {}",
confirmations + 1, min(confirmations + 1, desired_confirmations),
desired_confirmations, desired_confirmations,
signature, signature,
)); ));

View File

@ -62,7 +62,8 @@ use solana_sdk::{
}; };
use solana_stake_program::stake_state::StakeState; use solana_stake_program::stake_state::StakeState;
use solana_transaction_status::{ use solana_transaction_status::{
EncodedConfirmedBlock, EncodedConfirmedTransaction, TransactionStatus, UiTransactionEncoding, EncodedConfirmedBlock, EncodedConfirmedTransaction, TransactionConfirmationStatus,
TransactionStatus, UiTransactionEncoding,
}; };
use solana_vote_program::vote_state::{VoteState, MAX_LOCKOUT_HISTORY}; use solana_vote_program::vote_state::{VoteState, MAX_LOCKOUT_HISTORY};
use spl_token_v2_0::{ use spl_token_v2_0::{
@ -858,6 +859,7 @@ impl JsonRpcRequestProcessor {
status: status_meta.status, status: status_meta.status,
confirmations: None, confirmations: None,
err, err,
confirmation_status: Some(TransactionConfirmationStatus::Finalized),
} }
}) })
.or_else(|| { .or_else(|| {
@ -886,6 +888,10 @@ impl JsonRpcRequestProcessor {
let (slot, status) = bank.get_signature_status_slot(&signature)?; let (slot, status) = bank.get_signature_status_slot(&signature)?;
let r_block_commitment_cache = self.block_commitment_cache.read().unwrap(); let r_block_commitment_cache = self.block_commitment_cache.read().unwrap();
let optimistically_confirmed_bank = self.bank(Some(CommitmentConfig::single_gossip()));
let optimistically_confirmed =
optimistically_confirmed_bank.get_signature_status_slot(&signature);
let confirmations = if r_block_commitment_cache.root() >= slot let confirmations = if r_block_commitment_cache.root() >= slot
&& is_confirmed_rooted(&r_block_commitment_cache, bank, &self.blockstore, slot) && is_confirmed_rooted(&r_block_commitment_cache, bank, &self.blockstore, slot)
{ {
@ -901,6 +907,13 @@ impl JsonRpcRequestProcessor {
status, status,
confirmations, confirmations,
err, err,
confirmation_status: if confirmations.is_none() {
Some(TransactionConfirmationStatus::Finalized)
} else if optimistically_confirmed.is_some() {
Some(TransactionConfirmationStatus::Confirmed)
} else {
Some(TransactionConfirmationStatus::Processed)
},
}) })
} }

View File

@ -1945,6 +1945,7 @@ An array of:
- `slot: <u64>` - The slot the transaction was processed - `slot: <u64>` - The slot the transaction was processed
- `confirmations: <usize | null>` - Number of blocks since signature confirmation, null if rooted, as well as finalized by a supermajority of the cluster - `confirmations: <usize | null>` - Number of blocks since signature confirmation, null if rooted, as well as finalized by a supermajority of the cluster
- `err: <object | null>` - Error if transaction failed, null if transaction succeeded. [TransactionError definitions](https://github.com/solana-labs/solana/blob/master/sdk/src/transaction.rs#L24) - `err: <object | null>` - Error if transaction failed, null if transaction succeeded. [TransactionError definitions](https://github.com/solana-labs/solana/blob/master/sdk/src/transaction.rs#L24)
- `confirmationStatus: <string | null>` - The transaction's cluster confirmation status; either `processed`, `confirmed`, or `finalized`. See [Commitment](jsonrpc-api.md#configuring-state-commitment) for more on optimistic confirmation.
- DEPRECATED: `status: <object>` - Transaction status - DEPRECATED: `status: <object>` - Transaction status
- `"Ok": <null>` - Transaction was successful - `"Ok": <null>` - Transaction was successful
- `"Err": <ERR>` - Transaction failed with TransactionError - `"Err": <ERR>` - Transaction failed with TransactionError
@ -1983,7 +1984,8 @@ Result:
"err": null, "err": null,
"status": { "status": {
"Ok": null "Ok": null
} },
"confirmationStatus": "confirmed",
}, },
null null
] ]
@ -2027,7 +2029,8 @@ Result:
"err": null, "err": null,
"status": { "status": {
"Ok": null "Ok": null
} },
"confirmationStatus": "finalized",
}, },
null null
] ]

View File

@ -10,7 +10,8 @@ use solana_sdk::{
use solana_storage_proto::convert::generated; use solana_storage_proto::convert::generated;
use solana_transaction_status::{ use solana_transaction_status::{
ConfirmedBlock, ConfirmedTransaction, ConfirmedTransactionStatusWithSignature, Reward, ConfirmedBlock, ConfirmedTransaction, ConfirmedTransactionStatusWithSignature, Reward,
TransactionStatus, TransactionStatusMeta, TransactionWithStatusMeta, TransactionConfirmationStatus, TransactionStatus, TransactionStatusMeta,
TransactionWithStatusMeta,
}; };
use std::{collections::HashMap, convert::TryInto}; use std::{collections::HashMap, convert::TryInto};
use thiserror::Error; use thiserror::Error;
@ -257,6 +258,7 @@ impl From<TransactionInfo> for TransactionStatus {
confirmations: None, confirmations: None,
status, status,
err, err,
confirmation_status: Some(TransactionConfirmationStatus::Finalized),
} }
} }
} }

View File

@ -1038,6 +1038,7 @@ mod tests {
use solana_core::test_validator::TestValidator; use solana_core::test_validator::TestValidator;
use solana_sdk::signature::{read_keypair_file, write_keypair_file, Signer}; use solana_sdk::signature::{read_keypair_file, write_keypair_file, Signer};
use solana_stake_program::stake_instruction::StakeInstruction; use solana_stake_program::stake_instruction::StakeInstruction;
use solana_transaction_status::TransactionConfirmationStatus;
#[test] #[test]
fn test_process_token_allocations() { fn test_process_token_allocations() {
@ -2105,6 +2106,7 @@ mod tests {
confirmations: Some(15), confirmations: Some(15),
status: Ok(()), status: Ok(()),
err: None, err: None,
confirmation_status: Some(TransactionConfirmationStatus::Finalized),
})], })],
&mut confirmations, &mut confirmations,
) )
@ -2124,6 +2126,7 @@ mod tests {
confirmations: None, confirmations: None,
status: Ok(()), status: Ok(()),
err: None, err: None,
confirmation_status: Some(TransactionConfirmationStatus::Finalized),
})], })],
&mut confirmations, &mut confirmations,
) )

View File

@ -211,6 +211,7 @@ mod tests {
use super::*; use super::*;
use csv::{ReaderBuilder, Trim}; use csv::{ReaderBuilder, Trim};
use solana_sdk::transaction::TransactionError; use solana_sdk::transaction::TransactionError;
use solana_transaction_status::TransactionConfirmationStatus;
use tempfile::NamedTempFile; use tempfile::NamedTempFile;
#[test] #[test]
@ -307,6 +308,7 @@ mod tests {
confirmations: Some(1), confirmations: Some(1),
err: None, err: None,
status: Ok(()), status: Ok(()),
confirmation_status: Some(TransactionConfirmationStatus::Confirmed),
}; };
assert_eq!( assert_eq!(
update_finalized_transaction(&mut db, &signature, Some(transaction_status), 0, 0) update_finalized_transaction(&mut db, &signature, Some(transaction_status), 0, 0)
@ -334,6 +336,7 @@ mod tests {
confirmations: None, confirmations: None,
err: Some(TransactionError::AccountNotFound), err: Some(TransactionError::AccountNotFound),
status: Ok(()), status: Ok(()),
confirmation_status: Some(TransactionConfirmationStatus::Finalized),
}; };
assert_eq!( assert_eq!(
update_finalized_transaction(&mut db, &signature, Some(transaction_status), 0, 0) update_finalized_transaction(&mut db, &signature, Some(transaction_status), 0, 0)
@ -358,6 +361,7 @@ mod tests {
confirmations: None, confirmations: None,
err: None, err: None,
status: Ok(()), status: Ok(()),
confirmation_status: Some(TransactionConfirmationStatus::Finalized),
}; };
assert_eq!( assert_eq!(
update_finalized_transaction(&mut db, &signature, Some(transaction_status), 0, 0) update_finalized_transaction(&mut db, &signature, Some(transaction_status), 0, 0)

View File

@ -20,7 +20,7 @@ use solana_account_decoder::parse_token::UiTokenAmount;
pub use solana_runtime::bank::RewardType; pub use solana_runtime::bank::RewardType;
use solana_sdk::{ use solana_sdk::{
clock::{Slot, UnixTimestamp}, clock::{Slot, UnixTimestamp},
commitment_config::CommitmentConfig, commitment_config::{CommitmentConfig, CommitmentLevel},
deserialize_utils::default_on_eof, deserialize_utils::default_on_eof,
instruction::CompiledInstruction, instruction::CompiledInstruction,
message::{Message, MessageHeader}, message::{Message, MessageHeader},
@ -260,6 +260,14 @@ impl From<TransactionStatusMeta> for UiTransactionStatusMeta {
} }
} }
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub enum TransactionConfirmationStatus {
Processed,
Confirmed,
Finalized,
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
pub struct TransactionStatus { pub struct TransactionStatus {
@ -267,12 +275,28 @@ pub struct TransactionStatus {
pub confirmations: Option<usize>, // None = rooted pub confirmations: Option<usize>, // None = rooted
pub status: Result<()>, // legacy field pub status: Result<()>, // legacy field
pub err: Option<TransactionError>, pub err: Option<TransactionError>,
pub confirmation_status: Option<TransactionConfirmationStatus>,
} }
impl TransactionStatus { impl TransactionStatus {
pub fn satisfies_commitment(&self, commitment_config: CommitmentConfig) -> bool { pub fn satisfies_commitment(&self, commitment_config: CommitmentConfig) -> bool {
(commitment_config == CommitmentConfig::default() && self.confirmations.is_none()) match commitment_config.commitment {
|| commitment_config == CommitmentConfig::recent() CommitmentLevel::Max | CommitmentLevel::Root => self.confirmations.is_none(),
CommitmentLevel::SingleGossip => {
if let Some(status) = &self.confirmation_status {
*status != TransactionConfirmationStatus::Processed
} else {
// These fallback cases handle TransactionStatus RPC responses from older software
self.confirmations.is_some() && self.confirmations.unwrap() > 1
|| self.confirmations.is_none()
}
}
CommitmentLevel::Single => match self.confirmations {
Some(confirmations) => confirmations >= 1,
None => true,
},
CommitmentLevel::Recent => true,
}
} }
} }
@ -545,9 +569,13 @@ mod test {
confirmations: None, confirmations: None,
status: Ok(()), status: Ok(()),
err: None, err: None,
confirmation_status: Some(TransactionConfirmationStatus::Finalized),
}; };
assert!(status.satisfies_commitment(CommitmentConfig::default())); assert!(status.satisfies_commitment(CommitmentConfig::default()));
assert!(status.satisfies_commitment(CommitmentConfig::root()));
assert!(status.satisfies_commitment(CommitmentConfig::single()));
assert!(status.satisfies_commitment(CommitmentConfig::single_gossip()));
assert!(status.satisfies_commitment(CommitmentConfig::recent())); assert!(status.satisfies_commitment(CommitmentConfig::recent()));
let status = TransactionStatus { let status = TransactionStatus {
@ -555,9 +583,69 @@ mod test {
confirmations: Some(10), confirmations: Some(10),
status: Ok(()), status: Ok(()),
err: None, err: None,
confirmation_status: Some(TransactionConfirmationStatus::Confirmed),
}; };
assert!(!status.satisfies_commitment(CommitmentConfig::default())); assert!(!status.satisfies_commitment(CommitmentConfig::default()));
assert!(!status.satisfies_commitment(CommitmentConfig::root()));
assert!(status.satisfies_commitment(CommitmentConfig::single()));
assert!(status.satisfies_commitment(CommitmentConfig::single_gossip()));
assert!(status.satisfies_commitment(CommitmentConfig::recent())); assert!(status.satisfies_commitment(CommitmentConfig::recent()));
let status = TransactionStatus {
slot: 0,
confirmations: Some(1),
status: Ok(()),
err: None,
confirmation_status: Some(TransactionConfirmationStatus::Processed),
};
assert!(!status.satisfies_commitment(CommitmentConfig::default()));
assert!(!status.satisfies_commitment(CommitmentConfig::root()));
assert!(status.satisfies_commitment(CommitmentConfig::single()));
assert!(!status.satisfies_commitment(CommitmentConfig::single_gossip()));
assert!(status.satisfies_commitment(CommitmentConfig::recent()));
let status = TransactionStatus {
slot: 0,
confirmations: Some(0),
status: Ok(()),
err: None,
confirmation_status: None,
};
assert!(!status.satisfies_commitment(CommitmentConfig::default()));
assert!(!status.satisfies_commitment(CommitmentConfig::root()));
assert!(!status.satisfies_commitment(CommitmentConfig::single()));
assert!(!status.satisfies_commitment(CommitmentConfig::single_gossip()));
assert!(status.satisfies_commitment(CommitmentConfig::recent()));
// Test single_gossip fallback cases
let status = TransactionStatus {
slot: 0,
confirmations: Some(1),
status: Ok(()),
err: None,
confirmation_status: None,
};
assert!(!status.satisfies_commitment(CommitmentConfig::single_gossip()));
let status = TransactionStatus {
slot: 0,
confirmations: Some(2),
status: Ok(()),
err: None,
confirmation_status: None,
};
assert!(status.satisfies_commitment(CommitmentConfig::single_gossip()));
let status = TransactionStatus {
slot: 0,
confirmations: None,
status: Ok(()),
err: None,
confirmation_status: None,
};
assert!(status.satisfies_commitment(CommitmentConfig::single_gossip()));
} }
} }