Skip to content

Commit

Permalink
add freeze_lock() and fix par_process_entries() failure to detect sel…
Browse files Browse the repository at this point in the history
…f conflict (solana-labs#4415)

* add freeze_lock and fix par_process_entries failure to detect self conflict

* fixup

* fixup
  • Loading branch information
rob-solana authored May 24, 2019
1 parent 943cd0a commit 35e8f96
Show file tree
Hide file tree
Showing 3 changed files with 131 additions and 22 deletions.
3 changes: 3 additions & 0 deletions core/src/banking_stage.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand All @@ -442,6 +444,7 @@ impl BankingStage {
};

drop(record_locks);
drop(freeze_lock);

debug!(
"bank: {} load_execute: {}us record: {}us commit: {}us txs_len: {}",
Expand Down
144 changes: 123 additions & 21 deletions core/src/blocktree_processor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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(())
}
Expand Down Expand Up @@ -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;
}
// 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() {
// else loop on processing the entry
loop {
// try to lock the accounts
let lock_results = bank.lock_accounts(&entry.transactions);

let first_lock_err = first_err(lock_results.locked_accounts_results());

// 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",
(
Expand All @@ -93,19 +104,14 @@ pub fn process_entries(bank: &Bank, entries: &[Entry]) -> Result<()> {
String
)
);

// bail
first_lock_err?;
} else {
// 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)?;
// 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));
}
}
par_execute_entries(bank, &mt_group)?;
Expand Down Expand Up @@ -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 {
Expand Down
6 changes: 5 additions & 1 deletion runtime/src/bank.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<()>>;
Expand Down Expand Up @@ -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()
}
Expand Down

0 comments on commit 35e8f96

Please sign in to comment.