Extend getConfirmedBlock rpc to return account pre- and post-balances (#7543)

automerge
This commit is contained in:
Tyera Eulberg 2019-12-18 10:56:29 -07:00 committed by Grimes
parent dcaf69a5d5
commit 6aaf742dfe
6 changed files with 218 additions and 19 deletions

View File

@ -32,9 +32,12 @@ pub struct RpcConfirmedBlock {
} }
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct RpcTransactionStatus { pub struct RpcTransactionStatus {
pub status: Result<()>, pub status: Result<()>,
pub fee: u64, pub fee: u64,
pub pre_balances: Vec<u64>,
pub post_balances: Vec<u64>,
} }
#[derive(Serialize, Deserialize, Clone, Debug)] #[derive(Serialize, Deserialize, Clone, Debug)]

View File

@ -22,7 +22,7 @@ use solana_metrics::{inc_new_counter_debug, inc_new_counter_info, inc_new_counte
use solana_perf::{cuda_runtime::PinnedVec, perf_libs}; use solana_perf::{cuda_runtime::PinnedVec, perf_libs};
use solana_runtime::{ use solana_runtime::{
accounts_db::ErrorCounters, accounts_db::ErrorCounters,
bank::{Bank, TransactionProcessResult}, bank::{Bank, TransactionBalancesSet, TransactionProcessResult},
transaction_batch::TransactionBatch, transaction_batch::TransactionBatch,
}; };
use solana_sdk::{ use solana_sdk::{
@ -511,6 +511,11 @@ impl BankingStage {
// TODO: Banking stage threads should be prioritized to complete faster then this queue // TODO: Banking stage threads should be prioritized to complete faster then this queue
// expires. // expires.
let txs = batch.transactions(); let txs = batch.transactions();
let pre_balances = if transaction_status_sender.is_some() {
bank.collect_balances(txs)
} else {
vec![]
};
let (mut loaded_accounts, results, mut retryable_txs, tx_count, signature_count) = let (mut loaded_accounts, results, mut retryable_txs, tx_count, signature_count) =
bank.load_and_execute_transactions(batch, MAX_PROCESSING_AGE); bank.load_and_execute_transactions(batch, MAX_PROCESSING_AGE);
load_execute_time.stop(); load_execute_time.stop();
@ -541,11 +546,14 @@ impl BankingStage {
signature_count, signature_count,
) )
.processing_results; .processing_results;
if let Some(sender) = transaction_status_sender { if let Some(sender) = transaction_status_sender {
let post_balances = bank.collect_balances(txs);
send_transaction_status_batch( send_transaction_status_batch(
bank.clone(), bank.clone(),
batch.transactions(), batch.transactions(),
transaction_statuses, transaction_statuses,
TransactionBalancesSet::new(pre_balances, post_balances),
sender, sender,
); );
} }

View File

@ -53,10 +53,16 @@ impl TransactionStatusService {
bank, bank,
transactions, transactions,
statuses, statuses,
balances,
} = write_transaction_status_receiver.recv_timeout(Duration::from_secs(1))?; } = write_transaction_status_receiver.recv_timeout(Duration::from_secs(1))?;
let slot = bank.slot(); let slot = bank.slot();
for (transaction, (status, hash_age_kind)) in transactions.iter().zip(statuses) { for (((transaction, (status, hash_age_kind)), pre_balances), post_balances) in transactions
.iter()
.zip(statuses)
.zip(balances.pre_balances)
.zip(balances.post_balances)
{
if Bank::can_commit(&status) && !transaction.signatures.is_empty() { if Bank::can_commit(&status) && !transaction.signatures.is_empty() {
let fee_hash = if let Some(HashAgeKind::DurableNonce) = hash_age_kind { let fee_hash = if let Some(HashAgeKind::DurableNonce) = hash_age_kind {
bank.last_blockhash() bank.last_blockhash()
@ -70,7 +76,12 @@ impl TransactionStatusService {
blocktree blocktree
.write_transaction_status( .write_transaction_status(
(slot, transaction.signatures[0]), (slot, transaction.signatures[0]),
&RpcTransactionStatus { status, fee }, &RpcTransactionStatus {
status,
fee,
pre_balances,
post_balances,
},
) )
.expect("Expect database write to succeed"); .expect("Expect database write to succeed");
} }

View File

@ -4550,6 +4550,12 @@ pub mod tests {
.filter(|entry| !entry.is_tick()) .filter(|entry| !entry.is_tick())
.flat_map(|entry| entry.transactions) .flat_map(|entry| entry.transactions)
.map(|transaction| { .map(|transaction| {
let mut pre_balances: Vec<u64> = vec![];
let mut post_balances: Vec<u64> = vec![];
for (i, _account_key) in transaction.message.account_keys.iter().enumerate() {
pre_balances.push(i as u64 * 10);
post_balances.push(i as u64 * 11);
}
let signature = transaction.signatures[0]; let signature = transaction.signatures[0];
ledger ledger
.transaction_status_cf .transaction_status_cf
@ -4558,6 +4564,8 @@ pub mod tests {
&RpcTransactionStatus { &RpcTransactionStatus {
status: Ok(()), status: Ok(()),
fee: 42, fee: 42,
pre_balances: pre_balances.clone(),
post_balances: post_balances.clone(),
}, },
) )
.unwrap(); .unwrap();
@ -4568,6 +4576,8 @@ pub mod tests {
&RpcTransactionStatus { &RpcTransactionStatus {
status: Ok(()), status: Ok(()),
fee: 42, fee: 42,
pre_balances: pre_balances.clone(),
post_balances: post_balances.clone(),
}, },
) )
.unwrap(); .unwrap();
@ -4576,6 +4586,8 @@ pub mod tests {
Some(RpcTransactionStatus { Some(RpcTransactionStatus {
status: Ok(()), status: Ok(()),
fee: 42, fee: 42,
pre_balances,
post_balances,
}), }),
) )
}) })
@ -4694,6 +4706,9 @@ pub mod tests {
let blocktree = Blocktree::open(&blocktree_path).unwrap(); let blocktree = Blocktree::open(&blocktree_path).unwrap();
let transaction_status_cf = blocktree.db.column::<cf::TransactionStatus>(); let transaction_status_cf = blocktree.db.column::<cf::TransactionStatus>();
let pre_balances_vec = vec![1, 2, 3];
let post_balances_vec = vec![3, 2, 1];
// result not found // result not found
assert!(transaction_status_cf assert!(transaction_status_cf
.get((0, Signature::default())) .get((0, Signature::default()))
@ -4708,18 +4723,27 @@ pub mod tests {
status: solana_sdk::transaction::Result::<()>::Err( status: solana_sdk::transaction::Result::<()>::Err(
TransactionError::AccountNotFound TransactionError::AccountNotFound
), ),
fee: 5u64 fee: 5u64,
pre_balances: pre_balances_vec.clone(),
post_balances: post_balances_vec.clone(),
}, },
) )
.is_ok()); .is_ok());
// result found // result found
let RpcTransactionStatus { status, fee } = transaction_status_cf let RpcTransactionStatus {
status,
fee,
pre_balances,
post_balances,
} = transaction_status_cf
.get((0, Signature::default())) .get((0, Signature::default()))
.unwrap() .unwrap()
.unwrap(); .unwrap();
assert_eq!(status, Err(TransactionError::AccountNotFound)); assert_eq!(status, Err(TransactionError::AccountNotFound));
assert_eq!(fee, 5u64); assert_eq!(fee, 5u64);
assert_eq!(pre_balances, pre_balances_vec);
assert_eq!(post_balances, post_balances_vec);
// insert value // insert value
assert!(transaction_status_cf assert!(transaction_status_cf
@ -4727,13 +4751,20 @@ pub mod tests {
(9, Signature::default()), (9, Signature::default()),
&RpcTransactionStatus { &RpcTransactionStatus {
status: solana_sdk::transaction::Result::<()>::Ok(()), status: solana_sdk::transaction::Result::<()>::Ok(()),
fee: 9u64 fee: 9u64,
pre_balances: pre_balances_vec.clone(),
post_balances: post_balances_vec.clone(),
}, },
) )
.is_ok()); .is_ok());
// result found // result found
let RpcTransactionStatus { status, fee } = transaction_status_cf let RpcTransactionStatus {
status,
fee,
pre_balances,
post_balances,
} = transaction_status_cf
.get((9, Signature::default())) .get((9, Signature::default()))
.unwrap() .unwrap()
.unwrap(); .unwrap();
@ -4741,6 +4772,8 @@ pub mod tests {
// deserialize // deserialize
assert_eq!(status, Ok(())); assert_eq!(status, Ok(()));
assert_eq!(fee, 9u64); assert_eq!(fee, 9u64);
assert_eq!(pre_balances, pre_balances_vec);
assert_eq!(post_balances, post_balances_vec);
} }
Blocktree::destroy(&blocktree_path).expect("Expected successful database destruction"); Blocktree::destroy(&blocktree_path).expect("Expected successful database destruction");
} }
@ -4786,6 +4819,8 @@ pub mod tests {
TransactionError::AccountNotFound, TransactionError::AccountNotFound,
), ),
fee: x, fee: x,
pre_balances: vec![],
post_balances: vec![],
}, },
) )
.unwrap(); .unwrap();

