Fix parent record locks usage in child banks (#4159)
* Introduce record locks on txs that will be recorded * Add tests for LockedAccountsResults * Fix broken bench * Exit process_entries on detecting conflicting locks within same entry
This commit is contained in:
@ -326,40 +326,43 @@ impl BankingStage {
|
|||||||
.collect()
|
.collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn record_transactions(
|
fn record_transactions<'a, 'b>(
|
||||||
bank_slot: u64,
|
bank: &'a Bank,
|
||||||
txs: &[Transaction],
|
txs: &'b [Transaction],
|
||||||
results: &[transaction::Result<()>],
|
results: &[transaction::Result<()>],
|
||||||
poh: &Arc<Mutex<PohRecorder>>,
|
poh: &Arc<Mutex<PohRecorder>>,
|
||||||
) -> Result<()> {
|
recordable_txs: &'b mut Vec<&'b Transaction>,
|
||||||
|
) -> Result<LockedAccountsResults<'a, 'b, &'b Transaction>> {
|
||||||
let processed_transactions: Vec<_> = results
|
let processed_transactions: Vec<_> = results
|
||||||
.iter()
|
.iter()
|
||||||
.zip(txs.iter())
|
.zip(txs.iter())
|
||||||
.filter_map(|(r, x)| {
|
.filter_map(|(r, x)| {
|
||||||
if Bank::can_commit(r) {
|
if Bank::can_commit(r) {
|
||||||
|
recordable_txs.push(x);
|
||||||
Some(x.clone())
|
Some(x.clone())
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
|
let record_locks = bank.lock_record_accounts(recordable_txs);
|
||||||
debug!("processed: {} ", processed_transactions.len());
|
debug!("processed: {} ", processed_transactions.len());
|
||||||
// unlock all the accounts with errors which are filtered by the above `filter_map`
|
// unlock all the accounts with errors which are filtered by the above `filter_map`
|
||||||
if !processed_transactions.is_empty() {
|
if !processed_transactions.is_empty() {
|
||||||
let hash = hash_transactions(&processed_transactions);
|
let hash = hash_transactions(&processed_transactions);
|
||||||
// record and unlock will unlock all the successfull transactions
|
// record and unlock will unlock all the successful transactions
|
||||||
poh.lock()
|
poh.lock()
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.record(bank_slot, hash, processed_transactions)?;
|
.record(bank.slot(), hash, processed_transactions)?;
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(record_locks)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn process_and_record_transactions_locked(
|
fn process_and_record_transactions_locked(
|
||||||
bank: &Bank,
|
bank: &Bank,
|
||||||
txs: &[Transaction],
|
txs: &[Transaction],
|
||||||
poh: &Arc<Mutex<PohRecorder>>,
|
poh: &Arc<Mutex<PohRecorder>>,
|
||||||
lock_results: &LockedAccountsResults,
|
lock_results: &LockedAccountsResults<Transaction>,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
let now = Instant::now();
|
let now = Instant::now();
|
||||||
// Use a shorter maximum age when adding transactions into the pipeline. This will reduce
|
// Use a shorter maximum age when adding transactions into the pipeline. This will reduce
|
||||||
@ -370,10 +373,12 @@ impl BankingStage {
|
|||||||
bank.load_and_execute_transactions(txs, lock_results, MAX_RECENT_BLOCKHASHES / 2);
|
bank.load_and_execute_transactions(txs, lock_results, MAX_RECENT_BLOCKHASHES / 2);
|
||||||
let load_execute_time = now.elapsed();
|
let load_execute_time = now.elapsed();
|
||||||
|
|
||||||
let record_time = {
|
let mut recordable_txs = vec![];
|
||||||
|
let (record_time, record_locks) = {
|
||||||
let now = Instant::now();
|
let now = Instant::now();
|
||||||
Self::record_transactions(bank.slot(), txs, &results, poh)?;
|
let record_locks =
|
||||||
now.elapsed()
|
Self::record_transactions(bank, txs, &results, poh, &mut recordable_txs)?;
|
||||||
|
(now.elapsed(), record_locks)
|
||||||
};
|
};
|
||||||
|
|
||||||
let commit_time = {
|
let commit_time = {
|
||||||
@ -382,6 +387,8 @@ impl BankingStage {
|
|||||||
now.elapsed()
|
now.elapsed()
|
||||||
};
|
};
|
||||||
|
|
||||||
|
drop(record_locks);
|
||||||
|
|
||||||
debug!(
|
debug!(
|
||||||
"bank: {} load_execute: {}us record: {}us commit: {}us txs_len: {}",
|
"bank: {} load_execute: {}us record: {}us commit: {}us txs_len: {}",
|
||||||
bank.slot(),
|
bank.slot(),
|
||||||
@ -981,14 +988,22 @@ mod tests {
|
|||||||
|
|
||||||
poh_recorder.lock().unwrap().set_working_bank(working_bank);
|
poh_recorder.lock().unwrap().set_working_bank(working_bank);
|
||||||
let pubkey = Pubkey::new_rand();
|
let pubkey = Pubkey::new_rand();
|
||||||
|
let keypair2 = Keypair::new();
|
||||||
|
let pubkey2 = Pubkey::new_rand();
|
||||||
|
|
||||||
let transactions = vec![
|
let transactions = vec![
|
||||||
system_transaction::transfer(&mint_keypair, &pubkey, 1, genesis_block.hash(), 0),
|
system_transaction::transfer(&mint_keypair, &pubkey, 1, genesis_block.hash(), 0),
|
||||||
system_transaction::transfer(&mint_keypair, &pubkey, 1, genesis_block.hash(), 0),
|
system_transaction::transfer(&keypair2, &pubkey2, 1, genesis_block.hash(), 0),
|
||||||
];
|
];
|
||||||
|
|
||||||
let mut results = vec![Ok(()), Ok(())];
|
let mut results = vec![Ok(()), Ok(())];
|
||||||
BankingStage::record_transactions(bank.slot(), &transactions, &results, &poh_recorder)
|
BankingStage::record_transactions(
|
||||||
|
&bank,
|
||||||
|
&transactions,
|
||||||
|
&results,
|
||||||
|
&poh_recorder,
|
||||||
|
&mut vec![],
|
||||||
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let (_, entries) = entry_receiver.recv().unwrap();
|
let (_, entries) = entry_receiver.recv().unwrap();
|
||||||
assert_eq!(entries[0].0.transactions.len(), transactions.len());
|
assert_eq!(entries[0].0.transactions.len(), transactions.len());
|
||||||
@ -998,14 +1013,26 @@ mod tests {
|
|||||||
1,
|
1,
|
||||||
InstructionError::new_result_with_negative_lamports(),
|
InstructionError::new_result_with_negative_lamports(),
|
||||||
));
|
));
|
||||||
BankingStage::record_transactions(bank.slot(), &transactions, &results, &poh_recorder)
|
BankingStage::record_transactions(
|
||||||
|
&bank,
|
||||||
|
&transactions,
|
||||||
|
&results,
|
||||||
|
&poh_recorder,
|
||||||
|
&mut vec![],
|
||||||
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let (_, entries) = entry_receiver.recv().unwrap();
|
let (_, entries) = entry_receiver.recv().unwrap();
|
||||||
assert_eq!(entries[0].0.transactions.len(), transactions.len());
|
assert_eq!(entries[0].0.transactions.len(), transactions.len());
|
||||||
|
|
||||||
// Other TransactionErrors should not be recorded
|
// Other TransactionErrors should not be recorded
|
||||||
results[0] = Err(TransactionError::AccountNotFound);
|
results[0] = Err(TransactionError::AccountNotFound);
|
||||||
BankingStage::record_transactions(bank.slot(), &transactions, &results, &poh_recorder)
|
BankingStage::record_transactions(
|
||||||
|
&bank,
|
||||||
|
&transactions,
|
||||||
|
&results,
|
||||||
|
&poh_recorder,
|
||||||
|
&mut vec![],
|
||||||
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let (_, entries) = entry_receiver.recv().unwrap();
|
let (_, entries) = entry_receiver.recv().unwrap();
|
||||||
assert_eq!(entries[0].0.transactions.len(), transactions.len() - 1);
|
assert_eq!(entries[0].0.transactions.len(), transactions.len() - 1);
|
||||||
|
@ -10,6 +10,7 @@ use solana_sdk::genesis_block::GenesisBlock;
|
|||||||
use solana_sdk::timing::duration_as_ms;
|
use solana_sdk::timing::duration_as_ms;
|
||||||
use solana_sdk::timing::MAX_RECENT_BLOCKHASHES;
|
use solana_sdk::timing::MAX_RECENT_BLOCKHASHES;
|
||||||
use solana_sdk::transaction::Result;
|
use solana_sdk::transaction::Result;
|
||||||
|
use solana_sdk::transaction::Transaction;
|
||||||
use std::result;
|
use std::result;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use std::time::{Duration, Instant};
|
use std::time::{Duration, Instant};
|
||||||
@ -21,7 +22,10 @@ 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<Transaction>)],
|
||||||
|
) -> Result<()> {
|
||||||
inc_new_counter_info!("bank-par_execute_entries-count", entries.len());
|
inc_new_counter_info!("bank-par_execute_entries-count", entries.len());
|
||||||
let results: Vec<Result<()>> = entries
|
let results: Vec<Result<()>> = entries
|
||||||
.into_par_iter()
|
.into_par_iter()
|
||||||
@ -77,7 +81,25 @@ pub fn process_entries(bank: &Bank, entries: &[Entry]) -> Result<()> {
|
|||||||
let lock_results = bank.lock_accounts(&entry.transactions);
|
let lock_results = bank.lock_accounts(&entry.transactions);
|
||||||
// if any of the locks error out
|
// if any of the locks error out
|
||||||
// execute the current group
|
// execute the current group
|
||||||
if first_err(lock_results.locked_accounts_results()).is_err() {
|
let first_lock_err = first_err(lock_results.locked_accounts_results());
|
||||||
|
if first_lock_err.is_err() {
|
||||||
|
if mt_group.is_empty() {
|
||||||
|
// An entry has account lock conflicts with itself, which should not happen
|
||||||
|
// if generated by properly functioning leaders
|
||||||
|
solana_metrics::submit(
|
||||||
|
solana_metrics::influxdb::Point::new("validator_process_entry_error")
|
||||||
|
.add_field(
|
||||||
|
"error",
|
||||||
|
solana_metrics::influxdb::Value::String(format!(
|
||||||
|
"Lock accounts error, entry conflicts with itself, txs: {:?}",
|
||||||
|
entry.transactions
|
||||||
|
)),
|
||||||
|
)
|
||||||
|
.to_owned(),
|
||||||
|
);
|
||||||
|
|
||||||
|
first_lock_err?;
|
||||||
|
}
|
||||||
par_execute_entries(bank, &mt_group)?;
|
par_execute_entries(bank, &mt_group)?;
|
||||||
// Drop all the locks on accounts by clearing the LockedAccountsFinalizer's in the
|
// Drop all the locks on accounts by clearing the LockedAccountsFinalizer's in the
|
||||||
// mt_group
|
// mt_group
|
||||||
|
@ -99,6 +99,9 @@ fn append_vec_concurrent_read_append(bencher: &mut Bencher) {
|
|||||||
let indexes1 = indexes.clone();
|
let indexes1 = indexes.clone();
|
||||||
spawn(move || loop {
|
spawn(move || loop {
|
||||||
let len = indexes1.lock().unwrap().len();
|
let len = indexes1.lock().unwrap().len();
|
||||||
|
if len == 0 {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
let random_index: usize = thread_rng().gen_range(0, len + 1);
|
let random_index: usize = thread_rng().gen_range(0, len + 1);
|
||||||
let (sample, pos) = indexes1
|
let (sample, pos) = indexes1
|
||||||
.lock()
|
.lock()
|
||||||
|
@ -17,6 +17,7 @@ use solana_sdk::pubkey::Pubkey;
|
|||||||
use solana_sdk::signature::{Keypair, KeypairUtil};
|
use solana_sdk::signature::{Keypair, KeypairUtil};
|
||||||
use solana_sdk::transaction::Result;
|
use solana_sdk::transaction::Result;
|
||||||
use solana_sdk::transaction::{Transaction, TransactionError};
|
use solana_sdk::transaction::{Transaction, TransactionError};
|
||||||
|
use std::borrow::Borrow;
|
||||||
use std::env;
|
use std::env;
|
||||||
use std::fs::remove_dir_all;
|
use std::fs::remove_dir_all;
|
||||||
use std::iter::once;
|
use std::iter::once;
|
||||||
@ -24,16 +25,28 @@ use std::ops::Neg;
|
|||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
use std::sync::atomic::{AtomicUsize, Ordering};
|
use std::sync::atomic::{AtomicUsize, Ordering};
|
||||||
use std::sync::{Arc, Mutex};
|
use std::sync::{Arc, Mutex};
|
||||||
|
use std::thread::sleep;
|
||||||
|
use std::time::Duration;
|
||||||
|
|
||||||
const ACCOUNTSDB_DIR: &str = "accountsdb";
|
const ACCOUNTSDB_DIR: &str = "accountsdb";
|
||||||
const NUM_ACCOUNT_DIRS: usize = 4;
|
const NUM_ACCOUNT_DIRS: usize = 4;
|
||||||
|
const WAIT_FOR_PARENT_MS: u64 = 5;
|
||||||
|
|
||||||
type AccountLocks = (
|
#[derive(Clone, PartialEq, Eq, Debug)]
|
||||||
// Locks for the current bank
|
pub enum AccountLockType {
|
||||||
Arc<Mutex<HashSet<Pubkey>>>,
|
AccountLock,
|
||||||
// Any unreleased locks from all parent/grandparent banks. We use Arc<Mutex> to
|
RecordLock,
|
||||||
|
}
|
||||||
|
|
||||||
|
type AccountLocks = Mutex<HashSet<Pubkey>>;
|
||||||
|
|
||||||
|
// Locks for accounts that are currently being recorded + committed
|
||||||
|
type RecordLocks = (
|
||||||
|
// Record Locks for the current bank
|
||||||
|
Arc<AccountLocks>,
|
||||||
|
// Any unreleased record locks from all parent/grandparent banks. We use Arc<Mutex> to
|
||||||
// avoid copies when calling new_from_parent().
|
// avoid copies when calling new_from_parent().
|
||||||
Vec<Arc<Mutex<HashSet<Pubkey>>>>,
|
Vec<Arc<AccountLocks>>,
|
||||||
);
|
);
|
||||||
|
|
||||||
/// This structure handles synchronization for db
|
/// This structure handles synchronization for db
|
||||||
@ -43,7 +56,10 @@ pub struct Accounts {
|
|||||||
pub accounts_db: Arc<AccountsDB>,
|
pub accounts_db: Arc<AccountsDB>,
|
||||||
|
|
||||||
/// set of accounts which are currently in the pipeline
|
/// set of accounts which are currently in the pipeline
|
||||||
account_locks: Mutex<AccountLocks>,
|
account_locks: AccountLocks,
|
||||||
|
|
||||||
|
/// set of accounts which are about to record + commit
|
||||||
|
record_locks: Mutex<RecordLocks>,
|
||||||
|
|
||||||
/// List of persistent stores
|
/// List of persistent stores
|
||||||
paths: String,
|
paths: String,
|
||||||
@ -103,7 +119,8 @@ impl Accounts {
|
|||||||
let accounts_db = Arc::new(AccountsDB::new(&paths));
|
let accounts_db = Arc::new(AccountsDB::new(&paths));
|
||||||
Accounts {
|
Accounts {
|
||||||
accounts_db,
|
accounts_db,
|
||||||
account_locks: Mutex::new((Arc::new(Mutex::new(HashSet::new())), vec![])),
|
account_locks: Mutex::new(HashSet::new()),
|
||||||
|
record_locks: Mutex::new((Arc::new(Mutex::new(HashSet::new())), vec![])),
|
||||||
paths,
|
paths,
|
||||||
own_paths,
|
own_paths,
|
||||||
}
|
}
|
||||||
@ -111,29 +128,30 @@ impl Accounts {
|
|||||||
|
|
||||||
pub fn new_from_parent(parent: &Accounts) -> Self {
|
pub fn new_from_parent(parent: &Accounts) -> Self {
|
||||||
let accounts_db = parent.accounts_db.clone();
|
let accounts_db = parent.accounts_db.clone();
|
||||||
let parent_locks: Vec<_> = {
|
let parent_record_locks: Vec<_> = {
|
||||||
let (ref parent_locks, ref mut grandparent_locks) =
|
let (ref record_locks, ref mut grandparent_record_locks) =
|
||||||
*parent.account_locks.lock().unwrap();
|
*parent.record_locks.lock().unwrap();
|
||||||
|
|
||||||
// Copy all unreleased parent locks and the much more unlikely (but still possible)
|
// Note that when creating a child bank, we only care about the locks that are held for
|
||||||
// grandparent account locks into the new child. Note that by the time this function
|
// accounts that are in txs that are currently recording + committing, because other
|
||||||
// is called, no further transactions will be recorded on the parent bank, so even if
|
// incoming txs on this bank that are not yet recording will not make it to bank commit.
|
||||||
// banking threads grab account locks on this parent bank, none of those results will
|
//
|
||||||
// be committed.
|
|
||||||
// Thus:
|
// Thus:
|
||||||
// 1) The child doesn't need to care about potential "future" account locks on its parent
|
// 1) The child doesn't need to care about potential "future" account locks on its parent
|
||||||
// bank that the parent does not currently hold.
|
// bank that the parent does not currently hold.
|
||||||
// 2) The parent doesn't need to retain any of the locks other than the ones it owns so
|
// 2) The child only needs the currently held "record locks" from the parent.
|
||||||
|
// 3) The parent doesn't need to retain any of the locks other than the ones it owns so
|
||||||
// that unlock() can be called later (the grandparent locks can be given to the child).
|
// that unlock() can be called later (the grandparent locks can be given to the child).
|
||||||
once(parent_locks.clone())
|
once(record_locks.clone())
|
||||||
.chain(grandparent_locks.drain(..))
|
.chain(grandparent_record_locks.drain(..))
|
||||||
.filter(|a| !a.lock().unwrap().is_empty())
|
.filter(|a| !a.lock().unwrap().is_empty())
|
||||||
.collect()
|
.collect()
|
||||||
};
|
};
|
||||||
|
|
||||||
Accounts {
|
Accounts {
|
||||||
accounts_db,
|
accounts_db,
|
||||||
account_locks: Mutex::new((Arc::new(Mutex::new(HashSet::new())), parent_locks)),
|
account_locks: Mutex::new(HashSet::new()),
|
||||||
|
record_locks: Mutex::new((Arc::new(Mutex::new(HashSet::new())), parent_record_locks)),
|
||||||
paths: parent.paths.clone(),
|
paths: parent.paths.clone(),
|
||||||
own_paths: parent.own_paths,
|
own_paths: parent.own_paths,
|
||||||
}
|
}
|
||||||
@ -330,12 +348,11 @@ impl Accounts {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn lock_account(
|
fn lock_account(
|
||||||
(fork_locks, parent_locks): &mut AccountLocks,
|
(fork_locks, parent_locks): (&mut HashSet<Pubkey>, &mut Vec<Arc<AccountLocks>>),
|
||||||
keys: &[Pubkey],
|
keys: &[Pubkey],
|
||||||
error_counters: &mut ErrorCounters,
|
error_counters: &mut ErrorCounters,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
// Copy all the accounts
|
// Copy all the accounts
|
||||||
let mut fork_locks = fork_locks.lock().unwrap();
|
|
||||||
for k in keys {
|
for k in keys {
|
||||||
let is_locked = {
|
let is_locked = {
|
||||||
if fork_locks.contains(k) {
|
if fork_locks.contains(k) {
|
||||||
@ -344,16 +361,25 @@ impl Accounts {
|
|||||||
// Check parent locks. As soon as a set of parent locks is empty,
|
// Check parent locks. As soon as a set of parent locks is empty,
|
||||||
// we can remove it from the list b/c that means the parent has
|
// we can remove it from the list b/c that means the parent has
|
||||||
// released the locks.
|
// released the locks.
|
||||||
let mut is_locked = false;
|
|
||||||
parent_locks.retain(|p| {
|
parent_locks.retain(|p| {
|
||||||
|
loop {
|
||||||
|
{
|
||||||
|
// If a parent bank holds a record lock for this account, then loop
|
||||||
|
// until that lock is released
|
||||||
let p = p.lock().unwrap();
|
let p = p.lock().unwrap();
|
||||||
if p.contains(k) {
|
if !p.contains(k) {
|
||||||
is_locked = true;
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If a parent is currently recording for this key, then drop lock and wait
|
||||||
|
sleep(Duration::from_millis(WAIT_FOR_PARENT_MS));
|
||||||
|
}
|
||||||
|
|
||||||
|
let p = p.lock().unwrap();
|
||||||
!p.is_empty()
|
!p.is_empty()
|
||||||
});
|
});
|
||||||
is_locked
|
false
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
if is_locked {
|
if is_locked {
|
||||||
@ -368,6 +394,17 @@ impl Accounts {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn lock_record_account(record_locks: &AccountLocks, keys: &[Pubkey]) {
|
||||||
|
let mut fork_locks = record_locks.lock().unwrap();
|
||||||
|
for k in keys {
|
||||||
|
// The fork locks should always be a subset of the account locks, so
|
||||||
|
// the account locks should prevent record locks from ever touching the
|
||||||
|
// same accounts
|
||||||
|
assert!(!fork_locks.contains(k));
|
||||||
|
fork_locks.insert(*k);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn unlock_account(tx: &Transaction, result: &Result<()>, locks: &mut HashSet<Pubkey>) {
|
fn unlock_account(tx: &Transaction, result: &Result<()>, locks: &mut HashSet<Pubkey>) {
|
||||||
match result {
|
match result {
|
||||||
Err(TransactionError::AccountInUse) => (),
|
Err(TransactionError::AccountInUse) => (),
|
||||||
@ -379,6 +416,15 @@ impl Accounts {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn unlock_record_account<I>(tx: &I, locks: &mut HashSet<Pubkey>)
|
||||||
|
where
|
||||||
|
I: Borrow<Transaction>,
|
||||||
|
{
|
||||||
|
for k in &tx.borrow().message().account_keys {
|
||||||
|
locks.remove(k);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn hash_account(stored_account: &StoredAccount) -> Hash {
|
fn hash_account(stored_account: &StoredAccount) -> Hash {
|
||||||
let mut hasher = Hasher::default();
|
let mut hasher = Hasher::default();
|
||||||
hasher.hash(&serialize(&stored_account.balance).unwrap());
|
hasher.hash(&serialize(&stored_account.balance).unwrap());
|
||||||
@ -414,15 +460,18 @@ 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<I>(&self, txs: &[I]) -> Vec<Result<()>>
|
||||||
let mut account_locks = self.account_locks.lock().unwrap();
|
where
|
||||||
|
I: Borrow<Transaction>,
|
||||||
|
{
|
||||||
|
let (_, ref mut parent_record_locks) = *self.record_locks.lock().unwrap();
|
||||||
let mut error_counters = ErrorCounters::default();
|
let mut error_counters = ErrorCounters::default();
|
||||||
let rv = txs
|
let rv = txs
|
||||||
.iter()
|
.iter()
|
||||||
.map(|tx| {
|
.map(|tx| {
|
||||||
Self::lock_account(
|
Self::lock_account(
|
||||||
&mut account_locks,
|
(&mut self.account_locks.lock().unwrap(), parent_record_locks),
|
||||||
&tx.message().account_keys,
|
&tx.borrow().message().account_keys,
|
||||||
&mut error_counters,
|
&mut error_counters,
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
@ -436,13 +485,36 @@ impl Accounts {
|
|||||||
rv
|
rv
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn lock_record_accounts<I>(&self, txs: &[I])
|
||||||
|
where
|
||||||
|
I: Borrow<Transaction>,
|
||||||
|
{
|
||||||
|
let record_locks = self.record_locks.lock().unwrap();
|
||||||
|
for tx in txs {
|
||||||
|
Self::lock_record_account(&record_locks.0, &tx.borrow().message().account_keys);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Once accounts are unlocked, new transactions that modify that state can enter the pipeline
|
/// Once accounts are unlocked, new transactions that modify that state can enter the pipeline
|
||||||
pub fn unlock_accounts(&self, txs: &[Transaction], results: &[Result<()>]) {
|
pub fn unlock_accounts<I>(&self, txs: &[I], results: &[Result<()>])
|
||||||
let (ref my_locks, _) = *self.account_locks.lock().unwrap();
|
where
|
||||||
|
I: Borrow<Transaction>,
|
||||||
|
{
|
||||||
|
let my_locks = &mut self.account_locks.lock().unwrap();
|
||||||
debug!("bank unlock accounts");
|
debug!("bank unlock accounts");
|
||||||
txs.iter().zip(results.iter()).for_each(|(tx, result)| {
|
txs.iter()
|
||||||
Self::unlock_account(tx, result, &mut my_locks.lock().unwrap())
|
.zip(results.iter())
|
||||||
});
|
.for_each(|(tx, result)| Self::unlock_account(tx.borrow(), result, my_locks));
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn unlock_record_accounts<I>(&self, txs: &[I])
|
||||||
|
where
|
||||||
|
I: Borrow<Transaction>,
|
||||||
|
{
|
||||||
|
let (ref my_record_locks, _) = *self.record_locks.lock().unwrap();
|
||||||
|
for tx in txs {
|
||||||
|
Self::unlock_record_account(tx, &mut my_record_locks.lock().unwrap())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn has_accounts(&self, fork: Fork) -> bool {
|
pub fn has_accounts(&self, fork: Fork) -> bool {
|
||||||
@ -504,6 +576,8 @@ mod tests {
|
|||||||
use solana_sdk::instruction::CompiledInstruction;
|
use solana_sdk::instruction::CompiledInstruction;
|
||||||
use solana_sdk::signature::{Keypair, KeypairUtil};
|
use solana_sdk::signature::{Keypair, KeypairUtil};
|
||||||
use solana_sdk::transaction::Transaction;
|
use solana_sdk::transaction::Transaction;
|
||||||
|
use std::thread::{sleep, Builder};
|
||||||
|
use std::time::{Duration, Instant};
|
||||||
|
|
||||||
fn load_accounts_with_fee(
|
fn load_accounts_with_fee(
|
||||||
tx: Transaction,
|
tx: Transaction,
|
||||||
@ -957,69 +1031,83 @@ mod tests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_parent_locked_accounts() {
|
fn test_parent_locked_record_accounts() {
|
||||||
let mut parent = Accounts::new(None);
|
let mut parent = Accounts::new(None);
|
||||||
let locked_pubkey = Keypair::new().pubkey();
|
let locked_pubkey = Keypair::new().pubkey();
|
||||||
let mut locked_accounts = HashSet::new();
|
let mut locked_accounts = HashSet::new();
|
||||||
locked_accounts.insert(locked_pubkey);
|
locked_accounts.insert(locked_pubkey);
|
||||||
parent.account_locks = Mutex::new((Arc::new(Mutex::new(locked_accounts.clone())), vec![]));
|
parent.record_locks = Mutex::new((Arc::new(Mutex::new(locked_accounts.clone())), vec![]));
|
||||||
|
let parent = Arc::new(parent);
|
||||||
let child = Accounts::new_from_parent(&parent);
|
let child = Accounts::new_from_parent(&parent);
|
||||||
|
|
||||||
// Make sure child contains the parent's locked accounts
|
// Make sure child record locks contains the parent's locked record accounts
|
||||||
{
|
{
|
||||||
let (_, ref mut parent_account_locks) = *child.account_locks.lock().unwrap();
|
let (_, ref parent_record_locks) = *child.record_locks.lock().unwrap();
|
||||||
assert_eq!(parent_account_locks.len(), 1);
|
assert_eq!(parent_record_locks.len(), 1);
|
||||||
assert_eq!(locked_accounts, *parent_account_locks[0].lock().unwrap());
|
assert_eq!(locked_accounts, *parent_record_locks[0].lock().unwrap());
|
||||||
}
|
}
|
||||||
|
|
||||||
// Make sure locking on same account in the child fails
|
let parent_ = parent.clone();
|
||||||
|
let parent_thread = Builder::new()
|
||||||
|
.name("exit".to_string())
|
||||||
|
.spawn(move || {
|
||||||
|
sleep(Duration::from_secs(2));
|
||||||
|
// Unlock the accounts in the parent
|
||||||
|
{
|
||||||
|
let (ref parent_record_locks, _) = *parent_.record_locks.lock().unwrap();
|
||||||
|
parent_record_locks.lock().unwrap().clear();
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
// Function will block until the parent_thread unlocks the parent's record lock
|
||||||
|
let now = Instant::now();
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
Accounts::lock_account(
|
Accounts::lock_account(
|
||||||
|
(
|
||||||
&mut child.account_locks.lock().unwrap(),
|
&mut child.account_locks.lock().unwrap(),
|
||||||
|
&mut child.record_locks.lock().unwrap().1
|
||||||
|
),
|
||||||
&vec![locked_pubkey],
|
&vec![locked_pubkey],
|
||||||
&mut ErrorCounters::default()
|
&mut ErrorCounters::default()
|
||||||
),
|
),
|
||||||
Err(TransactionError::AccountInUse)
|
Ok(())
|
||||||
);
|
);
|
||||||
|
// Make sure that the function blocked
|
||||||
|
assert!(now.elapsed().as_secs() > 1);
|
||||||
|
parent_thread.join().unwrap();
|
||||||
|
|
||||||
// Unlock the accounts in the parent
|
|
||||||
{
|
{
|
||||||
let (ref parent_accounts, _) = *parent.account_locks.lock().unwrap();
|
// Check the lock was successfully obtained
|
||||||
parent_accounts.lock().unwrap().clear();
|
let child_account_locks = &mut child.account_locks.lock().unwrap();
|
||||||
}
|
let parent_record_locks = child.record_locks.lock().unwrap();
|
||||||
|
assert_eq!(child_account_locks.len(), 1);
|
||||||
|
|
||||||
// Make sure child removes the parent locked_accounts after the parent has
|
// Make sure child removed the parent's record locks after the parent had
|
||||||
// released all its locks
|
// released all its locks
|
||||||
assert!(Accounts::lock_account(
|
assert!(parent_record_locks.1.is_empty());
|
||||||
&mut child.account_locks.lock().unwrap(),
|
|
||||||
&vec![locked_pubkey],
|
// After all the checks pass, clear the account we just locked from the
|
||||||
&mut ErrorCounters::default()
|
// set of locks
|
||||||
)
|
child_account_locks.clear();
|
||||||
.is_ok());
|
|
||||||
{
|
|
||||||
let child_account_locks = child.account_locks.lock().unwrap();
|
|
||||||
assert_eq!(child_account_locks.0.lock().unwrap().len(), 1);
|
|
||||||
assert!(child_account_locks.1.is_empty());
|
|
||||||
// Clear the account we just locked from our locks
|
|
||||||
child_account_locks.0.lock().unwrap().clear();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Make sure new_from_parent() also cleans up old locked parent accounts, in
|
// Make sure calling new_from_parent() on the child bank also cleans up the copy of old locked
|
||||||
// case the child doesn't call lock_account() after a parent has released their
|
// parent accounts, in case the child doesn't call lock_account() after a parent has
|
||||||
// account locks
|
// released their account locks
|
||||||
{
|
{
|
||||||
// Mock an empty parent locked_accounts HashSet
|
// Mock an empty set of parent record accounts in the child bank
|
||||||
let (_, ref mut parent_account_locks) = *child.account_locks.lock().unwrap();
|
let (_, ref mut parent_record_locks) = *child.record_locks.lock().unwrap();
|
||||||
parent_account_locks.push(Arc::new(Mutex::new(HashSet::new())));
|
parent_record_locks.push(Arc::new(Mutex::new(HashSet::new())));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Call new_from_parent, make sure the empty parent locked_accounts is purged
|
// Call new_from_parent, make sure the empty parent locked_accounts is purged
|
||||||
let child2 = Accounts::new_from_parent(&child);
|
let child2 = Accounts::new_from_parent(&child);
|
||||||
{
|
{
|
||||||
let (_, ref mut parent_account_locks) = *child.account_locks.lock().unwrap();
|
let (_, ref mut parent_record_locks) = *child.record_locks.lock().unwrap();
|
||||||
assert!(parent_account_locks.is_empty());
|
assert!(parent_record_locks.is_empty());
|
||||||
let (_, ref mut parent_account_locks2) = *child2.account_locks.lock().unwrap();
|
let (_, ref mut parent_record_locks2) = *child2.record_locks.lock().unwrap();
|
||||||
assert!(parent_account_locks2.is_empty());
|
assert!(parent_record_locks2.is_empty());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
//! 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::accounts::Accounts;
|
use crate::accounts::{AccountLockType, Accounts};
|
||||||
use crate::accounts_db::{ErrorCounters, InstructionAccounts, InstructionLoaders};
|
use crate::accounts_db::{ErrorCounters, InstructionAccounts, InstructionLoaders};
|
||||||
use crate::accounts_index::Fork;
|
use crate::accounts_index::Fork;
|
||||||
use crate::blockhash_queue::BlockhashQueue;
|
use crate::blockhash_queue::BlockhashQueue;
|
||||||
@ -26,6 +26,7 @@ use solana_sdk::system_transaction;
|
|||||||
use solana_sdk::timing::{duration_as_ms, duration_as_us, MAX_RECENT_BLOCKHASHES};
|
use solana_sdk::timing::{duration_as_ms, duration_as_us, MAX_RECENT_BLOCKHASHES};
|
||||||
use solana_sdk::transaction::{Result, Transaction, TransactionError};
|
use solana_sdk::transaction::{Result, Transaction, TransactionError};
|
||||||
use solana_vote_api::vote_state::MAX_LOCKOUT_HISTORY;
|
use solana_vote_api::vote_state::MAX_LOCKOUT_HISTORY;
|
||||||
|
use std::borrow::Borrow;
|
||||||
use std::cmp;
|
use std::cmp;
|
||||||
use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering};
|
use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering};
|
||||||
use std::sync::{Arc, RwLock};
|
use std::sync::{Arc, RwLock};
|
||||||
@ -492,28 +493,51 @@ impl Bank {
|
|||||||
.map_or(Ok(()), |sig| self.get_signature_status(sig).unwrap())
|
.map_or(Ok(()), |sig| self.get_signature_status(sig).unwrap())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn lock_accounts<'a, 'b>(
|
pub fn lock_accounts<'a, 'b, I>(&'a self, txs: &'b [I]) -> LockedAccountsResults<'a, 'b, I>
|
||||||
&'a self,
|
where
|
||||||
txs: &'b [Transaction],
|
I: std::borrow::Borrow<Transaction>,
|
||||||
) -> 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.accounts.lock_accounts(txs);
|
let results = self.accounts.lock_accounts(txs);
|
||||||
LockedAccountsResults::new(results, &self, txs)
|
LockedAccountsResults::new(results, &self, txs, AccountLockType::AccountLock)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn unlock_accounts(&self, locked_accounts_results: &mut LockedAccountsResults) {
|
pub fn unlock_accounts<I>(&self, locked_accounts_results: &mut LockedAccountsResults<I>)
|
||||||
|
where
|
||||||
|
I: Borrow<Transaction>,
|
||||||
|
{
|
||||||
if locked_accounts_results.needs_unlock {
|
if locked_accounts_results.needs_unlock {
|
||||||
locked_accounts_results.needs_unlock = false;
|
locked_accounts_results.needs_unlock = false;
|
||||||
self.accounts.unlock_accounts(
|
match locked_accounts_results.lock_type() {
|
||||||
|
AccountLockType::AccountLock => self.accounts.unlock_accounts(
|
||||||
locked_accounts_results.transactions(),
|
locked_accounts_results.transactions(),
|
||||||
locked_accounts_results.locked_accounts_results(),
|
locked_accounts_results.locked_accounts_results(),
|
||||||
)
|
),
|
||||||
|
AccountLockType::RecordLock => self
|
||||||
|
.accounts
|
||||||
|
.unlock_record_accounts(locked_accounts_results.transactions()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn lock_record_accounts<'a, 'b, I>(
|
||||||
|
&'a self,
|
||||||
|
txs: &'b [I],
|
||||||
|
) -> LockedAccountsResults<'a, 'b, I>
|
||||||
|
where
|
||||||
|
I: std::borrow::Borrow<Transaction>,
|
||||||
|
{
|
||||||
|
self.accounts.lock_record_accounts(txs);
|
||||||
|
LockedAccountsResults::new(vec![], &self, txs, AccountLockType::RecordLock)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn unlock_record_accounts(&self, txs: &[Transaction]) {
|
||||||
|
self.accounts.unlock_record_accounts(txs)
|
||||||
|
}
|
||||||
|
|
||||||
fn load_accounts(
|
fn load_accounts(
|
||||||
&self,
|
&self,
|
||||||
@ -532,7 +556,7 @@ impl Bank {
|
|||||||
fn check_refs(
|
fn check_refs(
|
||||||
&self,
|
&self,
|
||||||
txs: &[Transaction],
|
txs: &[Transaction],
|
||||||
lock_results: &LockedAccountsResults,
|
lock_results: &LockedAccountsResults<Transaction>,
|
||||||
error_counters: &mut ErrorCounters,
|
error_counters: &mut ErrorCounters,
|
||||||
) -> Vec<Result<()>> {
|
) -> Vec<Result<()>> {
|
||||||
txs.iter()
|
txs.iter()
|
||||||
@ -603,7 +627,7 @@ impl Bank {
|
|||||||
pub fn load_and_execute_transactions(
|
pub fn load_and_execute_transactions(
|
||||||
&self,
|
&self,
|
||||||
txs: &[Transaction],
|
txs: &[Transaction],
|
||||||
lock_results: &LockedAccountsResults,
|
lock_results: &LockedAccountsResults<Transaction>,
|
||||||
max_age: usize,
|
max_age: usize,
|
||||||
) -> (
|
) -> (
|
||||||
Vec<Result<(InstructionAccounts, InstructionLoaders)>>,
|
Vec<Result<(InstructionAccounts, InstructionLoaders)>>,
|
||||||
@ -774,7 +798,7 @@ impl Bank {
|
|||||||
pub fn load_execute_and_commit_transactions(
|
pub fn load_execute_and_commit_transactions(
|
||||||
&self,
|
&self,
|
||||||
txs: &[Transaction],
|
txs: &[Transaction],
|
||||||
lock_results: &LockedAccountsResults,
|
lock_results: &LockedAccountsResults<Transaction>,
|
||||||
max_age: usize,
|
max_age: usize,
|
||||||
) -> Vec<Result<()>> {
|
) -> Vec<Result<()>> {
|
||||||
let (loaded_accounts, executed) =
|
let (loaded_accounts, executed) =
|
||||||
|
@ -1,42 +1,126 @@
|
|||||||
|
use crate::accounts::AccountLockType;
|
||||||
use crate::bank::Bank;
|
use crate::bank::Bank;
|
||||||
use solana_sdk::transaction::{Result, Transaction};
|
use solana_sdk::transaction::{Result, Transaction};
|
||||||
|
use std::borrow::Borrow;
|
||||||
|
|
||||||
// Represents the results of trying to lock a set of accounts
|
// Represents the results of trying to lock a set of accounts
|
||||||
pub struct LockedAccountsResults<'a, 'b> {
|
pub struct LockedAccountsResults<'a, 'b, I: Borrow<Transaction>> {
|
||||||
locked_accounts_results: Vec<Result<()>>,
|
locked_accounts_results: Vec<Result<()>>,
|
||||||
bank: &'a Bank,
|
bank: &'a Bank,
|
||||||
transactions: &'b [Transaction],
|
transactions: &'b [I],
|
||||||
|
lock_type: AccountLockType,
|
||||||
pub(crate) needs_unlock: bool,
|
pub(crate) needs_unlock: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a, 'b> LockedAccountsResults<'a, 'b> {
|
impl<'a, 'b, I: Borrow<Transaction>> LockedAccountsResults<'a, 'b, I> {
|
||||||
pub fn new(
|
pub fn new(
|
||||||
locked_accounts_results: Vec<Result<()>>,
|
locked_accounts_results: Vec<Result<()>>,
|
||||||
bank: &'a Bank,
|
bank: &'a Bank,
|
||||||
transactions: &'b [Transaction],
|
transactions: &'b [I],
|
||||||
|
lock_type: AccountLockType,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
Self {
|
Self {
|
||||||
locked_accounts_results,
|
locked_accounts_results,
|
||||||
bank,
|
bank,
|
||||||
transactions,
|
transactions,
|
||||||
needs_unlock: true,
|
needs_unlock: true,
|
||||||
|
lock_type,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn lock_type(&self) -> AccountLockType {
|
||||||
|
self.lock_type.clone()
|
||||||
|
}
|
||||||
|
|
||||||
pub fn locked_accounts_results(&self) -> &Vec<Result<()>> {
|
pub fn locked_accounts_results(&self) -> &Vec<Result<()>> {
|
||||||
&self.locked_accounts_results
|
&self.locked_accounts_results
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn transactions(&self) -> &[Transaction] {
|
pub fn transactions(&self) -> &[I] {
|
||||||
self.transactions
|
self.transactions
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Unlock all locked accounts in destructor.
|
// Unlock all locked accounts in destructor.
|
||||||
impl<'a, 'b> Drop for LockedAccountsResults<'a, 'b> {
|
impl<'a, 'b, I: Borrow<Transaction>> Drop for LockedAccountsResults<'a, 'b, I> {
|
||||||
fn drop(&mut self) {
|
fn drop(&mut self) {
|
||||||
if self.needs_unlock {
|
if self.needs_unlock {
|
||||||
self.bank.unlock_accounts(self)
|
self.bank.unlock_accounts(self)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
use crate::bank::tests::create_genesis_block_with_leader;
|
||||||
|
use solana_sdk::pubkey::Pubkey;
|
||||||
|
use solana_sdk::signature::{Keypair, KeypairUtil};
|
||||||
|
use solana_sdk::system_transaction;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_account_locks() {
|
||||||
|
let (bank, txs) = setup();
|
||||||
|
|
||||||
|
// Test getting locked accounts
|
||||||
|
let lock_results = bank.lock_accounts(&txs);
|
||||||
|
|
||||||
|
// Grab locks
|
||||||
|
assert!(lock_results
|
||||||
|
.locked_accounts_results()
|
||||||
|
.iter()
|
||||||
|
.all(|x| x.is_ok()));
|
||||||
|
|
||||||
|
// Trying to grab locks again should fail
|
||||||
|
let lock_results2 = bank.lock_accounts(&txs);
|
||||||
|
assert!(lock_results2
|
||||||
|
.locked_accounts_results()
|
||||||
|
.iter()
|
||||||
|
.all(|x| x.is_err()));
|
||||||
|
|
||||||
|
// Drop the first set of locks
|
||||||
|
drop(lock_results);
|
||||||
|
|
||||||
|
// Now grabbing locks should work again
|
||||||
|
let lock_results2 = bank.lock_accounts(&txs);
|
||||||
|
assert!(lock_results2
|
||||||
|
.locked_accounts_results()
|
||||||
|
.iter()
|
||||||
|
.all(|x| x.is_ok()));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_record_locks() {
|
||||||
|
let (bank, txs) = setup();
|
||||||
|
|
||||||
|
// Test getting record locks
|
||||||
|
let lock_results = bank.lock_record_accounts(&txs);
|
||||||
|
|
||||||
|
// Grabbing record locks doesn't return any results, must succeed or panic.
|
||||||
|
assert!(lock_results.locked_accounts_results().is_empty());
|
||||||
|
|
||||||
|
drop(lock_results);
|
||||||
|
|
||||||
|
// Now grabbing record locks should work again
|
||||||
|
let lock_results2 = bank.lock_record_accounts(&txs);
|
||||||
|
assert!(lock_results2.locked_accounts_results().is_empty());
|
||||||
|
}
|
||||||
|
|
||||||
|
fn setup() -> (Bank, Vec<Transaction>) {
|
||||||
|
let dummy_leader_id = Pubkey::new_rand();
|
||||||
|
let (genesis_block, mint_keypair, _) =
|
||||||
|
create_genesis_block_with_leader(500, &dummy_leader_id, 100);
|
||||||
|
let bank = Bank::new(&genesis_block);
|
||||||
|
|
||||||
|
let pubkey = Pubkey::new_rand();
|
||||||
|
let keypair2 = Keypair::new();
|
||||||
|
let pubkey2 = Pubkey::new_rand();
|
||||||
|
|
||||||
|
let txs = vec![
|
||||||
|
system_transaction::transfer(&mint_keypair, &pubkey, 1, genesis_block.hash(), 0),
|
||||||
|
system_transaction::transfer(&keypair2, &pubkey2, 1, genesis_block.hash(), 0),
|
||||||
|
];
|
||||||
|
|
||||||
|
(bank, txs)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Reference in New Issue
Block a user