add freeze_lock() and fix par_process_entries() failure to detect self conflict (#4415)

* add freeze_lock and fix par_process_entries failure to detect self conflict

* fixup

* fixup
This commit is contained in:
Rob Walker 2019-05-23 17:35:15 -07:00 committed by GitHub
parent 943cd0a24a
commit 35e8f966e3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 131 additions and 22 deletions

View File

@ -427,6 +427,8 @@ impl BankingStage {
bank.load_and_execute_transactions(txs, lock_results, MAX_RECENT_BLOCKHASHES / 2);
let load_execute_time = now.elapsed();
let freeze_lock = bank.freeze_lock();
let mut recordable_txs = vec![];
let (record_time, record_locks) = {
let now = Instant::now();
@ -442,6 +444,7 @@ impl BankingStage {
};
drop(record_locks);
drop(freeze_lock);
debug!(
"bank: {} load_execute: {}us record: {}us commit: {}us txs_len: {}",

View File

@ -17,7 +17,9 @@ use std::time::{Duration, Instant};
fn first_err(results: &[Result<()>]) -> Result<()> {
for r in results {
r.clone()?;
if r.is_err() {
return r.clone();
}
}
Ok(())
}
@ -69,19 +71,28 @@ pub fn process_entries(bank: &Bank, entries: &[Entry]) -> Result<()> {
if entry.is_tick() {
// if its a tick, execute the group and register the tick
par_execute_entries(bank, &mt_group)?;
bank.register_tick(&entry.hash);
mt_group = vec![];
bank.register_tick(&entry.hash);
continue;
}
// else loop on processing the entry
loop {
// try to lock the accounts
let lock_results = bank.lock_accounts(&entry.transactions);
// if any of the locks error out
// execute the current group
let first_lock_err = first_err(lock_results.locked_accounts_results());
if first_lock_err.is_err() {
// if locking worked
if first_lock_err.is_ok() {
// push the entry to the mt_group
mt_group.push((entry, lock_results));
// done with this entry
break;
}
// else we failed to lock, 2 possible reasons
if mt_group.is_empty() {
// An entry has account lock conflicts with itself, which should not happen
// if generated by properly functioning leaders
// An entry has account lock conflicts with *itself*, which should not happen
// if generated by a properly functioning leader
datapoint!(
"validator_process_entry_error",
(
@ -93,19 +104,14 @@ pub fn process_entries(bank: &Bank, entries: &[Entry]) -> Result<()> {
String
)
);
// bail
first_lock_err?;
}
par_execute_entries(bank, &mt_group)?;
// Drop all the locks on accounts by clearing the LockedAccountsFinalizer's in the
// mt_group
mt_group = vec![];
drop(lock_results);
let lock_results = bank.lock_accounts(&entry.transactions);
mt_group.push((entry, lock_results));
} else {
// push the entry to the mt_group
mt_group.push((entry, lock_results));
// else we have an entry that conflicts with a prior entry
// execute the current queue and try to process this entry again
par_execute_entries(bank, &mt_group)?;
mt_group = vec![];
}
}
}
par_execute_entries(bank, &mt_group)?;
@ -938,6 +944,102 @@ pub mod tests {
}
}
#[test]
fn test_process_entries_2nd_entry_collision_with_self_and_error() {
solana_logger::setup();
let GenesisBlockInfo {
genesis_block,
mint_keypair,
..
} = create_genesis_block(1000);
let bank = Bank::new(&genesis_block);
let keypair1 = Keypair::new();
let keypair2 = Keypair::new();
let keypair3 = Keypair::new();
// fund: put some money in each of 1 and 2
assert_matches!(bank.transfer(5, &mint_keypair, &keypair1.pubkey()), Ok(_));
assert_matches!(bank.transfer(4, &mint_keypair, &keypair2.pubkey()), Ok(_));
// 3 entries: first has a transfer, 2nd has a conflict with 1st, 3rd has a conflict with itself
let entry_1_to_mint = next_entry(
&bank.last_blockhash(),
1,
vec![system_transaction::transfer(
&keypair1,
&mint_keypair.pubkey(),
1,
bank.last_blockhash(),
)],
);
// should now be:
// keypair1=4
// keypair2=4
// keypair3=0
let entry_2_to_3_and_1_to_mint = next_entry(
&entry_1_to_mint.hash,
1,
vec![
system_transaction::create_user_account(
&keypair2,
&keypair3.pubkey(),
2,
bank.last_blockhash(),
), // should be fine
system_transaction::transfer(
&keypair1,
&mint_keypair.pubkey(),
2,
bank.last_blockhash(),
), // will collide with predecessor
],
);
// should now be:
// keypair1=2
// keypair2=2
// keypair3=2
let entry_conflict_itself = next_entry(
&entry_2_to_3_and_1_to_mint.hash,
1,
vec![
system_transaction::transfer(
&keypair1,
&keypair3.pubkey(),
1,
bank.last_blockhash(),
),
system_transaction::transfer(
&keypair1,
&keypair2.pubkey(),
1,
bank.last_blockhash(),
), // should be fine
],
);
// would now be:
// keypair1=0
// keypair2=3
// keypair3=3
assert!(process_entries(
&bank,
&[
entry_1_to_mint.clone(),
entry_2_to_3_and_1_to_mint.clone(),
entry_conflict_itself.clone()
]
)
.is_err());
// last entry should have been aborted before par_execute_entries
assert_eq!(bank.get_balance(&keypair1.pubkey()), 2);
assert_eq!(bank.get_balance(&keypair2.pubkey()), 2);
assert_eq!(bank.get_balance(&keypair3.pubkey()), 2);
}
#[test]
fn test_process_entries_2_entries_par() {
let GenesisBlockInfo {

View File

@ -31,7 +31,7 @@ use solana_sdk::transaction::{Result, Transaction, TransactionError};
use std::borrow::Borrow;
use std::cmp;
use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering};
use std::sync::{Arc, RwLock};
use std::sync::{Arc, RwLock, RwLockReadGuard};
use std::time::Instant;
type BankStatusCache = StatusCache<Result<()>>;
@ -191,6 +191,10 @@ impl Bank {
self.slot
}
pub fn freeze_lock(&self) -> RwLockReadGuard<Hash> {
self.hash.read().unwrap()
}
pub fn hash(&self) -> Hash {
*self.hash.read().unwrap()
}