View File

@ -14,7 +14,7 @@ use rayon::{prelude::*, ThreadPool};
use solana_metrics::{datapoint, datapoint_error, inc_new_counter_debug}; use solana_metrics::{datapoint, datapoint_error, inc_new_counter_debug};
use solana_rayon_threadlimit::get_thread_count; use solana_rayon_threadlimit::get_thread_count;
use solana_runtime::{ use solana_runtime::{
bank::{Bank, TransactionProcessResult, TransactionResults}, bank::{Bank, TransactionBalancesSet, TransactionProcessResult, TransactionResults},
transaction_batch::TransactionBatch, transaction_batch::TransactionBatch,
}; };
use solana_sdk::{ use solana_sdk::{
@ -54,18 +54,24 @@ fn execute_batch(
bank: &Arc<Bank>, bank: &Arc<Bank>,
transaction_status_sender: Option<TransactionStatusSender>, transaction_status_sender: Option<TransactionStatusSender>,
) -> Result<()> { ) -> Result<()> {
let TransactionResults { let (
fee_collection_results, TransactionResults {
processing_results, fee_collection_results,
} = batch processing_results,
.bank() },
.load_execute_and_commit_transactions(batch, MAX_RECENT_BLOCKHASHES); balances,
) = batch.bank().load_execute_and_commit_transactions(
batch,
MAX_RECENT_BLOCKHASHES,
transaction_status_sender.is_some(),
);
if let Some(sender) = transaction_status_sender { if let Some(sender) = transaction_status_sender {
send_transaction_status_batch( send_transaction_status_batch(
bank.clone(), bank.clone(),
batch.transactions(), batch.transactions(),
processing_results, processing_results,
balances,
sender, sender,
); );
} }
@ -560,6 +566,7 @@ pub struct TransactionStatusBatch {
pub bank: Arc<Bank>, pub bank: Arc<Bank>,
pub transactions: Vec<Transaction>, pub transactions: Vec<Transaction>,
pub statuses: Vec<TransactionProcessResult>, pub statuses: Vec<TransactionProcessResult>,
pub balances: TransactionBalancesSet,
} }
pub type TransactionStatusSender = Sender<TransactionStatusBatch>; pub type TransactionStatusSender = Sender<TransactionStatusBatch>;
@ -567,6 +574,7 @@ pub fn send_transaction_status_batch(
bank: Arc<Bank>, bank: Arc<Bank>,
transactions: &[Transaction], transactions: &[Transaction],
statuses: Vec<TransactionProcessResult>, statuses: Vec<TransactionProcessResult>,
balances: TransactionBalancesSet,
transaction_status_sender: TransactionStatusSender, transaction_status_sender: TransactionStatusSender,
) { ) {
let slot = bank.slot(); let slot = bank.slot();
@ -574,6 +582,7 @@ pub fn send_transaction_status_batch(
bank, bank,
transactions: transactions.to_vec(), transactions: transactions.to_vec(),
statuses, statuses,
balances,
}) { }) {
trace!( trace!(
"Slot {} transaction_status send batch failed: {:?}", "Slot {} transaction_status send batch failed: {:?}",

View File

@ -161,6 +161,20 @@ pub struct TransactionResults {
pub fee_collection_results: Vec<Result<()>>, pub fee_collection_results: Vec<Result<()>>,
pub processing_results: Vec<TransactionProcessResult>, pub processing_results: Vec<TransactionProcessResult>,
} }
pub struct TransactionBalancesSet {
pub pre_balances: TransactionBalances,
pub post_balances: TransactionBalances,
}
impl TransactionBalancesSet {
pub fn new(pre_balances: TransactionBalances, post_balances: TransactionBalances) -> Self {
assert_eq!(pre_balances.len(), post_balances.len());
Self {
pre_balances,
post_balances,
}
}
}
pub type TransactionBalances = Vec<Vec<u64>>;
#[derive(Clone, Debug, Eq, PartialEq)] #[derive(Clone, Debug, Eq, PartialEq)]
pub enum HashAgeKind { pub enum HashAgeKind {
@ -1030,6 +1044,18 @@ impl Bank {
self.check_signatures(txs, iteration_order, age_results, &mut error_counters) self.check_signatures(txs, iteration_order, age_results, &mut error_counters)
} }
pub fn collect_balances(&self, batch: &[Transaction]) -> TransactionBalances {
let mut balances: TransactionBalances = vec![];
for transaction in batch.iter() {
let mut transaction_balances: Vec<u64> = vec![];
for account_key in transaction.message.account_keys.iter() {
transaction_balances.push(self.get_balance(account_key));
}
balances.push(transaction_balances);
}
balances
}
fn update_error_counters(error_counters: &ErrorCounters) { fn update_error_counters(error_counters: &ErrorCounters) {
if 0 != error_counters.blockhash_not_found { if 0 != error_counters.blockhash_not_found {
inc_new_counter_error!( inc_new_counter_error!(
@ -1372,24 +1398,40 @@ impl Bank {
&self, &self,
batch: &TransactionBatch, batch: &TransactionBatch,
max_age: usize, max_age: usize,
) -> TransactionResults { collect_balances: bool,
) -> (TransactionResults, TransactionBalancesSet) {
let pre_balances = if collect_balances {
self.collect_balances(batch.transactions())
} else {
vec![]
};
let (mut loaded_accounts, executed, _, tx_count, signature_count) = let (mut loaded_accounts, executed, _, tx_count, signature_count) =
self.load_and_execute_transactions(batch, max_age); self.load_and_execute_transactions(batch, max_age);
self.commit_transactions( let results = self.commit_transactions(
batch.transactions(), batch.transactions(),
batch.iteration_order(), batch.iteration_order(),
&mut loaded_accounts, &mut loaded_accounts,
&executed, &executed,
tx_count, tx_count,
signature_count, signature_count,
);
let post_balances = if collect_balances {
self.collect_balances(batch.transactions())
} else {
vec![]
};
(
results,
TransactionBalancesSet::new(pre_balances, post_balances),
) )
} }
#[must_use] #[must_use]
pub fn process_transactions(&self, txs: &[Transaction]) -> Vec<Result<()>> { pub fn process_transactions(&self, txs: &[Transaction]) -> Vec<Result<()>> {
let batch = self.prepare_batch(txs, None); let batch = self.prepare_batch(txs, None);
self.load_execute_and_commit_transactions(&batch, MAX_RECENT_BLOCKHASHES) self.load_execute_and_commit_transactions(&batch, MAX_RECENT_BLOCKHASHES, false)
.0
.fee_collection_results .fee_collection_results
} }
@ -1816,7 +1858,7 @@ mod tests {
clock::DEFAULT_TICKS_PER_SLOT, clock::DEFAULT_TICKS_PER_SLOT,
epoch_schedule::MINIMUM_SLOTS_PER_EPOCH, epoch_schedule::MINIMUM_SLOTS_PER_EPOCH,
genesis_config::create_genesis_config, genesis_config::create_genesis_config,
instruction::{Instruction, InstructionError}, instruction::{CompiledInstruction, Instruction, InstructionError},
message::{Message, MessageHeader}, message::{Message, MessageHeader},
nonce_instruction, nonce_state, nonce_instruction, nonce_state,
poh_config::PohConfig, poh_config::PohConfig,
@ -3221,7 +3263,8 @@ mod tests {
let lock_result = bank.prepare_batch(&pay_alice, None); let lock_result = bank.prepare_batch(&pay_alice, None);
let results_alice = bank let results_alice = bank
.load_execute_and_commit_transactions(&lock_result, MAX_RECENT_BLOCKHASHES) .load_execute_and_commit_transactions(&lock_result, MAX_RECENT_BLOCKHASHES, false)
.0
.fee_collection_results; .fee_collection_results;
assert_eq!(results_alice[0], Ok(())); assert_eq!(results_alice[0], Ok(()));
@ -4731,4 +4774,94 @@ mod tests {
/* Check fee charged */ /* Check fee charged */
assert_eq!(bank.get_balance(&custodian_pubkey), 4_630_000); assert_eq!(bank.get_balance(&custodian_pubkey), 4_630_000);
} }
#[test]
fn test_collect_balances() {
let (genesis_config, _mint_keypair) = create_genesis_config(500);
let parent = Arc::new(Bank::new(&genesis_config));
let bank0 = Arc::new(new_from_parent(&parent));
let keypair = Keypair::new();
let pubkey0 = Pubkey::new_rand();
let pubkey1 = Pubkey::new_rand();
let program_id = Pubkey::new(&[2; 32]);
let keypair_account = Account::new(8, 0, &program_id);
let account0 = Account::new(11, 0, &program_id);
let program_account = Account::new(1, 10, &Pubkey::default());
bank0.store_account(&keypair.pubkey(), &keypair_account);
bank0.store_account(&pubkey0, &account0);
bank0.store_account(&program_id, &program_account);
let instructions = vec![CompiledInstruction::new(1, &(), vec![0])];
let tx0 = Transaction::new_with_compiled_instructions(
&[&keypair],
&[pubkey0],
Hash::default(),
vec![program_id],
instructions,
);
let instructions = vec![CompiledInstruction::new(1, &(), vec![0])];
let tx1 = Transaction::new_with_compiled_instructions(
&[&keypair],
&[pubkey1],
Hash::default(),
vec![program_id],
instructions,
);
let balances = bank0.collect_balances(&[tx0, tx1]);
assert_eq!(balances.len(), 2);
assert_eq!(balances[0], vec![8, 11, 1]);
assert_eq!(balances[1], vec![8, 0, 1]);
}
#[test]
fn test_pre_post_transaction_balances() {
let (mut genesis_config, _mint_keypair) = create_genesis_config(500);
let fee_calculator = FeeCalculator::new(1, 0);
genesis_config.fee_calculator = fee_calculator;
let parent = Arc::new(Bank::new(&genesis_config));
let bank0 = Arc::new(new_from_parent(&parent));
let keypair0 = Keypair::new();
let keypair1 = Keypair::new();
let pubkey0 = Pubkey::new_rand();
let pubkey1 = Pubkey::new_rand();
let pubkey2 = Pubkey::new_rand();
let keypair0_account = Account::new(8, 0, &Pubkey::default());
let keypair1_account = Account::new(9, 0, &Pubkey::default());
let account0 = Account::new(11, 0, &&Pubkey::default());
bank0.store_account(&keypair0.pubkey(), &keypair0_account);
bank0.store_account(&keypair1.pubkey(), &keypair1_account);
bank0.store_account(&pubkey0, &account0);
let blockhash = bank0.last_blockhash();
let tx0 = system_transaction::transfer(&keypair0, &pubkey0, 2, blockhash.clone());
let tx1 = system_transaction::transfer(&Keypair::new(), &pubkey1, 2, blockhash.clone());
let tx2 = system_transaction::transfer(&keypair1, &pubkey2, 12, blockhash.clone());
let txs = vec![tx0, tx1, tx2];
let lock_result = bank0.prepare_batch(&txs, None);
let (transaction_results, transaction_balances_set) =
bank0.load_execute_and_commit_transactions(&lock_result, MAX_RECENT_BLOCKHASHES, true);
assert_eq!(transaction_balances_set.pre_balances.len(), 3);
assert_eq!(transaction_balances_set.post_balances.len(), 3);
assert!(transaction_results.processing_results[0].0.is_ok());
assert_eq!(transaction_balances_set.pre_balances[0], vec![8, 11, 1]);
assert_eq!(transaction_balances_set.post_balances[0], vec![5, 13, 1]);
// Failed transactions still produce balance sets
// This is a TransactionError - not possible to charge fees
assert!(transaction_results.processing_results[1].0.is_err());
assert_eq!(transaction_balances_set.pre_balances[1], vec![0, 0, 1]);
assert_eq!(transaction_balances_set.post_balances[1], vec![0, 0, 1]);
// Failed transactions still produce balance sets
// This is an InstructionError - fees charged
assert!(transaction_results.processing_results[2].0.is_err());
assert_eq!(transaction_balances_set.pre_balances[2], vec![9, 0, 1]);
assert_eq!(transaction_balances_set.post_balances[2], vec![8, 0, 1]);
}
} }