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)]
#[serde(rename_all = "camelCase")]
pub struct RpcTransactionStatus {
pub status: Result<()>,
pub fee: u64,
pub pre_balances: Vec<u64>,
pub post_balances: Vec<u64>,
}
#[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_runtime::{
accounts_db::ErrorCounters,
bank::{Bank, TransactionProcessResult},
bank::{Bank, TransactionBalancesSet, TransactionProcessResult},
transaction_batch::TransactionBatch,
};
use solana_sdk::{
@ -511,6 +511,11 @@ impl BankingStage {
// TODO: Banking stage threads should be prioritized to complete faster then this queue
// expires.
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) =
bank.load_and_execute_transactions(batch, MAX_PROCESSING_AGE);
load_execute_time.stop();
@ -541,11 +546,14 @@ impl BankingStage {
signature_count,
)
.processing_results;
if let Some(sender) = transaction_status_sender {
let post_balances = bank.collect_balances(txs);
send_transaction_status_batch(
bank.clone(),
batch.transactions(),
transaction_statuses,
TransactionBalancesSet::new(pre_balances, post_balances),
sender,
);
}

View File

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

View File

@ -4550,6 +4550,12 @@ pub mod tests {
.filter(|entry| !entry.is_tick())
.flat_map(|entry| entry.transactions)
.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];
ledger
.transaction_status_cf
@ -4558,6 +4564,8 @@ pub mod tests {
&RpcTransactionStatus {
status: Ok(()),
fee: 42,
pre_balances: pre_balances.clone(),
post_balances: post_balances.clone(),
},
)
.unwrap();
@ -4568,6 +4576,8 @@ pub mod tests {
&RpcTransactionStatus {
status: Ok(()),
fee: 42,
pre_balances: pre_balances.clone(),
post_balances: post_balances.clone(),
},
)
.unwrap();
@ -4576,6 +4586,8 @@ pub mod tests {
Some(RpcTransactionStatus {
status: Ok(()),
fee: 42,
pre_balances,
post_balances,
}),
)
})
@ -4694,6 +4706,9 @@ pub mod tests {
let blocktree = Blocktree::open(&blocktree_path).unwrap();
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
assert!(transaction_status_cf
.get((0, Signature::default()))
@ -4708,18 +4723,27 @@ pub mod tests {
status: solana_sdk::transaction::Result::<()>::Err(
TransactionError::AccountNotFound
),
fee: 5u64
fee: 5u64,
pre_balances: pre_balances_vec.clone(),
post_balances: post_balances_vec.clone(),
},
)
.is_ok());
// result found
let RpcTransactionStatus { status, fee } = transaction_status_cf
let RpcTransactionStatus {
status,
fee,
pre_balances,
post_balances,
} = transaction_status_cf
.get((0, Signature::default()))
.unwrap()
.unwrap();
assert_eq!(status, Err(TransactionError::AccountNotFound));
assert_eq!(fee, 5u64);
assert_eq!(pre_balances, pre_balances_vec);
assert_eq!(post_balances, post_balances_vec);
// insert value
assert!(transaction_status_cf
@ -4727,13 +4751,20 @@ pub mod tests {
(9, Signature::default()),
&RpcTransactionStatus {
status: solana_sdk::transaction::Result::<()>::Ok(()),
fee: 9u64
fee: 9u64,
pre_balances: pre_balances_vec.clone(),
post_balances: post_balances_vec.clone(),
},
)
.is_ok());
// result found
let RpcTransactionStatus { status, fee } = transaction_status_cf
let RpcTransactionStatus {
status,
fee,
pre_balances,
post_balances,
} = transaction_status_cf
.get((9, Signature::default()))
.unwrap()
.unwrap();
@ -4741,6 +4772,8 @@ pub mod tests {
// deserialize
assert_eq!(status, Ok(()));
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");
}
@ -4786,6 +4819,8 @@ pub mod tests {
TransactionError::AccountNotFound,
),
fee: x,
pre_balances: vec![],
post_balances: vec![],
},
)
.unwrap();

View File

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

View File

@ -161,6 +161,20 @@ pub struct TransactionResults {
pub fee_collection_results: Vec<Result<()>>,
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)]
pub enum HashAgeKind {
@ -1030,6 +1044,18 @@ impl Bank {
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) {
if 0 != error_counters.blockhash_not_found {
inc_new_counter_error!(
@ -1372,24 +1398,40 @@ impl Bank {
&self,
batch: &TransactionBatch,
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) =
self.load_and_execute_transactions(batch, max_age);
self.commit_transactions(
let results = self.commit_transactions(
batch.transactions(),
batch.iteration_order(),
&mut loaded_accounts,
&executed,
tx_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]
pub fn process_transactions(&self, txs: &[Transaction]) -> Vec<Result<()>> {
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
}
@ -1816,7 +1858,7 @@ mod tests {
clock::DEFAULT_TICKS_PER_SLOT,
epoch_schedule::MINIMUM_SLOTS_PER_EPOCH,
genesis_config::create_genesis_config,
instruction::{Instruction, InstructionError},
instruction::{CompiledInstruction, Instruction, InstructionError},
message::{Message, MessageHeader},
nonce_instruction, nonce_state,
poh_config::PohConfig,
@ -3221,7 +3263,8 @@ mod tests {
let lock_result = bank.prepare_batch(&pay_alice, None);
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;
assert_eq!(results_alice[0], Ok(()));
@ -4731,4 +4774,94 @@ mod tests {
/* Check fee charged */
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]);
}
}