More reliable way to detect expired transactions (#10482) (#10505)

automerge
This commit is contained in:
mergify[bot]
2020-06-10 17:24:47 -07:00
committed by GitHub
parent 84b28fb261
commit ee450b2dd0
3 changed files with 43 additions and 57 deletions

View File

@ -160,7 +160,7 @@ fn distribute_tokens<T: Client>(
let fee_payer_pubkey = args.fee_payer.pubkey(); let fee_payer_pubkey = args.fee_payer.pubkey();
let message = Message::new_with_payer(&instructions, Some(&fee_payer_pubkey)); let message = Message::new_with_payer(&instructions, Some(&fee_payer_pubkey));
match client.send_message(message, &signers) { match client.send_message(message, &signers) {
Ok((transaction, _last_valid_slot)) => { Ok((transaction, last_valid_slot)) => {
db::set_transaction_info( db::set_transaction_info(
db, db,
&allocation.recipient.parse().unwrap(), &allocation.recipient.parse().unwrap(),
@ -168,6 +168,7 @@ fn distribute_tokens<T: Client>(
&transaction, &transaction,
Some(&new_stake_account_address), Some(&new_stake_account_address),
false, false,
last_valid_slot,
)?; )?;
} }
Err(e) => { Err(e) => {
@ -332,20 +333,20 @@ fn update_finalized_transactions<T: Client>(
if info.finalized_date.is_some() { if info.finalized_date.is_some() {
None None
} else { } else {
Some(&info.transaction) Some((&info.transaction, info.last_valid_slot))
} }
}) })
.collect(); .collect();
let unconfirmed_signatures: Vec<_> = unconfirmed_transactions let unconfirmed_signatures: Vec<_> = unconfirmed_transactions
.iter() .iter()
.map(|tx| tx.signatures[0]) .map(|(tx, _slot)| tx.signatures[0])
.filter(|sig| *sig != Signature::default()) // Filter out dry-run signatures .filter(|sig| *sig != Signature::default()) // Filter out dry-run signatures
.collect(); .collect();
let transaction_statuses = client.get_signature_statuses(&unconfirmed_signatures)?; let transaction_statuses = client.get_signature_statuses(&unconfirmed_signatures)?;
let recent_blockhashes = client.get_recent_blockhashes()?; let root_slot = client.get_slot()?;
let mut confirmations = None; let mut confirmations = None;
for (transaction, opt_transaction_status) in unconfirmed_transactions for ((transaction, last_valid_slot), opt_transaction_status) in unconfirmed_transactions
.into_iter() .into_iter()
.zip(transaction_statuses.into_iter()) .zip(transaction_statuses.into_iter())
{ {
@ -353,8 +354,8 @@ fn update_finalized_transactions<T: Client>(
db, db,
&transaction.signatures[0], &transaction.signatures[0],
opt_transaction_status, opt_transaction_status,
&transaction.message.recent_blockhash, last_valid_slot,
&recent_blockhashes, root_slot,
) { ) {
Ok(Some(confs)) => { Ok(Some(confs)) => {
confirmations = Some(cmp::min(confs, confirmations.unwrap_or(usize::MAX))); confirmations = Some(cmp::min(confs, confirmations.unwrap_or(usize::MAX)));

View File

@ -1,7 +1,7 @@
use chrono::prelude::*; use chrono::prelude::*;
use pickledb::{error::Error, PickleDb, PickleDbDumpPolicy}; use pickledb::{error::Error, PickleDb, PickleDbDumpPolicy};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use solana_sdk::{hash::Hash, pubkey::Pubkey, signature::Signature, transaction::Transaction}; use solana_sdk::{clock::Slot, pubkey::Pubkey, signature::Signature, transaction::Transaction};
use solana_transaction_status::TransactionStatus; use solana_transaction_status::TransactionStatus;
use std::{cmp::Ordering, fs, io, path::Path}; use std::{cmp::Ordering, fs, io, path::Path};
@ -12,6 +12,7 @@ pub struct TransactionInfo {
pub new_stake_account_address: Option<Pubkey>, pub new_stake_account_address: Option<Pubkey>,
pub finalized_date: Option<DateTime<Utc>>, pub finalized_date: Option<DateTime<Utc>>,
pub transaction: Transaction, pub transaction: Transaction,
pub last_valid_slot: Slot,
} }
#[derive(Serialize, Deserialize, Debug, Default, PartialEq)] #[derive(Serialize, Deserialize, Debug, Default, PartialEq)]
@ -35,6 +36,7 @@ impl Default for TransactionInfo {
new_stake_account_address: None, new_stake_account_address: None,
finalized_date: None, finalized_date: None,
transaction, transaction,
last_valid_slot: 0,
} }
} }
} }
@ -103,6 +105,7 @@ pub fn set_transaction_info(
transaction: &Transaction, transaction: &Transaction,
new_stake_account_address: Option<&Pubkey>, new_stake_account_address: Option<&Pubkey>,
finalized: bool, finalized: bool,
last_valid_slot: Slot,
) -> Result<(), Error> { ) -> Result<(), Error> {
let finalized_date = if finalized { Some(Utc::now()) } else { None }; let finalized_date = if finalized { Some(Utc::now()) } else { None };
let transaction_info = TransactionInfo { let transaction_info = TransactionInfo {
@ -111,6 +114,7 @@ pub fn set_transaction_info(
new_stake_account_address: new_stake_account_address.cloned(), new_stake_account_address: new_stake_account_address.cloned(),
finalized_date, finalized_date,
transaction: transaction.clone(), transaction: transaction.clone(),
last_valid_slot,
}; };
let signature = transaction.signatures[0]; let signature = transaction.signatures[0];
db.set(&signature.to_string(), &transaction_info)?; db.set(&signature.to_string(), &transaction_info)?;
@ -119,20 +123,22 @@ pub fn set_transaction_info(
// Set the finalized bit in the database if the transaction is rooted. // Set the finalized bit in the database if the transaction is rooted.
// Remove the TransactionInfo from the database if the transaction failed. // Remove the TransactionInfo from the database if the transaction failed.
// Return the number of confirmations on the transaction or None if finalized. // Return the number of confirmations on the transaction or None if either
// finalized or discarded.
pub fn update_finalized_transaction( pub fn update_finalized_transaction(
db: &mut PickleDb, db: &mut PickleDb,
signature: &Signature, signature: &Signature,
opt_transaction_status: Option<TransactionStatus>, opt_transaction_status: Option<TransactionStatus>,
blockhash: &Hash, last_valid_slot: Slot,
recent_blockhashes: &[Hash], root_slot: Slot,
) -> Result<Option<usize>, Error> { ) -> Result<Option<usize>, Error> {
if opt_transaction_status.is_none() { if opt_transaction_status.is_none() {
if !recent_blockhashes.contains(blockhash) { if root_slot > last_valid_slot {
eprintln!( eprintln!(
"Signature not found {} and blockhash not found, likely expired", "Signature not found {} and blockhash expired. Transaction either dropped or the validator purged the transaction status.",
signature signature
); );
// Don't discard the transaction, because we are not certain the // Don't discard the transaction, because we are not certain the
// blockhash is expired. Instead, return None to signal that // blockhash is expired. Instead, return None to signal that
// we don't need to wait for confirmations. // we don't need to wait for confirmations.
@ -161,7 +167,7 @@ pub fn update_finalized_transaction(
return Ok(None); return Ok(None);
} }
// Transaction is rooted. Set finalized in the database. // Transaction is rooted. Set the finalized date in the database.
let mut transaction_info = db.get::<TransactionInfo>(&signature.to_string()).unwrap(); let mut transaction_info = db.get::<TransactionInfo>(&signature.to_string()).unwrap();
transaction_info.finalized_date = Some(Utc::now()); transaction_info.finalized_date = Some(Utc::now());
db.set(&signature.to_string(), &transaction_info)?; db.set(&signature.to_string(), &transaction_info)?;
@ -230,12 +236,10 @@ mod tests {
let mut db = let mut db =
PickleDb::new_yaml(NamedTempFile::new().unwrap(), PickleDbDumpPolicy::NeverDump); PickleDb::new_yaml(NamedTempFile::new().unwrap(), PickleDbDumpPolicy::NeverDump);
let signature = Signature::default(); let signature = Signature::default();
let blockhash = Hash::default();
let transaction_info = TransactionInfo::default(); let transaction_info = TransactionInfo::default();
db.set(&signature.to_string(), &transaction_info).unwrap(); db.set(&signature.to_string(), &transaction_info).unwrap();
assert!(matches!( assert!(matches!(
update_finalized_transaction(&mut db, &signature, None, &blockhash, &[blockhash]) update_finalized_transaction(&mut db, &signature, None, 0, 0).unwrap(),
.unwrap(),
Some(0) Some(0)
)); ));
@ -247,7 +251,7 @@ mod tests {
// Same as before, but now with an expired blockhash // Same as before, but now with an expired blockhash
assert_eq!( assert_eq!(
update_finalized_transaction(&mut db, &signature, None, &blockhash, &[]).unwrap(), update_finalized_transaction(&mut db, &signature, None, 0, 1).unwrap(),
None None
); );
@ -264,7 +268,6 @@ mod tests {
let mut db = let mut db =
PickleDb::new_yaml(NamedTempFile::new().unwrap(), PickleDbDumpPolicy::NeverDump); PickleDb::new_yaml(NamedTempFile::new().unwrap(), PickleDbDumpPolicy::NeverDump);
let signature = Signature::default(); let signature = Signature::default();
let blockhash = Hash::default();
let transaction_info = TransactionInfo::default(); let transaction_info = TransactionInfo::default();
db.set(&signature.to_string(), &transaction_info).unwrap(); db.set(&signature.to_string(), &transaction_info).unwrap();
let transaction_status = TransactionStatus { let transaction_status = TransactionStatus {
@ -274,14 +277,8 @@ mod tests {
err: None, err: None,
}; };
assert_eq!( assert_eq!(
update_finalized_transaction( update_finalized_transaction(&mut db, &signature, Some(transaction_status), 0, 0)
&mut db, .unwrap(),
&signature,
Some(transaction_status),
&blockhash,
&[blockhash]
)
.unwrap(),
Some(1) Some(1)
); );
@ -298,7 +295,6 @@ mod tests {
let mut db = let mut db =
PickleDb::new_yaml(NamedTempFile::new().unwrap(), PickleDbDumpPolicy::NeverDump); PickleDb::new_yaml(NamedTempFile::new().unwrap(), PickleDbDumpPolicy::NeverDump);
let signature = Signature::default(); let signature = Signature::default();
let blockhash = Hash::default();
let transaction_info = TransactionInfo::default(); let transaction_info = TransactionInfo::default();
db.set(&signature.to_string(), &transaction_info).unwrap(); db.set(&signature.to_string(), &transaction_info).unwrap();
let status = Err(TransactionError::AccountNotFound); let status = Err(TransactionError::AccountNotFound);
@ -309,14 +305,8 @@ mod tests {
err: None, err: None,
}; };
assert_eq!( assert_eq!(
update_finalized_transaction( update_finalized_transaction(&mut db, &signature, Some(transaction_status), 0, 0)
&mut db, .unwrap(),
&signature,
Some(transaction_status),
&blockhash,
&[blockhash]
)
.unwrap(),
None None
); );
@ -330,7 +320,6 @@ mod tests {
let mut db = let mut db =
PickleDb::new_yaml(NamedTempFile::new().unwrap(), PickleDbDumpPolicy::NeverDump); PickleDb::new_yaml(NamedTempFile::new().unwrap(), PickleDbDumpPolicy::NeverDump);
let signature = Signature::default(); let signature = Signature::default();
let blockhash = Hash::default();
let transaction_info = TransactionInfo::default(); let transaction_info = TransactionInfo::default();
db.set(&signature.to_string(), &transaction_info).unwrap(); db.set(&signature.to_string(), &transaction_info).unwrap();
let transaction_status = TransactionStatus { let transaction_status = TransactionStatus {
@ -340,14 +329,8 @@ mod tests {
err: None, err: None,
}; };
assert_eq!( assert_eq!(
update_finalized_transaction( update_finalized_transaction(&mut db, &signature, Some(transaction_status), 0, 0)
&mut db, .unwrap(),
&signature,
Some(transaction_status),
&blockhash,
&[blockhash]
)
.unwrap(),
None None
); );

View File

@ -12,10 +12,6 @@ use solana_sdk::{
signature::{Signature, Signer}, signature::{Signature, Signer},
signers::Signers, signers::Signers,
system_instruction, system_instruction,
sysvar::{
recent_blockhashes::{self, RecentBlockhashes},
Sysvar,
},
transaction::Transaction, transaction::Transaction,
transport::{Result, TransportError}, transport::{Result, TransportError},
}; };
@ -29,6 +25,7 @@ pub trait Client {
) -> Result<Vec<Option<TransactionStatus>>>; ) -> Result<Vec<Option<TransactionStatus>>>;
fn get_balance1(&self, pubkey: &Pubkey) -> Result<u64>; fn get_balance1(&self, pubkey: &Pubkey) -> Result<u64>;
fn get_fees1(&self) -> Result<(Hash, FeeCalculator, Slot)>; fn get_fees1(&self) -> Result<(Hash, FeeCalculator, Slot)>;
fn get_slot1(&self) -> Result<Slot>;
fn get_account1(&self, pubkey: &Pubkey) -> Result<Option<Account>>; fn get_account1(&self, pubkey: &Pubkey) -> Result<Option<Account>>;
} }
@ -64,6 +61,11 @@ impl Client for RpcClient {
Ok(result.value) Ok(result.value)
} }
fn get_slot1(&self) -> Result<Slot> {
self.get_slot()
.map_err(|e| TransportError::Custom(e.to_string()))
}
fn get_account1(&self, pubkey: &Pubkey) -> Result<Option<Account>> { fn get_account1(&self, pubkey: &Pubkey) -> Result<Option<Account>> {
self.get_account(pubkey) self.get_account(pubkey)
.map(Some) .map(Some)
@ -103,6 +105,10 @@ impl Client for BankClient {
self.get_recent_blockhash_with_commitment(CommitmentConfig::default()) self.get_recent_blockhash_with_commitment(CommitmentConfig::default())
} }
fn get_slot1(&self) -> Result<Slot> {
self.get_slot()
}
fn get_account1(&self, pubkey: &Pubkey) -> Result<Option<Account>> { fn get_account1(&self, pubkey: &Pubkey) -> Result<Option<Account>> {
self.get_account(pubkey) self.get_account(pubkey)
} }
@ -170,6 +176,10 @@ impl<C: Client> ThinClient<C> {
self.client.get_fees1() self.client.get_fees1()
} }
pub fn get_slot(&self) -> Result<Slot> {
self.client.get_slot1()
}
pub fn get_balance(&self, pubkey: &Pubkey) -> Result<u64> { pub fn get_balance(&self, pubkey: &Pubkey) -> Result<u64> {
self.client.get_balance1(pubkey) self.client.get_balance1(pubkey)
} }
@ -177,12 +187,4 @@ impl<C: Client> ThinClient<C> {
pub fn get_account(&self, pubkey: &Pubkey) -> Result<Option<Account>> { pub fn get_account(&self, pubkey: &Pubkey) -> Result<Option<Account>> {
self.client.get_account1(pubkey) self.client.get_account1(pubkey)
} }
pub fn get_recent_blockhashes(&self) -> Result<Vec<Hash>> {
let opt_blockhashes_account = self.get_account(&recent_blockhashes::id())?;
let blockhashes_account = opt_blockhashes_account.unwrap();
let recent_blockhashes = RecentBlockhashes::from_account(&blockhashes_account).unwrap();
let hashes = recent_blockhashes.iter().map(|x| x.blockhash).collect();
Ok(hashes)
}
} }