randomize tx ordering (#4978)

Summary of Changes:
This change adds functionality to randomize tx execution for every entry. It does this by implementing OrderedIterator that iterates tx slice as per the order specified. The order is generated randomly for every entry.
This commit is contained in:
Parth
2019-08-28 21:08:32 +05:30
committed by GitHub
parent 1609765740
commit 7dfb735db9
12 changed files with 451 additions and 99 deletions

View File

@ -10,14 +10,18 @@ use rand::{thread_rng, Rng};
use rayon::prelude::*; use rayon::prelude::*;
use solana_core::banking_stage::{create_test_recorder, BankingStage}; use solana_core::banking_stage::{create_test_recorder, BankingStage};
use solana_core::blocktree::{get_tmp_ledger_path, Blocktree}; use solana_core::blocktree::{get_tmp_ledger_path, Blocktree};
use solana_core::blocktree_processor::process_entries;
use solana_core::cluster_info::ClusterInfo; use solana_core::cluster_info::ClusterInfo;
use solana_core::cluster_info::Node; use solana_core::cluster_info::Node;
use solana_core::entry::next_hash;
use solana_core::entry::Entry;
use solana_core::genesis_utils::{create_genesis_block, GenesisBlockInfo}; use solana_core::genesis_utils::{create_genesis_block, GenesisBlockInfo};
use solana_core::packet::to_packets_chunked; use solana_core::packet::to_packets_chunked;
use solana_core::poh_recorder::WorkingBankEntries; use solana_core::poh_recorder::WorkingBankEntries;
use solana_core::service::Service; use solana_core::service::Service;
use solana_core::test_tx::test_tx; use solana_core::test_tx::test_tx;
use solana_runtime::bank::Bank; use solana_runtime::bank::Bank;
use solana_sdk::genesis_block::GenesisBlock;
use solana_sdk::hash::Hash; use solana_sdk::hash::Hash;
use solana_sdk::pubkey::Pubkey; use solana_sdk::pubkey::Pubkey;
use solana_sdk::signature::Keypair; use solana_sdk::signature::Keypair;
@ -264,3 +268,83 @@ fn bench_banking_stage_multi_accounts(bencher: &mut Bencher) {
fn bench_banking_stage_multi_programs(bencher: &mut Bencher) { fn bench_banking_stage_multi_programs(bencher: &mut Bencher) {
bench_banking(bencher, TransactionType::Programs); bench_banking(bencher, TransactionType::Programs);
} }
fn simulate_process_entries(
randomize_txs: bool,
mint_keypair: &Keypair,
mut tx_vector: Vec<Transaction>,
genesis_block: &GenesisBlock,
keypairs: &Vec<Keypair>,
initial_lamports: u64,
num_accounts: usize,
) {
let bank = Bank::new(genesis_block);
for i in 0..(num_accounts / 2) {
bank.transfer(initial_lamports, mint_keypair, &keypairs[i * 2].pubkey())
.unwrap();
}
for i in (0..num_accounts).step_by(2) {
tx_vector.push(system_transaction::transfer(
&keypairs[i],
&keypairs[i + 1].pubkey(),
initial_lamports,
bank.last_blockhash(),
));
}
// Transfer lamports to each other
let entry = Entry {
num_hashes: 1,
hash: next_hash(&bank.last_blockhash(), 1, &tx_vector),
transactions: tx_vector,
};
process_entries(&bank, &vec![entry], randomize_txs).unwrap();
}
fn bench_process_entries(randomize_txs: bool, bencher: &mut Bencher) {
// entropy multiplier should be big enough to provide sufficient entropy
// but small enough to not take too much time while executing the test.
let entropy_multiplier: usize = 25;
let initial_lamports = 100;
// number of accounts need to be in multiple of 4 for correct
// execution of the test.
let num_accounts = entropy_multiplier * 4;
let GenesisBlockInfo {
genesis_block,
mint_keypair,
..
} = create_genesis_block((num_accounts + 1) as u64 * initial_lamports);
let mut keypairs: Vec<Keypair> = vec![];
let tx_vector: Vec<Transaction> = Vec::with_capacity(num_accounts / 2);
for _ in 0..num_accounts {
let keypair = Keypair::new();
keypairs.push(keypair);
}
bencher.iter(|| {
simulate_process_entries(
randomize_txs,
&mint_keypair,
tx_vector.clone(),
&genesis_block,
&keypairs,
initial_lamports,
num_accounts,
);
});
}
#[bench]
fn bench_process_entries_without_order_shuffeling(bencher: &mut Bencher) {
bench_process_entries(false, bencher);
}
#[bench]
fn bench_process_entries_with_order_shuffeling(bencher: &mut Bencher) {
bench_process_entries(true, bencher);
}

View File

@ -489,7 +489,7 @@ 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 (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(txs, lock_results, MAX_PROCESSING_AGE); bank.load_and_execute_transactions(txs, None, lock_results, MAX_PROCESSING_AGE);
load_execute_time.stop(); load_execute_time.stop();
let freeze_lock = bank.freeze_lock(); let freeze_lock = bank.freeze_lock();
@ -510,6 +510,7 @@ impl BankingStage {
if num_to_commit != 0 { if num_to_commit != 0 {
bank.commit_transactions( bank.commit_transactions(
txs, txs,
None,
&mut loaded_accounts, &mut loaded_accounts,
&results, &results,
tx_count, tx_count,
@ -541,7 +542,7 @@ impl BankingStage {
let mut lock_time = Measure::start("lock_time"); let mut lock_time = Measure::start("lock_time");
// Once accounts are locked, other threads cannot encode transactions that will modify the // Once accounts are locked, other threads cannot encode transactions that will modify the
// same account state // same account state
let lock_results = bank.lock_accounts(txs); let lock_results = bank.lock_accounts(txs, None);
lock_time.stop(); lock_time.stop();
let (result, mut retryable_txs) = let (result, mut retryable_txs) =
@ -696,6 +697,7 @@ impl BankingStage {
// Drop the transaction if it will expire by the time the next node receives and processes it // Drop the transaction if it will expire by the time the next node receives and processes it
let result = bank.check_transactions( let result = bank.check_transactions(
transactions, transactions,
None,
&filter, &filter,
(MAX_PROCESSING_AGE) (MAX_PROCESSING_AGE)
.saturating_sub(MAX_TRANSACTION_FORWARDING_DELAY) .saturating_sub(MAX_TRANSACTION_FORWARDING_DELAY)

View File

@ -15,6 +15,9 @@ use std::result;
use std::sync::Arc; use std::sync::Arc;
use std::time::{Duration, Instant}; use std::time::{Duration, Instant};
use rand::seq::SliceRandom;
use rand::thread_rng;
pub const NUM_THREADS: u32 = 10; pub const NUM_THREADS: u32 = 10;
use std::cell::RefCell; use std::cell::RefCell;
@ -32,35 +35,46 @@ fn first_err(results: &[Result<()>]) -> Result<()> {
Ok(()) Ok(())
} }
fn par_execute_entries(bank: &Bank, entries: &[(&Entry, LockedAccountsResults)]) -> Result<()> { fn par_execute_entries(
bank: &Bank,
entries: &[(&Entry, LockedAccountsResults, bool, Vec<usize>)],
) -> Result<()> {
inc_new_counter_debug!("bank-par_execute_entries-count", entries.len()); inc_new_counter_debug!("bank-par_execute_entries-count", entries.len());
let results: Vec<Result<()>> = PAR_THREAD_POOL.with(|thread_pool| { let results: Vec<Result<()>> = PAR_THREAD_POOL.with(|thread_pool| {
thread_pool.borrow().install(|| { thread_pool.borrow().install(|| {
entries entries
.into_par_iter() .into_par_iter()
.map(|(e, locked_accounts)| { .map(
let results = bank.load_execute_and_commit_transactions( |(e, locked_accounts, randomize_tx_order, random_txs_execution_order)| {
&e.transactions, let tx_execution_order: Option<&[usize]> = if *randomize_tx_order {
locked_accounts, Some(random_txs_execution_order)
MAX_RECENT_BLOCKHASHES, } else {
); None
let mut first_err = None; };
for (r, tx) in results.iter().zip(e.transactions.iter()) { let results = bank.load_execute_and_commit_transactions(
if let Err(ref e) = r { &e.transactions,
if first_err.is_none() { tx_execution_order,
first_err = Some(r.clone()); locked_accounts,
} MAX_RECENT_BLOCKHASHES,
if !Bank::can_commit(&r) { );
warn!("Unexpected validator error: {:?}, tx: {:?}", e, tx); let mut first_err = None;
datapoint_error!( for (r, tx) in results.iter().zip(e.transactions.iter()) {
"validator_process_entry_error", if let Err(ref e) = r {
("error", format!("error: {:?}, tx: {:?}", e, tx), String) if first_err.is_none() {
); first_err = Some(r.clone());
}
if !Bank::can_commit(&r) {
warn!("Unexpected validator error: {:?}, tx: {:?}", e, tx);
datapoint_error!(
"validator_process_entry_error",
("error", format!("error: {:?}, tx: {:?}", e, tx), String)
);
}
} }
} }
} first_err.unwrap_or(Ok(()))
first_err.unwrap_or(Ok(())) },
}) )
.collect() .collect()
}) })
}); });
@ -73,7 +87,11 @@ fn par_execute_entries(bank: &Bank, entries: &[(&Entry, LockedAccountsResults)])
/// 2. Process the locked group in parallel /// 2. Process the locked group in parallel
/// 3. Register the `Tick` if it's available /// 3. Register the `Tick` if it's available
/// 4. Update the leader scheduler, goto 1 /// 4. Update the leader scheduler, goto 1
pub fn process_entries(bank: &Bank, entries: &[Entry]) -> Result<()> { pub fn process_entries(
bank: &Bank,
entries: &[Entry],
randomize_tx_execution_order: bool,
) -> Result<()> {
// accumulator for entries that can be processed in parallel // accumulator for entries that can be processed in parallel
let mut mt_group = vec![]; let mut mt_group = vec![];
for entry in entries { for entry in entries {
@ -86,15 +104,34 @@ pub fn process_entries(bank: &Bank, entries: &[Entry]) -> Result<()> {
} }
// else loop on processing the entry // else loop on processing the entry
loop { loop {
// random_txs_execution_order need to be seperately defined apart from txs_execution_order,
// to satisfy borrow checker.
let mut random_txs_execution_order: Vec<usize> = vec![];
if randomize_tx_execution_order {
random_txs_execution_order = (0..entry.transactions.len()).collect();
random_txs_execution_order.shuffle(&mut thread_rng());
}
let txs_execution_order: Option<&[usize]> = if randomize_tx_execution_order {
Some(&random_txs_execution_order)
} else {
None
};
// try to lock the accounts // try to lock the accounts
let lock_results = bank.lock_accounts(&entry.transactions); let lock_results = bank.lock_accounts(&entry.transactions, txs_execution_order);
let first_lock_err = first_err(lock_results.locked_accounts_results()); let first_lock_err = first_err(lock_results.locked_accounts_results());
// if locking worked // if locking worked
if first_lock_err.is_ok() { if first_lock_err.is_ok() {
// push the entry to the mt_group // push the entry to the mt_group
mt_group.push((entry, lock_results)); mt_group.push((
entry,
lock_results,
randomize_tx_execution_order,
random_txs_execution_order,
));
// done with this entry // done with this entry
break; break;
} }
@ -225,7 +262,7 @@ fn verify_and_process_entries(
return Err(BlocktreeProcessorError::LedgerVerificationFailed); return Err(BlocktreeProcessorError::LedgerVerificationFailed);
} }
process_entries(&bank, &entries).map_err(|err| { process_entries(&bank, &entries, true).map_err(|err| {
warn!( warn!(
"Failed to process entries for slot {}: {:?}", "Failed to process entries for slot {}: {:?}",
bank.slot(), bank.slot(),
@ -417,6 +454,7 @@ pub mod tests {
use solana_sdk::pubkey::Pubkey; use solana_sdk::pubkey::Pubkey;
use solana_sdk::signature::{Keypair, KeypairUtil}; use solana_sdk::signature::{Keypair, KeypairUtil};
use solana_sdk::system_transaction; use solana_sdk::system_transaction;
use solana_sdk::transaction::Transaction;
use solana_sdk::transaction::TransactionError; use solana_sdk::transaction::TransactionError;
pub fn fill_blocktree_slot_with_ticks( pub fn fill_blocktree_slot_with_ticks(
@ -771,7 +809,7 @@ pub mod tests {
); );
// Now ensure the TX is accepted despite pointing to the ID of an empty entry. // Now ensure the TX is accepted despite pointing to the ID of an empty entry.
process_entries(&bank, &slot_entries).unwrap(); process_entries(&bank, &slot_entries, true).unwrap();
assert_eq!(bank.process_transaction(&tx), Ok(())); assert_eq!(bank.process_transaction(&tx), Ok(()));
} }
@ -868,7 +906,7 @@ pub mod tests {
// ensure bank can process a tick // ensure bank can process a tick
assert_eq!(bank.tick_height(), 0); assert_eq!(bank.tick_height(), 0);
let tick = next_entry(&genesis_block.hash(), 1, vec![]); let tick = next_entry(&genesis_block.hash(), 1, vec![]);
assert_eq!(process_entries(&bank, &[tick.clone()]), Ok(())); assert_eq!(process_entries(&bank, &[tick.clone()], true), Ok(()));
assert_eq!(bank.tick_height(), 1); assert_eq!(bank.tick_height(), 1);
} }
@ -900,7 +938,7 @@ pub mod tests {
bank.last_blockhash(), bank.last_blockhash(),
); );
let entry_2 = next_entry(&entry_1.hash, 1, vec![tx]); let entry_2 = next_entry(&entry_1.hash, 1, vec![tx]);
assert_eq!(process_entries(&bank, &[entry_1, entry_2]), Ok(())); assert_eq!(process_entries(&bank, &[entry_1, entry_2], true), Ok(()));
assert_eq!(bank.get_balance(&keypair1.pubkey()), 2); assert_eq!(bank.get_balance(&keypair1.pubkey()), 2);
assert_eq!(bank.get_balance(&keypair2.pubkey()), 2); assert_eq!(bank.get_balance(&keypair2.pubkey()), 2);
assert_eq!(bank.last_blockhash(), blockhash); assert_eq!(bank.last_blockhash(), blockhash);
@ -954,7 +992,7 @@ pub mod tests {
); );
assert_eq!( assert_eq!(
process_entries(&bank, &[entry_1_to_mint, entry_2_to_3_mint_to_1]), process_entries(&bank, &[entry_1_to_mint, entry_2_to_3_mint_to_1], false),
Ok(()) Ok(())
); );
@ -1022,7 +1060,8 @@ pub mod tests {
assert!(process_entries( assert!(process_entries(
&bank, &bank,
&[entry_1_to_mint.clone(), entry_2_to_3_mint_to_1.clone()] &[entry_1_to_mint.clone(), entry_2_to_3_mint_to_1.clone()],
false
) )
.is_err()); .is_err());
@ -1033,13 +1072,13 @@ pub mod tests {
// Check all accounts are unlocked // Check all accounts are unlocked
let txs1 = &entry_1_to_mint.transactions[..]; let txs1 = &entry_1_to_mint.transactions[..];
let txs2 = &entry_2_to_3_mint_to_1.transactions[..]; let txs2 = &entry_2_to_3_mint_to_1.transactions[..];
let locked_accounts1 = bank.lock_accounts(txs1); let locked_accounts1 = bank.lock_accounts(txs1, None);
for result in locked_accounts1.locked_accounts_results() { for result in locked_accounts1.locked_accounts_results() {
assert!(result.is_ok()); assert!(result.is_ok());
} }
// txs1 and txs2 have accounts that conflict, so we must drop txs1 first // txs1 and txs2 have accounts that conflict, so we must drop txs1 first
drop(locked_accounts1); drop(locked_accounts1);
let locked_accounts2 = bank.lock_accounts(txs2); let locked_accounts2 = bank.lock_accounts(txs2, None);
for result in locked_accounts2.locked_accounts_results() { for result in locked_accounts2.locked_accounts_results() {
assert!(result.is_ok()); assert!(result.is_ok());
} }
@ -1131,7 +1170,8 @@ pub mod tests {
entry_1_to_mint.clone(), entry_1_to_mint.clone(),
entry_2_to_3_and_1_to_mint.clone(), entry_2_to_3_and_1_to_mint.clone(),
entry_conflict_itself.clone() entry_conflict_itself.clone()
] ],
false
) )
.is_err()); .is_err());
@ -1186,12 +1226,88 @@ pub mod tests {
bank.last_blockhash(), bank.last_blockhash(),
); );
let entry_2 = next_entry(&entry_1.hash, 1, vec![tx]); let entry_2 = next_entry(&entry_1.hash, 1, vec![tx]);
assert_eq!(process_entries(&bank, &[entry_1, entry_2]), Ok(())); assert_eq!(process_entries(&bank, &[entry_1, entry_2], true), Ok(()));
assert_eq!(bank.get_balance(&keypair3.pubkey()), 1); assert_eq!(bank.get_balance(&keypair3.pubkey()), 1);
assert_eq!(bank.get_balance(&keypair4.pubkey()), 1); assert_eq!(bank.get_balance(&keypair4.pubkey()), 1);
assert_eq!(bank.last_blockhash(), blockhash); assert_eq!(bank.last_blockhash(), blockhash);
} }
#[test]
fn test_process_entry_tx_random_execution_no_error() {
// entropy multiplier should be big enough to provide sufficient entropy
// but small enough to not take too much time while executing the test.
let entropy_multiplier: usize = 25;
let initial_lamports = 100;
// number of accounts need to be in multiple of 4 for correct
// execution of the test.
let num_accounts = entropy_multiplier * 4;
let GenesisBlockInfo {
genesis_block,
mint_keypair,
..
} = create_genesis_block((num_accounts + 1) as u64 * initial_lamports);
let bank = Bank::new(&genesis_block);
let mut keypairs: Vec<Keypair> = vec![];
for _ in 0..num_accounts {
let keypair = Keypair::new();
let create_account_tx = system_transaction::create_user_account(
&mint_keypair,
&keypair.pubkey(),
0,
bank.last_blockhash(),
);
assert_eq!(bank.process_transaction(&create_account_tx), Ok(()));
assert_matches!(
bank.transfer(initial_lamports, &mint_keypair, &keypair.pubkey()),
Ok(_)
);
keypairs.push(keypair);
}
let mut tx_vector: Vec<Transaction> = vec![];
for i in (0..num_accounts).step_by(4) {
tx_vector.append(&mut vec![
system_transaction::transfer(
&keypairs[i + 1],
&keypairs[i].pubkey(),
initial_lamports,
bank.last_blockhash(),
),
system_transaction::transfer(
&keypairs[i + 3],
&keypairs[i + 2].pubkey(),
initial_lamports,
bank.last_blockhash(),
),
]);
}
// Transfer lamports to each other
let entry = next_entry(&bank.last_blockhash(), 1, tx_vector);
assert_eq!(process_entries(&bank, &vec![entry], true), Ok(()));
bank.squash();
// Even number keypair should have balance of 2 * initial_lamports and
// odd number keypair should have balance of 0, which proves
// that even in case of random order of execution, overall state remains
// consistent.
for i in 0..num_accounts {
if i % 2 == 0 {
assert_eq!(
bank.get_balance(&keypairs[i].pubkey()),
2 * initial_lamports
);
} else {
assert_eq!(bank.get_balance(&keypairs[i].pubkey()), 0);
}
}
}
#[test] #[test]
fn test_process_entries_2_entries_tick() { fn test_process_entries_2_entries_tick() {
let GenesisBlockInfo { let GenesisBlockInfo {
@ -1239,7 +1355,11 @@ pub mod tests {
); );
let entry_2 = next_entry(&tick.hash, 1, vec![tx]); let entry_2 = next_entry(&tick.hash, 1, vec![tx]);
assert_eq!( assert_eq!(
process_entries(&bank, &[entry_1.clone(), tick.clone(), entry_2.clone()]), process_entries(
&bank,
&[entry_1.clone(), tick.clone(), entry_2.clone()],
true
),
Ok(()) Ok(())
); );
assert_eq!(bank.get_balance(&keypair3.pubkey()), 1); assert_eq!(bank.get_balance(&keypair3.pubkey()), 1);
@ -1254,7 +1374,7 @@ pub mod tests {
); );
let entry_3 = next_entry(&entry_2.hash, 1, vec![tx]); let entry_3 = next_entry(&entry_2.hash, 1, vec![tx]);
assert_eq!( assert_eq!(
process_entries(&bank, &[entry_3]), process_entries(&bank, &[entry_3], true),
Err(TransactionError::AccountNotFound) Err(TransactionError::AccountNotFound)
); );
} }
@ -1335,7 +1455,7 @@ pub mod tests {
); );
assert_eq!( assert_eq!(
process_entries(&bank, &[entry_1_to_mint]), process_entries(&bank, &[entry_1_to_mint], false),
Err(TransactionError::AccountInUse) Err(TransactionError::AccountInUse)
); );
@ -1461,7 +1581,7 @@ pub mod tests {
}) })
.collect(); .collect();
info!("paying iteration {}", i); info!("paying iteration {}", i);
process_entries(&bank, &entries).expect("paying failed"); process_entries(&bank, &entries, true).expect("paying failed");
let entries: Vec<_> = (0..NUM_TRANSFERS) let entries: Vec<_> = (0..NUM_TRANSFERS)
.map(|i| { .map(|i| {
@ -1479,7 +1599,7 @@ pub mod tests {
.collect(); .collect();
info!("refunding iteration {}", i); info!("refunding iteration {}", i);
process_entries(&bank, &entries).expect("refunding failed"); process_entries(&bank, &entries, true).expect("refunding failed");
// advance to next block // advance to next block
process_entries( process_entries(
@ -1487,6 +1607,7 @@ pub mod tests {
&(0..bank.ticks_per_slot()) &(0..bank.ticks_per_slot())
.map(|_| next_entry_mut(&mut hash, 1, vec![])) .map(|_| next_entry_mut(&mut hash, 1, vec![]))
.collect::<Vec<_>>(), .collect::<Vec<_>>(),
true,
) )
.expect("process ticks failed"); .expect("process ticks failed");

View File

@ -189,7 +189,7 @@ pub fn hash_transactions(transactions: &[Transaction]) -> Hash {
/// a signature, the final hash will be a hash of both the previous ID and /// a signature, the final hash will be a hash of both the previous ID and
/// the signature. If num_hashes is zero and there's no transaction data, /// the signature. If num_hashes is zero and there's no transaction data,
/// start_hash is returned. /// start_hash is returned.
fn next_hash(start_hash: &Hash, num_hashes: u64, transactions: &[Transaction]) -> Hash { pub fn next_hash(start_hash: &Hash, num_hashes: u64, transactions: &[Transaction]) -> Hash {
if num_hashes == 0 && transactions.is_empty() { if num_hashes == 0 && transactions.is_empty() {
return *start_hash; return *start_hash;
} }

View File

@ -726,7 +726,7 @@ impl ReplayStage {
); );
return Err(Error::BlobError(BlobError::VerificationFailed)); return Err(Error::BlobError(BlobError::VerificationFailed));
} }
blocktree_processor::process_entries(bank, entries)?; blocktree_processor::process_entries(bank, entries, true)?;
Ok(()) Ok(())
} }

View File

@ -743,6 +743,7 @@ mod tests {
blocktree_processor::process_entries( blocktree_processor::process_entries(
&bank, &bank,
&entry::create_ticks(64, bank.last_blockhash()), &entry::create_ticks(64, bank.last_blockhash()),
true,
) )
.expect("failed process entries"); .expect("failed process entries");
last_bank = Arc::new(bank); last_bank = Arc::new(bank);
@ -863,6 +864,7 @@ mod tests {
DEFAULT_TICKS_PER_SLOT * next_bank.slots_per_segment() + 1, DEFAULT_TICKS_PER_SLOT * next_bank.slots_per_segment() + 1,
bank.last_blockhash(), bank.last_blockhash(),
), ),
true,
) )
.unwrap(); .unwrap();
let message = Message::new_with_payer(vec![mining_proof_ix], Some(&mint_keypair.pubkey())); let message = Message::new_with_payer(vec![mining_proof_ix], Some(&mint_keypair.pubkey()));

View File

@ -0,0 +1,19 @@
#![feature(test)]
extern crate test;
use rand::seq::SliceRandom;
use rand::thread_rng;
use solana_runtime::transaction_utils::OrderedIterator;
use test::Bencher;
#[bench]
fn bench_ordered_iterator_with_order_shuffling(bencher: &mut Bencher) {
let vec: Vec<usize> = (0..100_usize).collect();
bencher.iter(|| {
let mut order: Vec<usize> = (0..100_usize).collect();
order.shuffle(&mut thread_rng());
let _ordered_iterator_resp: Vec<&usize> =
OrderedIterator::new(&vec, Some(&order)).collect();
});
}

View File

@ -23,6 +23,8 @@ use std::path::Path;
use std::sync::atomic::{AtomicU64, Ordering}; use std::sync::atomic::{AtomicU64, Ordering};
use std::sync::{Arc, Mutex, RwLock, RwLockReadGuard, RwLockWriteGuard}; use std::sync::{Arc, Mutex, RwLock, RwLockReadGuard, RwLockWriteGuard};
use crate::transaction_utils::OrderedIterator;
#[derive(Default, Debug)] #[derive(Default, Debug)]
struct CreditOnlyLock { struct CreditOnlyLock {
credits: AtomicU64, credits: AtomicU64,
@ -210,6 +212,7 @@ impl Accounts {
&self, &self,
ancestors: &HashMap<Fork, usize>, ancestors: &HashMap<Fork, usize>,
txs: &[Transaction], txs: &[Transaction],
txs_iteration_order: Option<&[usize]>,
lock_results: Vec<Result<()>>, lock_results: Vec<Result<()>>,
hash_queue: &BlockhashQueue, hash_queue: &BlockhashQueue,
error_counters: &mut ErrorCounters, error_counters: &mut ErrorCounters,
@ -226,7 +229,7 @@ impl Accounts {
//TODO: two locks usually leads to deadlocks, should this be one structure? //TODO: two locks usually leads to deadlocks, should this be one structure?
let accounts_index = self.accounts_db.accounts_index.read().unwrap(); let accounts_index = self.accounts_db.accounts_index.read().unwrap();
let storage = self.accounts_db.storage.read().unwrap(); let storage = self.accounts_db.storage.read().unwrap();
txs.iter() OrderedIterator::new(txs, txs_iteration_order)
.zip(lock_results.into_iter()) .zip(lock_results.into_iter())
.map(|etx| match etx { .map(|etx| match etx {
(tx, Ok(())) => { (tx, Ok(())) => {
@ -477,10 +480,13 @@ impl Accounts {
/// This function will prevent multiple threads from modifying the same account state at the /// This function will prevent multiple threads from modifying the same account state at the
/// same time /// same time
#[must_use] #[must_use]
pub fn lock_accounts(&self, txs: &[Transaction]) -> Vec<Result<()>> { pub fn lock_accounts(
&self,
txs: &[Transaction],
txs_iteration_order: Option<&[usize]>,
) -> Vec<Result<()>> {
let mut error_counters = ErrorCounters::default(); let mut error_counters = ErrorCounters::default();
let rv = txs let rv = OrderedIterator::new(txs, txs_iteration_order)
.iter()
.map(|tx| { .map(|tx| {
let message = &tx.message(); let message = &tx.message();
Self::lock_account( Self::lock_account(
@ -521,6 +527,7 @@ impl Accounts {
&self, &self,
fork: Fork, fork: Fork,
txs: &[Transaction], txs: &[Transaction],
txs_iteration_order: Option<&[usize]>,
res: &[Result<()>], res: &[Result<()>],
loaded: &mut [Result<( loaded: &mut [Result<(
TransactionAccounts, TransactionAccounts,
@ -529,7 +536,8 @@ impl Accounts {
TransactionRents, TransactionRents,
)>], )>],
) { ) {
let accounts_to_store = self.collect_accounts_to_store(txs, res, loaded); let accounts_to_store =
self.collect_accounts_to_store(txs, txs_iteration_order, res, loaded);
self.accounts_db.store(fork, &accounts_to_store); self.accounts_db.store(fork, &accounts_to_store);
} }
@ -601,6 +609,7 @@ impl Accounts {
fn collect_accounts_to_store<'a>( fn collect_accounts_to_store<'a>(
&self, &self,
txs: &'a [Transaction], txs: &'a [Transaction],
txs_iteration_order: Option<&'a [usize]>,
res: &'a [Result<()>], res: &'a [Result<()>],
loaded: &'a mut [Result<( loaded: &'a mut [Result<(
TransactionAccounts, TransactionAccounts,
@ -610,12 +619,16 @@ impl Accounts {
)>], )>],
) -> Vec<(&'a Pubkey, &'a Account)> { ) -> Vec<(&'a Pubkey, &'a Account)> {
let mut accounts = Vec::new(); let mut accounts = Vec::new();
for (i, raccs) in loaded.iter_mut().enumerate() { for (i, (raccs, tx)) in loaded
.iter_mut()
.zip(OrderedIterator::new(txs, txs_iteration_order))
.enumerate()
{
if res[i].is_err() || raccs.is_err() { if res[i].is_err() || raccs.is_err() {
continue; continue;
} }
let message = &txs[i].message(); let message = &tx.message();
let acc = raccs.as_mut().unwrap(); let acc = raccs.as_mut().unwrap();
for (((i, key), account), credit) in message for (((i, key), account), credit) in message
.account_keys .account_keys
@ -700,6 +713,7 @@ mod tests {
let res = accounts.load_accounts( let res = accounts.load_accounts(
&ancestors, &ancestors,
&[tx], &[tx],
None,
vec![Ok(())], vec![Ok(())],
&hash_queue, &hash_queue,
error_counters, error_counters,
@ -1276,7 +1290,7 @@ mod tests {
instructions, instructions,
); );
let tx = Transaction::new(&[&keypair0], message, Hash::default()); let tx = Transaction::new(&[&keypair0], message, Hash::default());
let results0 = accounts.lock_accounts(&[tx.clone()]); let results0 = accounts.lock_accounts(&[tx.clone()], None);
assert!(results0[0].is_ok()); assert!(results0[0].is_ok());
assert_eq!( assert_eq!(
@ -1315,7 +1329,7 @@ mod tests {
); );
let tx1 = Transaction::new(&[&keypair1], message, Hash::default()); let tx1 = Transaction::new(&[&keypair1], message, Hash::default());
let txs = vec![tx0, tx1]; let txs = vec![tx0, tx1];
let results1 = accounts.lock_accounts(&txs); let results1 = accounts.lock_accounts(&txs, None);
assert!(results1[0].is_ok()); // Credit-only account (keypair1) can be referenced multiple times assert!(results1[0].is_ok()); // Credit-only account (keypair1) can be referenced multiple times
assert!(results1[1].is_err()); // Credit-only account (keypair1) cannot also be locked as credit-debit assert!(results1[1].is_err()); // Credit-only account (keypair1) cannot also be locked as credit-debit
@ -1347,7 +1361,7 @@ mod tests {
instructions, instructions,
); );
let tx = Transaction::new(&[&keypair1], message, Hash::default()); let tx = Transaction::new(&[&keypair1], message, Hash::default());
let results2 = accounts.lock_accounts(&[tx]); let results2 = accounts.lock_accounts(&[tx], None);
assert!(results2[0].is_ok()); // Now keypair1 account can be locked as credit-debit assert!(results2[0].is_ok()); // Now keypair1 account can be locked as credit-debit
@ -1409,7 +1423,7 @@ mod tests {
let exit_clone = exit_clone.clone(); let exit_clone = exit_clone.clone();
loop { loop {
let txs = vec![credit_debit_tx.clone()]; let txs = vec![credit_debit_tx.clone()];
let results = accounts_clone.clone().lock_accounts(&txs); let results = accounts_clone.clone().lock_accounts(&txs, None);
for result in results.iter() { for result in results.iter() {
if result.is_ok() { if result.is_ok() {
counter_clone.clone().fetch_add(1, Ordering::SeqCst); counter_clone.clone().fetch_add(1, Ordering::SeqCst);
@ -1424,7 +1438,7 @@ mod tests {
let counter_clone = counter.clone(); let counter_clone = counter.clone();
for _ in 0..5 { for _ in 0..5 {
let txs = vec![credit_only_tx.clone()]; let txs = vec![credit_only_tx.clone()];
let results = accounts_arc.clone().lock_accounts(&txs); let results = accounts_arc.clone().lock_accounts(&txs, None);
if results[0].is_ok() { if results[0].is_ok() {
let counter_value = counter_clone.clone().load(Ordering::SeqCst); let counter_value = counter_clone.clone().load(Ordering::SeqCst);
thread::sleep(time::Duration::from_millis(50)); thread::sleep(time::Duration::from_millis(50));
@ -1577,7 +1591,8 @@ mod tests {
}, },
); );
} }
let collected_accounts = accounts.collect_accounts_to_store(&txs, &loaders, &mut loaded); let collected_accounts =
accounts.collect_accounts_to_store(&txs, None, &loaders, &mut loaded);
assert_eq!(collected_accounts.len(), 2); assert_eq!(collected_accounts.len(), 2);
assert!(collected_accounts assert!(collected_accounts
.iter() .iter()

View File

@ -2,6 +2,7 @@
//! programs. It offers a high-level API that signs transactions //! programs. It offers a high-level API that signs transactions
//! on behalf of the caller, and a low-level API for when they have //! on behalf of the caller, and a low-level API for when they have
//! already been signed and verified. //! already been signed and verified.
use crate::transaction_utils::OrderedIterator;
use crate::{ use crate::{
accounts::{ accounts::{
Accounts, TransactionAccounts, TransactionCredits, TransactionLoaders, TransactionRents, Accounts, TransactionAccounts, TransactionCredits, TransactionLoaders, TransactionRents,
@ -674,9 +675,14 @@ impl Bank {
} }
} }
fn update_transaction_statuses(&self, txs: &[Transaction], res: &[Result<()>]) { fn update_transaction_statuses(
&self,
txs: &[Transaction],
txs_iteration_order: Option<&[usize]>,
res: &[Result<()>],
) {
let mut status_cache = self.src.status_cache.write().unwrap(); let mut status_cache = self.src.status_cache.write().unwrap();
for (i, tx) in txs.iter().enumerate() { for (i, tx) in OrderedIterator::new(txs, txs_iteration_order).enumerate() {
if Self::can_commit(&res[i]) && !tx.signatures.is_empty() { if Self::can_commit(&res[i]) && !tx.signatures.is_empty() {
status_cache.insert( status_cache.insert(
&tx.message().recent_blockhash, &tx.message().recent_blockhash,
@ -763,13 +769,14 @@ impl Bank {
pub fn lock_accounts<'a, 'b>( pub fn lock_accounts<'a, 'b>(
&'a self, &'a self,
txs: &'b [Transaction], txs: &'b [Transaction],
txs_iteration_order: Option<&[usize]>,
) -> LockedAccountsResults<'a, 'b> { ) -> LockedAccountsResults<'a, 'b> {
if self.is_frozen() { if self.is_frozen() {
warn!("=========== FIXME: lock_accounts() working on a frozen bank! ================"); warn!("=========== FIXME: lock_accounts() working on a frozen bank! ================");
} }
// TODO: put this assert back in // TODO: put this assert back in
// assert!(!self.is_frozen()); // assert!(!self.is_frozen());
let results = self.rc.accounts.lock_accounts(txs); let results = self.rc.accounts.lock_accounts(txs, txs_iteration_order);
LockedAccountsResults::new(results, &self, txs) LockedAccountsResults::new(results, &self, txs)
} }
@ -786,6 +793,7 @@ impl Bank {
fn load_accounts( fn load_accounts(
&self, &self,
txs: &[Transaction], txs: &[Transaction],
txs_iteration_order: Option<&[usize]>,
results: Vec<Result<()>>, results: Vec<Result<()>>,
error_counters: &mut ErrorCounters, error_counters: &mut ErrorCounters,
) -> Vec< ) -> Vec<
@ -799,6 +807,7 @@ impl Bank {
self.rc.accounts.load_accounts( self.rc.accounts.load_accounts(
&self.ancestors, &self.ancestors,
txs, txs,
txs_iteration_order,
results, results,
&self.blockhash_queue.read().unwrap(), &self.blockhash_queue.read().unwrap(),
error_counters, error_counters,
@ -808,10 +817,11 @@ impl Bank {
fn check_refs( fn check_refs(
&self, &self,
txs: &[Transaction], txs: &[Transaction],
txs_iteration_order: Option<&[usize]>,
lock_results: &[Result<()>], lock_results: &[Result<()>],
error_counters: &mut ErrorCounters, error_counters: &mut ErrorCounters,
) -> Vec<Result<()>> { ) -> Vec<Result<()>> {
txs.iter() OrderedIterator::new(txs, txs_iteration_order)
.zip(lock_results) .zip(lock_results)
.map(|(tx, lock_res)| { .map(|(tx, lock_res)| {
if lock_res.is_ok() && !tx.verify_refs() { if lock_res.is_ok() && !tx.verify_refs() {
@ -826,12 +836,13 @@ impl Bank {
fn check_age( fn check_age(
&self, &self,
txs: &[Transaction], txs: &[Transaction],
txs_iteration_order: Option<&[usize]>,
lock_results: Vec<Result<()>>, lock_results: Vec<Result<()>>,
max_age: usize, max_age: usize,
error_counters: &mut ErrorCounters, error_counters: &mut ErrorCounters,
) -> Vec<Result<()>> { ) -> Vec<Result<()>> {
let hash_queue = self.blockhash_queue.read().unwrap(); let hash_queue = self.blockhash_queue.read().unwrap();
txs.iter() OrderedIterator::new(txs, txs_iteration_order)
.zip(lock_results.into_iter()) .zip(lock_results.into_iter())
.map(|(tx, lock_res)| { .map(|(tx, lock_res)| {
if lock_res.is_ok() if lock_res.is_ok()
@ -848,11 +859,12 @@ impl Bank {
fn check_signatures( fn check_signatures(
&self, &self,
txs: &[Transaction], txs: &[Transaction],
txs_iteration_order: Option<&[usize]>,
lock_results: Vec<Result<()>>, lock_results: Vec<Result<()>>,
error_counters: &mut ErrorCounters, error_counters: &mut ErrorCounters,
) -> Vec<Result<()>> { ) -> Vec<Result<()>> {
let rcache = self.src.status_cache.read().unwrap(); let rcache = self.src.status_cache.read().unwrap();
txs.iter() OrderedIterator::new(txs, txs_iteration_order)
.zip(lock_results.into_iter()) .zip(lock_results.into_iter())
.map(|(tx, lock_res)| { .map(|(tx, lock_res)| {
if tx.signatures.is_empty() { if tx.signatures.is_empty() {
@ -886,13 +898,21 @@ impl Bank {
pub fn check_transactions( pub fn check_transactions(
&self, &self,
txs: &[Transaction], txs: &[Transaction],
txs_iteration_order: Option<&[usize]>,
lock_results: &[Result<()>], lock_results: &[Result<()>],
max_age: usize, max_age: usize,
mut error_counters: &mut ErrorCounters, mut error_counters: &mut ErrorCounters,
) -> Vec<Result<()>> { ) -> Vec<Result<()>> {
let refs_results = self.check_refs(txs, lock_results, &mut error_counters); let refs_results =
let age_results = self.check_age(txs, refs_results, max_age, &mut error_counters); self.check_refs(txs, txs_iteration_order, lock_results, &mut error_counters);
self.check_signatures(txs, age_results, &mut error_counters) let age_results = self.check_age(
txs,
txs_iteration_order,
refs_results,
max_age,
&mut error_counters,
);
self.check_signatures(txs, txs_iteration_order, age_results, &mut error_counters)
} }
fn update_error_counters(error_counters: &ErrorCounters) { fn update_error_counters(error_counters: &ErrorCounters) {
@ -944,6 +964,7 @@ impl Bank {
pub fn load_and_execute_transactions( pub fn load_and_execute_transactions(
&self, &self,
txs: &[Transaction], txs: &[Transaction],
txs_iteration_order: Option<&[usize]>,
lock_results: &LockedAccountsResults, lock_results: &LockedAccountsResults,
max_age: usize, max_age: usize,
) -> ( ) -> (
@ -965,31 +986,32 @@ impl Bank {
let mut error_counters = ErrorCounters::default(); let mut error_counters = ErrorCounters::default();
let mut load_time = Measure::start("accounts_load"); let mut load_time = Measure::start("accounts_load");
let retryable_txs: Vec<_> = lock_results let retryable_txs: Vec<_> =
.locked_accounts_results() OrderedIterator::new(lock_results.locked_accounts_results(), txs_iteration_order)
.iter() .enumerate()
.enumerate() .filter_map(|(index, res)| match res {
.filter_map(|(index, res)| match res { Err(TransactionError::AccountInUse) => Some(index),
Err(TransactionError::AccountInUse) => Some(index), Ok(_) => None,
Ok(_) => None, Err(_) => None,
Err(_) => None, })
}) .collect();
.collect();
let sig_results = self.check_transactions( let sig_results = self.check_transactions(
txs, txs,
txs_iteration_order,
lock_results.locked_accounts_results(), lock_results.locked_accounts_results(),
max_age, max_age,
&mut error_counters, &mut error_counters,
); );
let mut loaded_accounts = self.load_accounts(txs, sig_results, &mut error_counters); let mut loaded_accounts =
self.load_accounts(txs, txs_iteration_order, sig_results, &mut error_counters);
load_time.stop(); load_time.stop();
let mut execution_time = Measure::start("execution_time"); let mut execution_time = Measure::start("execution_time");
let mut signature_count = 0; let mut signature_count = 0;
let executed: Vec<Result<()>> = loaded_accounts let executed: Vec<Result<()>> = loaded_accounts
.iter_mut() .iter_mut()
.zip(txs.iter()) .zip(OrderedIterator::new(txs, txs_iteration_order))
.map(|(accs, tx)| match accs { .map(|(accs, tx)| match accs {
Err(e) => Err(e.clone()), Err(e) => Err(e.clone()),
Ok((ref mut accounts, ref mut loaders, ref mut credits, ref mut _rents)) => { Ok((ref mut accounts, ref mut loaders, ref mut credits, ref mut _rents)) => {
@ -1042,12 +1064,12 @@ impl Bank {
fn filter_program_errors_and_collect_fee( fn filter_program_errors_and_collect_fee(
&self, &self,
txs: &[Transaction], txs: &[Transaction],
txs_iteration_order: Option<&[usize]>,
executed: &[Result<()>], executed: &[Result<()>],
) -> Vec<Result<()>> { ) -> Vec<Result<()>> {
let hash_queue = self.blockhash_queue.read().unwrap(); let hash_queue = self.blockhash_queue.read().unwrap();
let mut fees = 0; let mut fees = 0;
let results = txs let results = OrderedIterator::new(txs, txs_iteration_order)
.iter()
.zip(executed.iter()) .zip(executed.iter())
.map(|(tx, res)| { .map(|(tx, res)| {
let fee_calculator = hash_queue let fee_calculator = hash_queue
@ -1082,6 +1104,7 @@ impl Bank {
pub fn commit_transactions( pub fn commit_transactions(
&self, &self,
txs: &[Transaction], txs: &[Transaction],
txs_iteration_order: Option<&[usize]>,
loaded_accounts: &mut [Result<( loaded_accounts: &mut [Result<(
TransactionAccounts, TransactionAccounts,
TransactionLoaders, TransactionLoaders,
@ -1109,17 +1132,21 @@ impl Bank {
// TODO: put this assert back in // TODO: put this assert back in
// assert!(!self.is_frozen()); // assert!(!self.is_frozen());
let mut write_time = Measure::start("write_time"); let mut write_time = Measure::start("write_time");
self.rc self.rc.accounts.store_accounts(
.accounts self.slot(),
.store_accounts(self.slot(), txs, executed, loaded_accounts); txs,
txs_iteration_order,
executed,
loaded_accounts,
);
self.update_cached_accounts(txs, executed, loaded_accounts); self.update_cached_accounts(txs, txs_iteration_order, executed, loaded_accounts);
// once committed there is no way to unroll // once committed there is no way to unroll
write_time.stop(); write_time.stop();
debug!("store: {}us txs_len={}", write_time.as_us(), txs.len(),); debug!("store: {}us txs_len={}", write_time.as_us(), txs.len(),);
self.update_transaction_statuses(txs, &executed); self.update_transaction_statuses(txs, txs_iteration_order, &executed);
self.filter_program_errors_and_collect_fee(txs, executed) self.filter_program_errors_and_collect_fee(txs, txs_iteration_order, executed)
} }
/// Process a batch of transactions. /// Process a batch of transactions.
@ -1127,14 +1154,16 @@ impl Bank {
pub fn load_execute_and_commit_transactions( pub fn load_execute_and_commit_transactions(
&self, &self,
txs: &[Transaction], txs: &[Transaction],
txs_iteration_order: Option<&[usize]>,
lock_results: &LockedAccountsResults, lock_results: &LockedAccountsResults,
max_age: usize, max_age: usize,
) -> Vec<Result<()>> { ) -> Vec<Result<()>> {
let (mut loaded_accounts, executed, _, tx_count, signature_count) = let (mut loaded_accounts, executed, _, tx_count, signature_count) =
self.load_and_execute_transactions(txs, lock_results, max_age); self.load_and_execute_transactions(txs, txs_iteration_order, lock_results, max_age);
self.commit_transactions( self.commit_transactions(
txs, txs,
txs_iteration_order,
&mut loaded_accounts, &mut loaded_accounts,
&executed, &executed,
tx_count, tx_count,
@ -1144,8 +1173,8 @@ impl Bank {
#[must_use] #[must_use]
pub fn process_transactions(&self, txs: &[Transaction]) -> Vec<Result<()>> { pub fn process_transactions(&self, txs: &[Transaction]) -> Vec<Result<()>> {
let lock_results = self.lock_accounts(txs); let lock_results = self.lock_accounts(txs, None);
self.load_execute_and_commit_transactions(txs, &lock_results, MAX_RECENT_BLOCKHASHES) self.load_execute_and_commit_transactions(txs, None, &lock_results, MAX_RECENT_BLOCKHASHES)
} }
/// Create, sign, and process a Transaction from `keypair` to `to` of /// Create, sign, and process a Transaction from `keypair` to `to` of
@ -1359,6 +1388,7 @@ impl Bank {
fn update_cached_accounts( fn update_cached_accounts(
&self, &self,
txs: &[Transaction], txs: &[Transaction],
txs_iteration_order: Option<&[usize]>,
res: &[Result<()>], res: &[Result<()>],
loaded: &[Result<( loaded: &[Result<(
TransactionAccounts, TransactionAccounts,
@ -1367,12 +1397,16 @@ impl Bank {
TransactionRents, TransactionRents,
)>], )>],
) { ) {
for (i, raccs) in loaded.iter().enumerate() { for (i, (raccs, tx)) in loaded
.iter()
.zip(OrderedIterator::new(txs, txs_iteration_order))
.enumerate()
{
if res[i].is_err() || raccs.is_err() { if res[i].is_err() || raccs.is_err() {
continue; continue;
} }
let message = &txs[i].message(); let message = &tx.message();
let acc = raccs.as_ref().unwrap(); let acc = raccs.as_ref().unwrap();
for (pubkey, account) in for (pubkey, account) in
@ -1986,7 +2020,7 @@ mod tests {
]; ];
let initial_balance = bank.get_balance(&leader); let initial_balance = bank.get_balance(&leader);
let results = bank.filter_program_errors_and_collect_fee(&vec![tx1, tx2], &results); let results = bank.filter_program_errors_and_collect_fee(&vec![tx1, tx2], None, &results);
bank.freeze(); bank.freeze();
assert_eq!( assert_eq!(
bank.get_balance(&leader), bank.get_balance(&leader),
@ -2091,9 +2125,10 @@ mod tests {
); );
let pay_alice = vec![tx1]; let pay_alice = vec![tx1];
let lock_result = bank.lock_accounts(&pay_alice); let lock_result = bank.lock_accounts(&pay_alice, None);
let results_alice = bank.load_execute_and_commit_transactions( let results_alice = bank.load_execute_and_commit_transactions(
&pay_alice, &pay_alice,
None,
&lock_result, &lock_result,
MAX_RECENT_BLOCKHASHES, MAX_RECENT_BLOCKHASHES,
); );
@ -2140,7 +2175,7 @@ mod tests {
let tx = Transaction::new(&[&key0], message, genesis_block.hash()); let tx = Transaction::new(&[&key0], message, genesis_block.hash());
let txs = vec![tx]; let txs = vec![tx];
let lock_result0 = bank.lock_accounts(&txs); let lock_result0 = bank.lock_accounts(&txs, None);
assert!(lock_result0.locked_accounts_results()[0].is_ok()); assert!(lock_result0.locked_accounts_results()[0].is_ok());
// Try locking accounts, locking a previously credit-only account as credit-debit // Try locking accounts, locking a previously credit-only account as credit-debit
@ -2158,7 +2193,7 @@ mod tests {
let tx = Transaction::new(&[&key1], message, genesis_block.hash()); let tx = Transaction::new(&[&key1], message, genesis_block.hash());
let txs = vec![tx]; let txs = vec![tx];
let lock_result1 = bank.lock_accounts(&txs); let lock_result1 = bank.lock_accounts(&txs, None);
assert!(lock_result1.locked_accounts_results()[0].is_err()); assert!(lock_result1.locked_accounts_results()[0].is_err());
// Try locking a previously credit-only account a 2nd time; should succeed // Try locking a previously credit-only account a 2nd time; should succeed
@ -2175,7 +2210,7 @@ mod tests {
let tx = Transaction::new(&[&key2], message, genesis_block.hash()); let tx = Transaction::new(&[&key2], message, genesis_block.hash());
let txs = vec![tx]; let txs = vec![tx];
let lock_result2 = bank.lock_accounts(&txs); let lock_result2 = bank.lock_accounts(&txs, None);
assert!(lock_result2.locked_accounts_results()[0].is_ok()); assert!(lock_result2.locked_accounts_results()[0].is_ok());
} }

View File

@ -18,6 +18,7 @@ pub mod stakes;
pub mod status_cache; pub mod status_cache;
pub mod storage_utils; pub mod storage_utils;
mod system_instruction_processor; mod system_instruction_processor;
pub mod transaction_utils;
#[macro_use] #[macro_use]
extern crate solana_metrics; extern crate solana_metrics;

View File

@ -54,7 +54,7 @@ mod tests {
let (bank, txs) = setup(); let (bank, txs) = setup();
// Test getting locked accounts // Test getting locked accounts
let lock_results = bank.lock_accounts(&txs); let lock_results = bank.lock_accounts(&txs, None);
// Grab locks // Grab locks
assert!(lock_results assert!(lock_results
@ -63,7 +63,7 @@ mod tests {
.all(|x| x.is_ok())); .all(|x| x.is_ok()));
// Trying to grab locks again should fail // Trying to grab locks again should fail
let lock_results2 = bank.lock_accounts(&txs); let lock_results2 = bank.lock_accounts(&txs, None);
assert!(lock_results2 assert!(lock_results2
.locked_accounts_results() .locked_accounts_results()
.iter() .iter()
@ -73,7 +73,7 @@ mod tests {
drop(lock_results); drop(lock_results);
// Now grabbing locks should work again // Now grabbing locks should work again
let lock_results2 = bank.lock_accounts(&txs); let lock_results2 = bank.lock_accounts(&txs, None);
assert!(lock_results2 assert!(lock_results2
.locked_accounts_results() .locked_accounts_results()
.iter() .iter()

View File

@ -0,0 +1,73 @@
use std::ops::Index;
/// OrderedIterator allows iterating with specific order specified
pub struct OrderedIterator<'a, T: 'a> {
element_order: Option<&'a [usize]>,
current: usize,
vec: &'a [T],
}
impl<'a, T> OrderedIterator<'a, T> {
pub fn new(vec: &'a [T], element_order: Option<&'a [usize]>) -> OrderedIterator<'a, T> {
if let Some(custom_order) = element_order {
assert!(custom_order.len() == vec.len());
}
OrderedIterator {
element_order,
current: 0,
vec,
}
}
}
impl<'a, T> Iterator for OrderedIterator<'a, T> {
type Item = &'a T;
fn next(&mut self) -> Option<Self::Item> {
if self.current >= self.vec.len() {
None
} else {
let index: usize;
if let Some(custom_order) = self.element_order {
index = custom_order[self.current];
} else {
index = self.current;
}
self.current += 1;
Some(self.vec.index(index))
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_ordered_iterator_custom_order() {
let vec: Vec<usize> = vec![1, 2, 3, 4];
let custom_order: Vec<usize> = vec![3, 1, 0, 2];
let ordered_iterator = OrderedIterator::new(&vec, Some(&custom_order));
let expected_response: Vec<usize> = vec![4, 2, 1, 3];
let resp: Vec<(&usize, &usize)> = ordered_iterator
.zip(expected_response.iter())
.filter(|(actual_elem, expected_elem)| *actual_elem == *expected_elem)
.collect();
assert_eq!(resp.len(), custom_order.len());
}
#[test]
fn test_ordered_iterator_original_order() {
let vec: Vec<usize> = vec![1, 2, 3, 4];
let ordered_iterator = OrderedIterator::new(&vec, None);
let resp: Vec<(&usize, &usize)> = ordered_iterator
.zip(vec.iter())
.filter(|(actual_elem, expected_elem)| *actual_elem == *expected_elem)
.collect();
assert_eq!(resp.len(), vec.len());
}
}