From 81e3ac2cd2ab2e9713d8900b728b48ec34db3692 Mon Sep 17 00:00:00 2001 From: Anatoly Yakovenko Date: Fri, 1 Feb 2019 12:41:20 -0800 Subject: [PATCH] reforkering fmt rebase forkup fixing build all leave the last block open more tests rebase fix replay test fix rollback depth parameter cleanup better replay stage s/finalized/frozen for bank_checkpoints that are no longer able to write transactions or ticks remove println disconnect blob sender so that TVU isn't replyaing onto TPU's fork finalize()->freeze(), code review comments rebased fix window_send_test remove entries_to_blocks() guard against too many ticks in a slot limiting ticks, take #2 more info fix test_replay_stage_poh_error_entry_receiver fix leader_scheduler::test_update_height s/bank_state/bank_fork/g s/bank_checkpoint/bank_delta/g fix up stragglers from rename get it building again. num_ticks_left_in_slot() should not assume slot from tick_height fixups ignore leader rotation tests until ledger is turned around renames, compile fixes --- benches/bank.rs | 19 +- benches/banking_stage.rs | 20 +- ledger-tool/src/main.rs | 4 +- src/accounts.rs | 32 +- src/bank.rs | 1170 ++++++-------------- src/bank_delta.rs | 344 ++++++ src/bank_fork.rs | 481 ++++++++ src/banking_stage.rs | 22 +- src/broadcast_service.rs | 31 +- src/cluster_info.rs | 2 +- src/compute_leader_confirmation_service.rs | 12 +- src/deltas.rs | 134 +++ src/forks.rs | 259 +++++ src/fullnode.rs | 33 +- src/last_id_queue.rs | 35 - src/leader_scheduler.rs | 9 +- src/lib.rs | 4 + src/poh_recorder.rs | 9 +- src/poh_service.rs | 2 +- src/replay_stage.rs | 186 ++-- src/retransmit_stage.rs | 6 +- src/rpc.rs | 20 +- src/rpc_pubsub.rs | 25 +- src/thin_client.rs | 1 - src/tpu.rs | 10 +- src/tvu.rs | 38 +- src/vote_signer_proxy.rs | 251 +++++ src/window_service.rs | 15 +- tests/multinode.rs | 2 +- 29 files changed, 2126 insertions(+), 1050 deletions(-) create mode 100644 src/bank_delta.rs create mode 100644 src/bank_fork.rs create mode 100644 src/deltas.rs create mode 100644 src/forks.rs create mode 100644 src/vote_signer_proxy.rs diff --git a/benches/bank.rs b/benches/bank.rs index 3925ffce97474a..bae2d38566f04c 100644 --- a/benches/bank.rs +++ b/benches/bank.rs @@ -12,6 +12,7 @@ use test::Bencher; #[bench] fn bench_process_transaction(bencher: &mut Bencher) { + solana_logger::setup(); let (genesis_block, mint_keypair) = GenesisBlock::new(100_000_000); let bank = Bank::new(&genesis_block); @@ -25,14 +26,20 @@ fn bench_process_transaction(bencher: &mut Bencher) { &mint_keypair, rando0.pubkey(), 10_000, - bank.last_id(), + bank.active_fork().last_id(), 0, ); assert_eq!(bank.process_transaction(&tx), Ok(())); // Seed the 'to' account and a cell for its signature. let rando1 = Keypair::new(); - let tx = SystemTransaction::new_move(&rando0, rando1.pubkey(), 1, bank.last_id(), 0); + let tx = SystemTransaction::new_move( + &rando0, + rando1.pubkey(), + 1, + bank.active_fork().last_id(), + 0, + ); assert_eq!(bank.process_transaction(&tx), Ok(())); // Finally, return the transaction to the benchmark. @@ -40,16 +47,16 @@ fn bench_process_transaction(bencher: &mut Bencher) { }) .collect(); - let mut id = bank.last_id(); + let mut id = bank.active_fork().last_id(); - for _ in 0..(MAX_ENTRY_IDS - 1) { - bank.register_tick(&id); + for _ in 0..(MAX_ENTRY_IDS / 2) { + bank.active_fork().register_tick(&id); id = hash(&id.as_ref()) } bencher.iter(|| { // Since benchmarker runs this multiple times, we need to clear the signatures. - bank.clear_signatures(); + bank.active_fork().clear_signatures(); let results = bank.process_transactions(&transactions); assert!(results.iter().all(Result::is_ok)); }) diff --git a/benches/banking_stage.rs b/benches/banking_stage.rs index 9d39cb8a481c81..3dccaaa4e9b40e 100644 --- a/benches/banking_stage.rs +++ b/benches/banking_stage.rs @@ -86,13 +86,13 @@ fn bench_banking_stage_multi_accounts(bencher: &mut Bencher) { let res = bank.process_transaction(&tx); assert!(res.is_ok(), "sanity test transactions"); }); - bank.clear_signatures(); + bank.active_fork().clear_signatures(); //sanity check, make sure all the transactions can execute in parallel let res = bank.process_transactions(&transactions); for r in res { assert!(r.is_ok(), "sanity parallel execution"); } - bank.clear_signatures(); + bank.active_fork().clear_signatures(); let verified: Vec<_> = to_packets_chunked(&transactions.clone(), 192) .into_iter() .map(|x| { @@ -114,19 +114,19 @@ fn bench_banking_stage_multi_accounts(bencher: &mut Bencher) { let mut id = genesis_block.last_id(); for _ in 0..MAX_ENTRY_IDS { id = hash(&id.as_ref()); - bank.register_tick(&id); + bank.active_fork().register_tick(&id); } let half_len = verified.len() / 2; let mut start = 0; bencher.iter(move || { // make sure the transactions are still valid - bank.register_tick(&genesis_block.last_id()); + bank.active_fork().register_tick(&genesis_block.last_id()); for v in verified[start..start + half_len].chunks(verified.len() / num_threads) { verified_sender.send(v.to_vec()).unwrap(); } check_txs(&signal_receiver, txes / 2); - bank.clear_signatures(); + bank.active_fork().clear_signatures(); start += half_len; start %= verified.len(); }); @@ -195,13 +195,13 @@ fn bench_banking_stage_multi_programs(bencher: &mut Bencher) { let res = bank.process_transaction(&tx); assert!(res.is_ok(), "sanity test transactions"); }); - bank.clear_signatures(); + bank.active_fork().clear_signatures(); //sanity check, make sure all the transactions can execute in parallel let res = bank.process_transactions(&transactions); for r in res { assert!(r.is_ok(), "sanity parallel execution"); } - bank.clear_signatures(); + bank.active_fork().clear_signatures(); let verified: Vec<_> = to_packets_chunked(&transactions.clone(), 96) .into_iter() .map(|x| { @@ -223,19 +223,19 @@ fn bench_banking_stage_multi_programs(bencher: &mut Bencher) { let mut id = genesis_block.last_id(); for _ in 0..MAX_ENTRY_IDS { id = hash(&id.as_ref()); - bank.register_tick(&id); + bank.active_fork().register_tick(&id); } let half_len = verified.len() / 2; let mut start = 0; bencher.iter(move || { // make sure the transactions are still valid - bank.register_tick(&genesis_block.last_id()); + bank.active_fork().register_tick(&genesis_block.last_id()); for v in verified[start..start + half_len].chunks(verified.len() / num_threads) { verified_sender.send(v.to_vec()).unwrap(); } check_txs(&signal_receiver, txes / 2); - bank.clear_signatures(); + bank.active_fork().clear_signatures(); start += half_len; start %= verified.len(); }); diff --git a/ledger-tool/src/main.rs b/ledger-tool/src/main.rs index a8a03345ab0aae..b931c4ab9bdd79 100644 --- a/ledger-tool/src/main.rs +++ b/ledger-tool/src/main.rs @@ -113,7 +113,7 @@ fn main() { } ("verify", _) => { let bank = Bank::new(&genesis_block); - let mut last_id = bank.last_id(); + let mut last_id = bank.active_fork().last_id(); let mut num_entries = 0; for (i, entry) in entries.enumerate() { if i >= head { @@ -129,7 +129,7 @@ fn main() { last_id = entry.id; num_entries += 1; - if let Err(e) = bank.process_entry(&entry) { + if let Err(e) = bank.active_fork().process_entries(&[entry]) { eprintln!("verify failed at entry[{}], err: {:?}", i + 2, e); if !matches.is_present("continue") { exit(1); diff --git a/src/accounts.rs b/src/accounts.rs index 6d913f0db46d6d..609e3a2d7d996b 100644 --- a/src/accounts.rs +++ b/src/accounts.rs @@ -81,11 +81,11 @@ impl AccountsDB { hash(&serialize(&ordered_accounts).unwrap()) } - fn load(checkpoints: &[U], pubkey: &Pubkey) -> Option + fn load(deltas: &[U], pubkey: &Pubkey) -> Option where U: Deref, { - for db in checkpoints { + for db in deltas { if let Some(account) = db.accounts.get(pubkey) { return Some(account.clone()); } @@ -97,7 +97,7 @@ impl AccountsDB { pub fn store(&mut self, purge: bool, pubkey: &Pubkey, account: &Account) { if account.tokens == 0 { if purge { - // purge if balance is 0 and no checkpoints + // purge if balance is 0 and no deltas self.accounts.remove(pubkey); } else { // store default account if balance is 0 and there's a checkpoint @@ -128,7 +128,7 @@ impl AccountsDB { } } fn load_tx_accounts( - checkpoints: &[U], + deltas: &[U], tx: &Transaction, error_counters: &mut ErrorCounters, ) -> Result> @@ -149,7 +149,7 @@ impl AccountsDB { // If a fee can pay for execution then the program will be scheduled let mut called_accounts: Vec = vec![]; for key in &tx.account_keys { - called_accounts.push(Self::load(checkpoints, key).unwrap_or_default()); + called_accounts.push(Self::load(deltas, key).unwrap_or_default()); } if called_accounts.is_empty() || called_accounts[0].tokens == 0 { error_counters.account_not_found += 1; @@ -165,7 +165,7 @@ impl AccountsDB { } fn load_executable_accounts( - checkpoints: &[U], + deltas: &[U], mut program_id: Pubkey, error_counters: &mut ErrorCounters, ) -> Result> @@ -186,7 +186,7 @@ impl AccountsDB { } depth += 1; - let program = match Self::load(checkpoints, &program_id) { + let program = match Self::load(deltas, &program_id) { Some(program) => program, None => { error_counters.account_not_found += 1; @@ -208,7 +208,7 @@ impl AccountsDB { /// For each program_id in the transaction, load its loaders. fn load_loaders( - checkpoints: &[U], + deltas: &[U], tx: &Transaction, error_counters: &mut ErrorCounters, ) -> Result>> @@ -223,13 +223,13 @@ impl AccountsDB { return Err(BankError::AccountNotFound); } let program_id = tx.program_ids[ix.program_ids_index as usize]; - Self::load_executable_accounts(checkpoints, program_id, error_counters) + Self::load_executable_accounts(deltas, program_id, error_counters) }) .collect() } fn load_accounts( - checkpoints: &[U], + deltas: &[U], txs: &[Transaction], lock_results: Vec>, error_counters: &mut ErrorCounters, @@ -241,8 +241,8 @@ impl AccountsDB { .zip(lock_results.into_iter()) .map(|etx| match etx { (tx, Ok(())) => { - let accounts = Self::load_tx_accounts(checkpoints, tx, error_counters)?; - let loaders = Self::load_loaders(checkpoints, tx, error_counters)?; + let accounts = Self::load_tx_accounts(deltas, tx, error_counters)?; + let loaders = Self::load_loaders(deltas, tx, error_counters)?; Ok((accounts, loaders)) } (_, Err(e)) => Err(e), @@ -268,11 +268,11 @@ impl AccountsDB { impl Accounts { /// Slow because lock is held for 1 operation insted of many - pub fn load_slow(checkpoints: &[U], pubkey: &Pubkey) -> Option + pub fn load_slow(deltas: &[U], pubkey: &Pubkey) -> Option where U: Deref, { - let dbs: Vec<_> = checkpoints + let dbs: Vec<_> = deltas .iter() .map(|obj| obj.accounts_db.read().unwrap()) .collect(); @@ -350,7 +350,7 @@ impl Accounts { } pub fn load_accounts( - checkpoints: &[U], + deltas: &[U], txs: &[Transaction], results: Vec>, error_counters: &mut ErrorCounters, @@ -358,7 +358,7 @@ impl Accounts { where U: Deref, { - let dbs: Vec<_> = checkpoints + let dbs: Vec<_> = deltas .iter() .map(|obj| obj.accounts_db.read().unwrap()) .collect(); diff --git a/src/bank.rs b/src/bank.rs index fa309ff37ceb8a..3eb90c4bf30210 100644 --- a/src/bank.rs +++ b/src/bank.rs @@ -3,22 +3,17 @@ //! on behalf of the caller, and a low-level API for when they have //! already been signed and verified. -use crate::accounts::{Accounts, ErrorCounters, InstructionAccounts, InstructionLoaders}; -use crate::counter::Counter; +use crate::bank_delta::BankDelta; +use crate::bank_fork::BankFork; use crate::entry::Entry; use crate::entry::EntrySlice; +use crate::forks::{self, Forks, ForksError}; use crate::genesis_block::GenesisBlock; -use crate::last_id_queue::{LastIdQueue, MAX_ENTRY_IDS}; use crate::leader_scheduler::{LeaderScheduler, LeaderSchedulerConfig}; -use crate::poh_recorder::{PohRecorder, PohRecorderError}; -use crate::result::Error; +use crate::poh_recorder::PohRecorder; use crate::rpc_pubsub::RpcSubscriptions; -use crate::status_cache::StatusCache; use bincode::deserialize; use itertools::Itertools; -use log::Level; -use rayon::prelude::*; -use solana_runtime::{self, RuntimeError}; use solana_sdk::account::Account; use solana_sdk::bpf_loader; use solana_sdk::budget_program; @@ -31,7 +26,6 @@ use solana_sdk::signature::Signature; use solana_sdk::storage_program; use solana_sdk::system_program; use solana_sdk::system_transaction::SystemTransaction; -use solana_sdk::timing::duration_as_us; use solana_sdk::token_program; use solana_sdk::transaction::Transaction; use solana_sdk::vote_program; @@ -39,7 +33,6 @@ use std; use std::result; use std::sync::atomic::{AtomicUsize, Ordering}; use std::sync::{Arc, RwLock}; -use std::time::Instant; /// Reasons a transaction might be rejected. #[derive(Debug, PartialEq, Eq, Clone)] @@ -83,6 +76,18 @@ pub enum BankError { // Poh recorder hit the maximum tick height before leader rotation MaxHeightReached, + + /// Fork is not in the Deltas DAG + UnknownFork, + + /// The specified trunk is not in the Deltas DAG + InvalidTrunk, + + /// Specified base delta is still live + DeltaNotFrozen, + + /// Requested live delta is frozen + DeltaIsFrozen, } pub type Result = result::Result; @@ -94,17 +99,9 @@ pub trait BankSubscriptions { fn check_signature(&self, signature: &Signature, status: &Result<()>); } -type BankStatusCache = StatusCache; - /// Manager for the state of all accounts and programs after processing its entries. pub struct Bank { - pub accounts: Accounts, - - /// A cache of signature statuses - status_cache: RwLock, - - /// FIFO queue of `last_id` items - last_id_queue: RwLock, + forks: RwLock, // The latest confirmation time for the network confirmation_time: AtomicUsize, @@ -112,16 +109,13 @@ pub struct Bank { /// Tracks and updates the leader schedule based on the votes and account stakes /// processed by the bank pub leader_scheduler: Arc>, - subscriptions: RwLock>>, } impl Default for Bank { fn default() -> Self { Bank { - accounts: Accounts::default(), - last_id_queue: RwLock::new(LastIdQueue::default()), - status_cache: RwLock::new(BankStatusCache::default()), + forks: RwLock::new(Forks::default()), confirmation_time: AtomicUsize::new(std::usize::MAX), leader_scheduler: Arc::new(RwLock::new(LeaderScheduler::default())), subscriptions: RwLock::new(None), @@ -137,6 +131,8 @@ impl Bank { let mut bank = Self::default(); bank.leader_scheduler = Arc::new(RwLock::new(LeaderScheduler::new(leader_scheduler_config))); + let last_id = genesis_block.last_id(); + bank.init_root(&last_id); bank.process_genesis_block(genesis_block); bank.add_builtin_programs(); bank @@ -146,22 +142,42 @@ impl Bank { Self::new_with_leader_scheduler_config(genesis_block, &LeaderSchedulerConfig::default()) } + pub fn init_fork(&self, current: u64, last_id: &Hash, base: u64) -> Result<()> { + if self.forks.read().unwrap().is_active_fork(current) { + return Ok(()); + } + self.forks + .write() + .unwrap() + .init_fork(current, last_id, base) + .map_err(|e| match e { + ForksError::UnknownFork => BankError::UnknownFork, + ForksError::InvalidTrunk => BankError::InvalidTrunk, + ForksError::DeltaNotFrozen => BankError::DeltaNotFrozen, + ForksError::DeltaIsFrozen => BankError::DeltaIsFrozen, + }) + } + pub fn active_fork(&self) -> BankFork { + self.forks.read().unwrap().active_fork() + } + pub fn root(&self) -> BankFork { + self.forks.read().unwrap().root() + } + pub fn fork(&self, slot: u64) -> Option { + self.forks.read().unwrap().fork(slot) + } + pub fn set_subscriptions(&self, subscriptions: Arc) { let mut sub = self.subscriptions.write().unwrap(); *sub = Some(subscriptions) } - pub fn copy_for_tpu(&self) -> Self { - let mut status_cache = BankStatusCache::default(); - status_cache.merge_into_root(self.status_cache.read().unwrap().clone()); - Self { - accounts: self.accounts.copy_for_tpu(), - status_cache: RwLock::new(status_cache), - last_id_queue: RwLock::new(self.last_id_queue.read().unwrap().clone()), - confirmation_time: AtomicUsize::new(self.confirmation_time()), - leader_scheduler: self.leader_scheduler.clone(), - subscriptions: RwLock::new(None), - } + /// Init the root fork. Only tests should be using this. + pub fn init_root(&self, last_id: &Hash) { + self.forks + .write() + .unwrap() + .init_root(BankDelta::new(0, &last_id)); } fn process_genesis_block(&self, genesis_block: &GenesisBlock) { @@ -173,12 +189,13 @@ impl Bank { let mut mint_account = Account::default(); mint_account.tokens = genesis_block.tokens - genesis_block.bootstrap_leader_tokens; - self.accounts + self.root() + .head() .store_slow(true, &genesis_block.mint_id, &mint_account); let mut bootstrap_leader_account = Account::default(); bootstrap_leader_account.tokens = genesis_block.bootstrap_leader_tokens - 1; - self.accounts.store_slow( + self.root().head().store_slow( true, &genesis_block.bootstrap_leader_id, &bootstrap_leader_account, @@ -203,7 +220,7 @@ impl Bank { .serialize(&mut bootstrap_leader_vote_account.userdata) .unwrap(); - self.accounts.store_slow( + self.root().head().store_slow( true, &genesis_block.bootstrap_leader_vote_account_id, &bootstrap_leader_vote_account, @@ -213,10 +230,9 @@ impl Bank { .unwrap() .update_tick_height(0, self); - self.last_id_queue - .write() - .unwrap() - .genesis_last_id(&genesis_block.last_id()); + self.root() + .head() + .set_genesis_last_id(&genesis_block.last_id()); } fn add_builtin_programs(&self) { @@ -227,7 +243,8 @@ impl Bank { executable: true, loader: native_loader::id(), }; - self.accounts + self.root() + .head() .store_slow(true, &system_program::id(), &system_program_account); // Vote program @@ -238,7 +255,8 @@ impl Bank { executable: true, loader: native_loader::id(), }; - self.accounts + self.root() + .head() .store_slow(true, &vote_program::id(), &vote_program_account); // Storage program @@ -249,7 +267,8 @@ impl Bank { executable: true, loader: native_loader::id(), }; - self.accounts + self.root() + .head() .store_slow(true, &storage_program::id(), &storage_program_account); let storage_system_account = Account { @@ -259,7 +278,8 @@ impl Bank { executable: false, loader: Pubkey::default(), }; - self.accounts + self.root() + .head() .store_slow(true, &storage_program::system_id(), &storage_system_account); // Bpf Loader @@ -271,7 +291,8 @@ impl Bank { loader: native_loader::id(), }; - self.accounts + self.root() + .head() .store_slow(true, &bpf_loader::id(), &bpf_loader_account); // Budget program @@ -282,7 +303,8 @@ impl Bank { executable: true, loader: native_loader::id(), }; - self.accounts + self.root() + .head() .store_slow(true, &budget_program::id(), &budget_program_account); // Erc20 token program @@ -294,21 +316,17 @@ impl Bank { loader: native_loader::id(), }; - self.accounts + self.root() + .head() .store_slow(true, &token_program::id(), &erc20_account); } - /// Return the last entry ID registered. - pub fn last_id(&self) -> Hash { - self.last_id_queue - .read() - .unwrap() - .last_id - .expect("no last_id has been set") - } - pub fn get_storage_entry_height(&self) -> u64 { - match self.get_account(&storage_program::system_id()) { + //TODO: root or live? + match self + .active_fork() + .get_account_slow(&storage_program::system_id()) + { Some(storage_system_account) => { let state = deserialize(&storage_system_account.userdata); if let Ok(state) = state { @@ -324,7 +342,10 @@ impl Bank { } pub fn get_storage_last_id(&self) -> Hash { - if let Some(storage_system_account) = self.get_account(&storage_program::system_id()) { + if let Some(storage_system_account) = self + .active_fork() + .get_account_slow(&storage_program::system_id()) + { let state = deserialize(&storage_system_account.userdata); if let Ok(state) = state { let state: storage_program::StorageProgramState = state; @@ -334,478 +355,14 @@ impl Bank { Hash::default() } - /// Forget all signatures. Useful for benchmarking. - pub fn clear_signatures(&self) { - self.status_cache.write().unwrap().clear(); - } - - fn update_subscriptions(&self, txs: &[Transaction], res: &[Result<()>]) { - for (i, tx) in txs.iter().enumerate() { - if let Some(ref subs) = *self.subscriptions.read().unwrap() { - subs.check_signature(&tx.signatures[0], &res[i]); - } - } - } - fn update_transaction_statuses(&self, txs: &[Transaction], res: &[Result<()>]) { - let mut status_cache = self.status_cache.write().unwrap(); - for (i, tx) in txs.iter().enumerate() { - match &res[i] { - Ok(_) => status_cache.add(&tx.signatures[0]), - Err(BankError::LastIdNotFound) => (), - Err(BankError::DuplicateSignature) => (), - Err(BankError::AccountNotFound) => (), - Err(e) => { - status_cache.add(&tx.signatures[0]); - status_cache.save_failure_status(&tx.signatures[0], e.clone()); - } - } - } - } - - /// Looks through a list of tick heights and stakes, and finds the latest - /// tick that has achieved confirmation - pub fn get_confirmation_timestamp( - &self, - ticks_and_stakes: &mut [(u64, u64)], - supermajority_stake: u64, - ) -> Option { - let last_ids = self.last_id_queue.read().unwrap(); - last_ids.get_confirmation_timestamp(ticks_and_stakes, supermajority_stake) - } - - /// Tell the bank which Entry IDs exist on the ledger. This function - /// assumes subsequent calls correspond to later entries, and will boot - /// the oldest ones once its internal cache is full. Once boot, the - /// bank will reject transactions using that `last_id`. - pub fn register_tick(&self, last_id: &Hash) { - let current_tick_height = { - let mut last_id_queue = self.last_id_queue.write().unwrap(); - inc_new_counter_info!("bank-register_tick-registered", 1); - last_id_queue.register_tick(last_id); - last_id_queue.tick_height - }; - self.leader_scheduler - .write() - .unwrap() - .update_tick_height(current_tick_height, self); - } - - /// Process a Transaction. This is used for unit tests and simply calls the vector Bank::process_transactions method. - pub fn process_transaction(&self, tx: &Transaction) -> Result<()> { - let txs = vec![tx.clone()]; - match self.process_transactions(&txs)[0] { - Err(ref e) => { - info!("process_transaction error: {:?}", e); - Err((*e).clone()) - } - Ok(_) => Ok(()), - } - } - - fn lock_accounts(&self, txs: &[Transaction]) -> Vec> { - self.accounts.lock_accounts(txs) - } - - fn unlock_accounts(&self, txs: &[Transaction], results: &[Result<()>]) { - self.accounts.unlock_accounts(txs, results) - } - - pub fn process_and_record_transactions( - &self, - txs: &[Transaction], - poh: &PohRecorder, - ) -> Result<()> { - let now = Instant::now(); - // Once accounts are locked, other threads cannot encode transactions that will modify the - // same account state - let lock_results = self.lock_accounts(txs); - let lock_time = now.elapsed(); - - let now = Instant::now(); - // Use a shorter maximum age when adding transactions into the pipeline. This will reduce - // the likelihood of any single thread getting starved and processing old ids. - // TODO: Banking stage threads should be prioritized to complete faster then this queue - // expires. - let (loaded_accounts, results) = - self.load_and_execute_transactions(txs, lock_results, MAX_ENTRY_IDS as usize / 2); - let load_execute_time = now.elapsed(); - - let record_time = { - let now = Instant::now(); - self.record_transactions(txs, &results, poh)?; - now.elapsed() - }; - - let commit_time = { - let now = Instant::now(); - self.commit_transactions(txs, &loaded_accounts, &results); - now.elapsed() - }; - - let now = Instant::now(); - // Once the accounts are new transactions can enter the pipeline to process them - self.unlock_accounts(&txs, &results); - let unlock_time = now.elapsed(); - debug!( - "lock: {}us load_execute: {}us record: {}us commit: {}us unlock: {}us txs_len: {}", - duration_as_us(&lock_time), - duration_as_us(&load_execute_time), - duration_as_us(&record_time), - duration_as_us(&commit_time), - duration_as_us(&unlock_time), - txs.len(), - ); - Ok(()) - } - - fn record_transactions( - &self, - txs: &[Transaction], - results: &[Result<()>], - poh: &PohRecorder, - ) -> Result<()> { - let processed_transactions: Vec<_> = results - .iter() - .zip(txs.iter()) - .filter_map(|(r, x)| match r { - Ok(_) => Some(x.clone()), - Err(BankError::ProgramError(index, err)) => { - info!("program error {:?}, {:?}", index, err); - Some(x.clone()) - } - Err(ref e) => { - debug!("process transaction failed {:?}", e); - None - } - }) - .collect(); - debug!("processed: {} ", processed_transactions.len()); - // unlock all the accounts with errors which are filtered by the above `filter_map` - if !processed_transactions.is_empty() { - let hash = Transaction::hash(&processed_transactions); - // record and unlock will unlock all the successfull transactions - poh.record(hash, processed_transactions).map_err(|e| { - warn!("record failure: {:?}", e); - match e { - Error::PohRecorderError(PohRecorderError::MaxHeightReached) => { - BankError::MaxHeightReached - } - _ => BankError::RecordFailure, - } - })?; - } - Ok(()) - } - - fn load_accounts( - &self, - txs: &[Transaction], - results: Vec>, - error_counters: &mut ErrorCounters, - ) -> Vec> { - Accounts::load_accounts(&[&self.accounts], txs, results, error_counters) - } - fn check_age( - &self, - txs: &[Transaction], - lock_results: Vec>, - max_age: usize, - error_counters: &mut ErrorCounters, - ) -> Vec> { - let last_ids = self.last_id_queue.read().unwrap(); - txs.iter() - .zip(lock_results.into_iter()) - .map(|(tx, lock_res)| { - if lock_res.is_ok() && !last_ids.check_entry_id_age(tx.last_id, max_age) { - error_counters.reserve_last_id += 1; - Err(BankError::LastIdNotFound) - } else { - lock_res - } - }) - .collect() - } - fn check_signatures( - &self, - txs: &[Transaction], - lock_results: Vec>, - error_counters: &mut ErrorCounters, - ) -> Vec> { - let status_cache = self.status_cache.read().unwrap(); - txs.iter() - .zip(lock_results.into_iter()) - .map(|(tx, lock_res)| { - if lock_res.is_ok() && status_cache.has_signature(&tx.signatures[0]) { - error_counters.duplicate_signature += 1; - Err(BankError::DuplicateSignature) - } else { - lock_res - } - }) - .collect() - } - #[allow(clippy::type_complexity)] - fn load_and_execute_transactions( - &self, - txs: &[Transaction], - lock_results: Vec>, - max_age: usize, - ) -> ( - Vec>, - Vec>, - ) { - debug!("processing transactions: {}", txs.len()); - let mut error_counters = ErrorCounters::default(); - let now = Instant::now(); - let age_results = self.check_age(txs, lock_results, max_age, &mut error_counters); - let sig_results = self.check_signatures(txs, age_results, &mut error_counters); - let mut loaded_accounts = self.load_accounts(txs, sig_results, &mut error_counters); - let tick_height = self.tick_height(); - - let load_elapsed = now.elapsed(); - let now = Instant::now(); - let executed: Vec> = loaded_accounts - .iter_mut() - .zip(txs.iter()) - .map(|(accs, tx)| match accs { - Err(e) => Err(e.clone()), - Ok((ref mut accounts, ref mut loaders)) => { - solana_runtime::execute_transaction(tx, loaders, accounts, tick_height).map_err( - |RuntimeError::ProgramError(index, err)| { - BankError::ProgramError(index, err) - }, - ) - } - }) - .collect(); - - let execution_elapsed = now.elapsed(); - - debug!( - "load: {}us execute: {}us txs_len={}", - duration_as_us(&load_elapsed), - duration_as_us(&execution_elapsed), - txs.len(), - ); - let mut tx_count = 0; - let mut err_count = 0; - for (r, tx) in executed.iter().zip(txs.iter()) { - if r.is_ok() { - tx_count += 1; - } else { - if err_count == 0 { - info!("tx error: {:?} {:?}", r, tx); - } - err_count += 1; - } - } - if err_count > 0 { - info!("{} errors of {} txs", err_count, err_count + tx_count); - inc_new_counter_info!( - "bank-process_transactions-account_not_found", - error_counters.account_not_found - ); - inc_new_counter_info!("bank-process_transactions-error_count", err_count); - } - - self.accounts.increment_transaction_count(tx_count); - - inc_new_counter_info!("bank-process_transactions-txs", tx_count); - if 0 != error_counters.last_id_not_found { - inc_new_counter_info!( - "bank-process_transactions-error-last_id_not_found", - error_counters.last_id_not_found - ); - } - if 0 != error_counters.reserve_last_id { - inc_new_counter_info!( - "bank-process_transactions-error-reserve_last_id", - error_counters.reserve_last_id - ); - } - if 0 != error_counters.duplicate_signature { - inc_new_counter_info!( - "bank-process_transactions-error-duplicate_signature", - error_counters.duplicate_signature - ); - } - if 0 != error_counters.insufficient_funds { - inc_new_counter_info!( - "bank-process_transactions-error-insufficient_funds", - error_counters.insufficient_funds - ); - } - if 0 != error_counters.account_loaded_twice { - inc_new_counter_info!( - "bank-process_transactions-account_loaded_twice", - error_counters.account_loaded_twice - ); - } - (loaded_accounts, executed) - } - - fn commit_transactions( - &self, - txs: &[Transaction], - loaded_accounts: &[Result<(InstructionAccounts, InstructionLoaders)>], - executed: &[Result<()>], - ) { - let now = Instant::now(); - self.accounts - .store_accounts(true, txs, executed, loaded_accounts); - - // Check account subscriptions and send notifications - self.send_account_notifications(txs, executed, loaded_accounts); - - // once committed there is no way to unroll - let write_elapsed = now.elapsed(); - debug!( - "store: {}us txs_len={}", - duration_as_us(&write_elapsed), - txs.len(), - ); - self.update_transaction_statuses(txs, &executed); - self.update_subscriptions(txs, &executed); - } - - /// Process a batch of transactions. - #[must_use] - pub fn load_execute_and_commit_transactions( - &self, - txs: &[Transaction], - lock_results: Vec>, - max_age: usize, - ) -> Vec> { - let (loaded_accounts, executed) = - self.load_and_execute_transactions(txs, lock_results, max_age); - - self.commit_transactions(txs, &loaded_accounts, &executed); - executed - } - - #[must_use] - pub fn process_transactions(&self, txs: &[Transaction]) -> Vec> { - let lock_results = self.lock_accounts(txs); - let results = self.load_execute_and_commit_transactions(txs, lock_results, MAX_ENTRY_IDS); - self.unlock_accounts(txs, &results); - results - } - - pub fn process_entry(&self, entry: &Entry) -> Result<()> { - if !entry.is_tick() { - for result in self.process_transactions(&entry.transactions) { - match result { - // Entries that result in a ProgramError are still valid and are written in the - // ledger so map them to an ok return value - Err(BankError::ProgramError(_, _)) => Ok(()), - _ => result, - }?; - } - } else { - self.register_tick(&entry.id); - } - - Ok(()) - } - - /// Process an ordered list of entries. - pub fn process_entries(&self, entries: &[Entry]) -> Result<()> { - self.par_process_entries(entries) - } - - pub fn first_err(results: &[Result<()>]) -> Result<()> { - for r in results { - r.clone()?; - } - Ok(()) - } - - fn ignore_program_errors(results: Vec>) -> Vec> { - results - .into_iter() - .map(|result| match result { - // Entries that result in a ProgramError are still valid and are written in the - // ledger so map them to an ok return value - Err(BankError::ProgramError(index, err)) => { - info!("program error {:?}, {:?}", index, err); - inc_new_counter_info!("bank-ignore_program_err", 1); - Ok(()) - } - _ => result, - }) - .collect() - } - - fn par_execute_entries(&self, entries: &[(&Entry, Vec>)]) -> Result<()> { - inc_new_counter_info!("bank-par_execute_entries-count", entries.len()); - let results: Vec> = entries - .into_par_iter() - .map(|(e, lock_results)| { - let old_results = self.load_execute_and_commit_transactions( - &e.transactions, - lock_results.to_vec(), - MAX_ENTRY_IDS, - ); - let results = Bank::ignore_program_errors(old_results); - self.unlock_accounts(&e.transactions, &results); - Self::first_err(&results) - }) - .collect(); - Self::first_err(&results) - } - - /// process entries in parallel - /// 1. In order lock accounts for each entry while the lock succeeds, up to a Tick entry - /// 2. Process the locked group in parallel - /// 3. Register the `Tick` if it's available, goto 1 - pub fn par_process_entries(&self, entries: &[Entry]) -> Result<()> { - // accumulator for entries that can be processed in parallel - let mut mt_group = vec![]; - for entry in entries { - if entry.is_tick() { - // if its a tick, execute the group and register the tick - self.par_execute_entries(&mt_group)?; - self.register_tick(&entry.id); - mt_group = vec![]; - continue; - } - // try to lock the accounts - let lock_results = self.lock_accounts(&entry.transactions); - // if any of the locks error out - // execute the current group - if Self::first_err(&lock_results).is_err() { - self.par_execute_entries(&mt_group)?; - mt_group = vec![]; - //reset the lock and push the entry - self.unlock_accounts(&entry.transactions, &lock_results); - let lock_results = self.lock_accounts(&entry.transactions); - mt_group.push((entry, lock_results)); - } else { - // push the entry to the mt_group - mt_group.push((entry, lock_results)); - } - } - self.par_execute_entries(&mt_group)?; - Ok(()) - } - - /// Process an ordered list of entries, populating a circular buffer "tail" - /// as we go. - fn process_block(&self, entries: &[Entry]) -> Result<()> { - for entry in entries { - self.process_entry(entry)?; - } - - Ok(()) - } - /// Starting from the genesis block, append the provided entries to the ledger verifying them /// along the way. pub fn process_ledger(&mut self, entries: I) -> Result<(u64, Hash)> where I: IntoIterator, { - let mut last_entry_id = self.last_id(); + // assumes this function is starting from genesis + let mut last_entry_id = self.root().last_id(); let mut entries_iter = entries.into_iter(); trace!("genesis last_id={}", last_entry_id); @@ -833,14 +390,63 @@ impl Bank { return Err(BankError::LedgerVerificationFailed); } - self.process_block(&block)?; + // issue #2691 + // if slot > 0 && block[0].tick_height % ticks_per_slot == 0 { + // //TODO: EntryTree should provide base slot + // let base = slot - 1; + // { + // info!("freezing from ledger at {}", base); + // let base_state = self.fork(base).expect("base fork"); + // base_state.head().freeze(); + // } + // self.init_fork(slot, &block[0].id, base) + // .expect("init new fork"); + // self.merge_into_root(slot); + // } + + let bank_state = self.root(); + bank_state.process_entries(&block)?; last_entry_id = block.last().unwrap().id; entry_height += block.len() as u64; + + self.leader_scheduler + .write() + .unwrap() + .update_tick_height(bank_state.tick_height(), &self); } Ok((entry_height, last_entry_id)) } + #[must_use] + pub fn process_and_record_transactions( + &self, + txs: &[Transaction], + poh: Option<&PohRecorder>, + ) -> Result>> { + let sub = self.subscriptions.read().unwrap(); + self.active_fork() + .process_and_record_transactions(&sub, txs, poh) + } + + /// Process a Transaction. This is used for unit tests and simply calls the vector Bank::process_transactions method. + pub fn process_transaction(&self, tx: &Transaction) -> Result<()> { + let txs = vec![tx.clone()]; + match self.process_transactions(&txs)[0] { + Err(ref e) => { + info!("process_transaction error: {:?}", e); + Err((*e).clone()) + } + Ok(_) => Ok(()), + } + } + + #[must_use] + pub fn process_transactions(&self, txs: &[Transaction]) -> Vec> { + self.process_and_record_transactions(txs, None) + .expect("record skipped") + } + /// Create, sign, and process a Transaction from `keypair` to `to` of /// `n` tokens where `last_id` is the last Entry ID observed by the client. pub fn transfer( @@ -855,48 +461,6 @@ impl Bank { self.process_transaction(&tx).map(|_| signature) } - pub fn read_balance(account: &Account) -> u64 { - // TODO: Re-instate budget_program special case? - /* - if budget_program::check_id(&account.owner) { - return budget_program::get_balance(account); - } - */ - account.tokens - } - /// Each program would need to be able to introspect its own state - /// this is hard-coded to the Budget language - pub fn get_balance(&self, pubkey: &Pubkey) -> u64 { - self.get_account(pubkey) - .map(|x| Self::read_balance(&x)) - .unwrap_or(0) - } - - pub fn get_account(&self, pubkey: &Pubkey) -> Option { - Accounts::load_slow(&[&self.accounts], pubkey) - } - - pub fn transaction_count(&self) -> u64 { - self.accounts.transaction_count() - } - - pub fn get_signature_status(&self, signature: &Signature) -> Option> { - self.status_cache - .read() - .unwrap() - .get_signature_status(signature) - } - - pub fn has_signature(&self, signature: &Signature) -> bool { - self.status_cache.read().unwrap().has_signature(signature) - } - - /// Hash the `accounts` HashMap. This represents a validator's interpretation - /// of the delta of the ledger since the last vote and up to now - pub fn hash_internal_state(&self) -> Hash { - self.accounts.hash_internal_state() - } - pub fn confirmation_time(&self) -> usize { self.confirmation_time.load(Ordering::Relaxed) } @@ -906,51 +470,40 @@ impl Bank { .store(confirmation, Ordering::Relaxed); } - fn send_account_notifications( - &self, - txs: &[Transaction], - res: &[Result<()>], - loaded: &[Result<(InstructionAccounts, InstructionLoaders)>], - ) { - for (i, raccs) in loaded.iter().enumerate() { - if res[i].is_err() || raccs.is_err() { - continue; - } - - let tx = &txs[i]; - let accs = raccs.as_ref().unwrap(); - for (key, account) in tx.account_keys.iter().zip(accs.0.iter()) { - if let Some(ref subs) = *self.subscriptions.read().unwrap() { - subs.check_account(&key, account) - } - } - } - } - #[cfg(test)] fn get_current_leader(&self) -> Option { - let tick_height = self.tick_height(); + let tick_height = self.active_fork().tick_height(); let leader_scheduler = self.leader_scheduler.read().unwrap(); let slot = leader_scheduler.tick_height_to_slot(tick_height); leader_scheduler.get_leader_for_slot(slot) } - pub fn tick_height(&self) -> u64 { - self.last_id_queue.read().unwrap().tick_height - } - - #[cfg(test)] - pub fn last_ids(&self) -> &RwLock { - &self.last_id_queue + /// An active chain is computed from the leaf_slot + /// The base that is a direct descendant of the root and is in the active chain to the leaf + /// is merged into root, and any forks not attached to the new root are purged. + pub fn merge_into_root(&self, leaf_slot: u64) { + //there is only one base, and its the current live fork + self.forks + .write() + .unwrap() + .merge_into_root(forks::ROLLBACK_DEPTH, leaf_slot) + .expect("merge into root"); + let height = self.root().tick_height(); + self.leader_scheduler + .write() + .unwrap() + .update_tick_height(height, &self); } } #[cfg(test)] mod tests { use super::*; + use crate::bank_fork::BankFork; use crate::entry::{next_entries, next_entry, Entry}; use crate::gen_keys::GenKeys; use crate::genesis_block::BOOTSTRAP_LEADER_TOKENS; + use crate::poh_recorder::PohRecorder; use bincode::serialize; use hashbrown::HashSet; use solana_sdk::hash::hash; @@ -969,7 +522,7 @@ mod tests { let (genesis_block, _) = GenesisBlock::new(10_000); let bank = Bank::new(&genesis_block); assert_eq!( - bank.get_balance(&genesis_block.mint_id), + bank.active_fork().get_balance_slow(&genesis_block.mint_id), 10_000 - genesis_block.bootstrap_leader_tokens ); } @@ -983,11 +536,11 @@ mod tests { assert_eq!(genesis_block.bootstrap_leader_tokens, dummy_leader_tokens); let bank = Bank::new(&genesis_block); assert_eq!( - bank.get_balance(&genesis_block.mint_id), + bank.active_fork().get_balance_slow(&genesis_block.mint_id), 10_000 - dummy_leader_tokens ); assert_eq!( - bank.get_balance(&dummy_leader_id), + bank.active_fork().get_balance_slow(&dummy_leader_id), dummy_leader_tokens - 1 /* 1 token goes to the vote account associated with dummy_leader_tokens */ ); } @@ -997,16 +550,16 @@ mod tests { let (genesis_block, mint_keypair) = GenesisBlock::new(10_000); let pubkey = Keypair::new().pubkey(); let bank = Bank::new(&genesis_block); - assert_eq!(bank.last_id(), genesis_block.last_id()); + assert_eq!(bank.active_fork().last_id(), genesis_block.last_id()); bank.transfer(1_000, &mint_keypair, pubkey, genesis_block.last_id()) .unwrap(); - assert_eq!(bank.get_balance(&pubkey), 1_000); + assert_eq!(bank.active_fork().get_balance_slow(&pubkey), 1_000); bank.transfer(500, &mint_keypair, pubkey, genesis_block.last_id()) .unwrap(); - assert_eq!(bank.get_balance(&pubkey), 1_500); - assert_eq!(bank.transaction_count(), 2); + assert_eq!(bank.active_fork().get_balance_slow(&pubkey), 1_500); + assert_eq!(bank.active_fork().transaction_count(), 2); } #[test] @@ -1015,7 +568,7 @@ mod tests { let key1 = Keypair::new().pubkey(); let key2 = Keypair::new().pubkey(); let bank = Bank::new(&genesis_block); - assert_eq!(bank.last_id(), genesis_block.last_id()); + assert_eq!(bank.active_fork().last_id(), genesis_block.last_id()); let t1 = SystemTransaction::new_move(&mint_keypair, key1, 1, genesis_block.last_id(), 0); let t2 = SystemTransaction::new_move(&mint_keypair, key2, 1, genesis_block.last_id(), 0); @@ -1023,13 +576,19 @@ mod tests { assert_eq!(res.len(), 2); assert_eq!(res[0], Ok(())); assert_eq!(res[1], Err(BankError::AccountInUse)); - assert_eq!(bank.get_balance(&mint_keypair.pubkey()), 0); - assert_eq!(bank.get_balance(&key1), 1); - assert_eq!(bank.get_balance(&key2), 0); - assert_eq!(bank.get_signature_status(&t1.signatures[0]), Some(Ok(()))); + assert_eq!( + bank.active_fork().get_balance_slow(&mint_keypair.pubkey()), + 0 + ); + assert_eq!(bank.active_fork().get_balance_slow(&key1), 1); + assert_eq!(bank.active_fork().get_balance_slow(&key2), 0); + assert_eq!( + bank.active_fork().get_signature_status(&t1.signatures[0]), + Some(Ok(())) + ); // TODO: Transactions that fail to pay a fee could be dropped silently assert_eq!( - bank.get_signature_status(&t2.signatures[0]), + bank.active_fork().get_signature_status(&t2.signatures[0]), Some(Err(BankError::AccountInUse)) ); } @@ -1071,11 +630,14 @@ mod tests { ProgramError::ResultWithNegativeTokens )) ); - assert_eq!(bank.get_balance(&mint_keypair.pubkey()), 1); - assert_eq!(bank.get_balance(&key1), 0); - assert_eq!(bank.get_balance(&key2), 0); assert_eq!( - bank.get_signature_status(&t1.signatures[0]), + bank.active_fork().get_balance_slow(&mint_keypair.pubkey()), + 1 + ); + assert_eq!(bank.active_fork().get_balance_slow(&key1), 0); + assert_eq!(bank.active_fork().get_balance_slow(&key2), 0); + assert_eq!( + bank.active_fork().get_signature_status(&t1.signatures[0]), Some(Err(BankError::ProgramError( 1, ProgramError::ResultWithNegativeTokens @@ -1098,10 +660,16 @@ mod tests { let res = bank.process_transactions(&vec![t1.clone()]); assert_eq!(res.len(), 1); assert_eq!(res[0], Ok(())); - assert_eq!(bank.get_balance(&mint_keypair.pubkey()), 0); - assert_eq!(bank.get_balance(&key1), 1); - assert_eq!(bank.get_balance(&key2), 1); - assert_eq!(bank.get_signature_status(&t1.signatures[0]), Some(Ok(()))); + assert_eq!( + bank.active_fork().get_balance_slow(&mint_keypair.pubkey()), + 0 + ); + assert_eq!(bank.active_fork().get_balance_slow(&key1), 1); + assert_eq!(bank.active_fork().get_balance_slow(&key2), 1); + assert_eq!( + bank.active_fork().get_signature_status(&t1.signatures[0]), + Some(Ok(())) + ); } // TODO: This test demonstrates that fees are not paid when a program fails. @@ -1121,14 +689,14 @@ mod tests { 1, ); let signature = tx.signatures[0]; - assert!(!bank.has_signature(&signature)); + assert!(!bank.active_fork().head().has_signature(&signature)); let res = bank.process_transaction(&tx); // Result failed, but signature is registered assert!(res.is_err()); - assert!(bank.has_signature(&signature)); + assert!(bank.active_fork().head().has_signature(&signature)); assert_matches!( - bank.get_signature_status(&signature), + bank.active_fork().get_signature_status(&signature), Some(Err(BankError::ProgramError( 0, ProgramError::ResultWithNegativeTokens @@ -1136,10 +704,10 @@ mod tests { ); // The tokens didn't move, but the from address paid the transaction fee. - assert_eq!(bank.get_balance(&dest.pubkey()), 0); + assert_eq!(bank.active_fork().get_balance_slow(&dest.pubkey()), 0); // BUG: This should be the original balance minus the transaction fee. - //assert_eq!(bank.get_balance(&mint_keypair.pubkey()), 0); + //assert_eq!(bank.active_fork().get_balance_slow(&mint_keypair.pubkey()), 0); } #[test] @@ -1151,7 +719,7 @@ mod tests { bank.transfer(1, &keypair, mint_keypair.pubkey(), genesis_block.last_id()), Err(BankError::AccountNotFound) ); - assert_eq!(bank.transaction_count(), 0); + assert_eq!(bank.active_fork().transaction_count(), 0); } #[test] @@ -1161,8 +729,8 @@ mod tests { let pubkey = Keypair::new().pubkey(); bank.transfer(1_000, &mint_keypair, pubkey, genesis_block.last_id()) .unwrap(); - assert_eq!(bank.transaction_count(), 1); - assert_eq!(bank.get_balance(&pubkey), 1_000); + assert_eq!(bank.active_fork().transaction_count(), 1); + assert_eq!(bank.active_fork().get_balance_slow(&pubkey), 1_000); assert_matches!( bank.transfer(10_001, &mint_keypair, pubkey, genesis_block.last_id()), Err(BankError::ProgramError( @@ -1170,11 +738,11 @@ mod tests { ProgramError::ResultWithNegativeTokens )) ); - assert_eq!(bank.transaction_count(), 1); + assert_eq!(bank.active_fork().transaction_count(), 1); let mint_pubkey = mint_keypair.pubkey(); - assert_eq!(bank.get_balance(&mint_pubkey), 10_000); - assert_eq!(bank.get_balance(&pubkey), 1_000); + assert_eq!(bank.active_fork().get_balance_slow(&mint_pubkey), 10_000); + assert_eq!(bank.active_fork().get_balance_slow(&pubkey), 1_000); } #[test] @@ -1184,7 +752,7 @@ mod tests { let pubkey = Keypair::new().pubkey(); bank.transfer(500, &mint_keypair, pubkey, genesis_block.last_id()) .unwrap(); - assert_eq!(bank.get_balance(&pubkey), 500); + assert_eq!(bank.active_fork().get_balance_slow(&pubkey), 500); } #[test] @@ -1211,7 +779,7 @@ mod tests { assert!(results[1].is_err()); // Assert bad transactions aren't counted. - assert_eq!(bank.transaction_count(), 1); + assert_eq!(bank.active_fork().transaction_count(), 1); } #[test] @@ -1229,7 +797,7 @@ mod tests { ); // Now ensure the TX is accepted despite pointing to the ID of an empty entry. - bank.process_entries(&[entry]).unwrap(); + bank.active_fork().process_entries(&[entry]).unwrap(); assert_eq!(bank.process_transaction(&tx), Ok(())); } @@ -1241,8 +809,11 @@ mod tests { let (genesis_block, _) = GenesisBlock::new_with_leader(5, dummy_leader_id, dummy_leader_tokens); let bank = Bank::new(&genesis_block); - assert_eq!(bank.get_balance(&genesis_block.mint_id), 3); - assert_eq!(bank.get_balance(&dummy_leader_id), 1); + assert_eq!( + bank.active_fork().get_balance_slow(&genesis_block.mint_id), + 3 + ); + assert_eq!(bank.active_fork().get_balance_slow(&dummy_leader_id), 1); assert_eq!(bank.get_current_leader(), Some(dummy_leader_id)); } @@ -1342,23 +913,30 @@ mod tests { } #[test] + #[ignore] // issue #2691 fn test_process_ledger_simple() { let (genesis_block, mint_keypair, ledger) = create_sample_ledger(100 + BOOTSTRAP_LEADER_TOKENS, 3); let mut bank = Bank::default(); bank.add_builtin_programs(); bank.process_genesis_block(&genesis_block); - assert_eq!(bank.tick_height(), 0); - assert_eq!(bank.get_balance(&mint_keypair.pubkey()), 100); + assert_eq!(bank.active_fork().tick_height(), 0); + assert_eq!( + bank.active_fork().get_balance_slow(&mint_keypair.pubkey()), + 100 + ); assert_eq!( bank.get_current_leader(), Some(genesis_block.bootstrap_leader_id) ); let (ledger_height, last_id) = bank.process_ledger(ledger).unwrap(); - assert_eq!(bank.get_balance(&mint_keypair.pubkey()), 100 - 3); + assert_eq!( + bank.active_fork().get_balance_slow(&mint_keypair.pubkey()), + 100 - 3 + ); assert_eq!(ledger_height, 8); - assert_eq!(bank.tick_height(), 1); - assert_eq!(bank.last_id(), last_id); + assert_eq!(bank.active_fork().tick_height(), 2); + assert_eq!(bank.active_fork().last_id(), last_id); } #[test] @@ -1386,28 +964,27 @@ mod tests { &keypairs, ); - let mut bank0 = Bank::default(); - bank0.add_builtin_programs(); - bank0.process_genesis_block(&genesis_block); + let mut bank0 = Bank::new(&genesis_block); bank0.process_ledger(ledger0).unwrap(); - let mut bank1 = Bank::default(); - bank1.add_builtin_programs(); - bank1.process_genesis_block(&genesis_block); + let mut bank1 = Bank::new(&genesis_block); bank1.process_ledger(ledger1).unwrap(); - let initial_state = bank0.hash_internal_state(); + let initial_state = bank0.active_fork().hash_internal_state(); - assert_eq!(bank1.hash_internal_state(), initial_state); + assert_eq!(bank1.active_fork().hash_internal_state(), initial_state); let pubkey = keypairs[0].pubkey(); bank0 - .transfer(1_000, &mint_keypair, pubkey, bank0.last_id()) + .transfer(1_000, &mint_keypair, pubkey, bank0.active_fork().last_id()) .unwrap(); - assert_ne!(bank0.hash_internal_state(), initial_state); + assert_ne!(bank0.active_fork().hash_internal_state(), initial_state); bank1 - .transfer(1_000, &mint_keypair, pubkey, bank1.last_id()) + .transfer(1_000, &mint_keypair, pubkey, bank1.active_fork().last_id()) .unwrap(); - assert_eq!(bank0.hash_internal_state(), bank1.hash_internal_state()); + assert_eq!( + bank0.active_fork().hash_internal_state(), + bank1.active_fork().hash_internal_state() + ); } #[test] fn test_confirmation_time() { @@ -1417,87 +994,14 @@ mod tests { assert_eq!(def_bank.confirmation_time(), 90); } #[test] - fn test_interleaving_locks() { - let (genesis_block, mint_keypair) = GenesisBlock::new(3 + BOOTSTRAP_LEADER_TOKENS); - let bank = Bank::new(&genesis_block); - let alice = Keypair::new(); - let bob = Keypair::new(); - - let tx1 = SystemTransaction::new_account( - &mint_keypair, - alice.pubkey(), - 1, - genesis_block.last_id(), - 0, - ); - let pay_alice = vec![tx1]; - - let lock_result = bank.lock_accounts(&pay_alice); - let results_alice = - bank.load_execute_and_commit_transactions(&pay_alice, lock_result, MAX_ENTRY_IDS); - assert_eq!(results_alice[0], Ok(())); - - // try executing an interleaved transfer twice - assert_eq!( - bank.transfer(1, &mint_keypair, bob.pubkey(), genesis_block.last_id()), - Err(BankError::AccountInUse) - ); - // the second time should fail as well - // this verifies that `unlock_accounts` doesn't unlock `AccountInUse` accounts - assert_eq!( - bank.transfer(1, &mint_keypair, bob.pubkey(), genesis_block.last_id()), - Err(BankError::AccountInUse) - ); - - bank.unlock_accounts(&pay_alice, &results_alice); - - assert_matches!( - bank.transfer(2, &mint_keypair, bob.pubkey(), genesis_block.last_id()), - Ok(_) - ); - } - - #[test] - fn test_first_err() { - assert_eq!(Bank::first_err(&[Ok(())]), Ok(())); - assert_eq!( - Bank::first_err(&[Ok(()), Err(BankError::DuplicateSignature)]), - Err(BankError::DuplicateSignature) - ); - assert_eq!( - Bank::first_err(&[ - Ok(()), - Err(BankError::DuplicateSignature), - Err(BankError::AccountInUse) - ]), - Err(BankError::DuplicateSignature) - ); - assert_eq!( - Bank::first_err(&[ - Ok(()), - Err(BankError::AccountInUse), - Err(BankError::DuplicateSignature) - ]), - Err(BankError::AccountInUse) - ); - assert_eq!( - Bank::first_err(&[ - Err(BankError::AccountInUse), - Ok(()), - Err(BankError::DuplicateSignature) - ]), - Err(BankError::AccountInUse) - ); - } - #[test] fn test_par_process_entries_tick() { let (genesis_block, _mint_keypair) = GenesisBlock::new(1000); let bank = Bank::new(&genesis_block); // ensure bank can process a tick let tick = next_entry(&genesis_block.last_id(), 1, vec![]); - assert_eq!(bank.par_process_entries(&[tick.clone()]), Ok(())); - assert_eq!(bank.last_id(), tick.id); + assert_eq!(bank.active_fork().process_entries(&[tick.clone()]), Ok(())); + assert_eq!(bank.active_fork().last_id(), tick.id); } #[test] fn test_par_process_entries_2_entries_collision() { @@ -1506,19 +1010,32 @@ mod tests { let keypair1 = Keypair::new(); let keypair2 = Keypair::new(); - let last_id = bank.last_id(); + let last_id = bank.active_fork().last_id(); // ensure bank can process 2 entries that have a common account and no tick is registered - let tx = - SystemTransaction::new_account(&mint_keypair, keypair1.pubkey(), 2, bank.last_id(), 0); + let tx = SystemTransaction::new_account( + &mint_keypair, + keypair1.pubkey(), + 2, + bank.active_fork().last_id(), + 0, + ); let entry_1 = next_entry(&last_id, 1, vec![tx]); - let tx = - SystemTransaction::new_account(&mint_keypair, keypair2.pubkey(), 2, bank.last_id(), 0); + let tx = SystemTransaction::new_account( + &mint_keypair, + keypair2.pubkey(), + 2, + bank.active_fork().last_id(), + 0, + ); let entry_2 = next_entry(&entry_1.id, 1, vec![tx]); - assert_eq!(bank.par_process_entries(&[entry_1, entry_2]), Ok(())); - assert_eq!(bank.get_balance(&keypair1.pubkey()), 2); - assert_eq!(bank.get_balance(&keypair2.pubkey()), 2); - assert_eq!(bank.last_id(), last_id); + assert_eq!( + bank.active_fork().process_entries(&[entry_1, entry_2]), + Ok(()) + ); + assert_eq!(bank.active_fork().get_balance_slow(&keypair1.pubkey()), 2); + assert_eq!(bank.active_fork().get_balance_slow(&keypair2.pubkey()), 2); + assert_eq!(bank.active_fork().last_id(), last_id); } #[test] fn test_par_process_entries_2_txes_collision() { @@ -1530,23 +1047,33 @@ mod tests { // fund: put 4 in each of 1 and 2 assert_matches!( - bank.transfer(4, &mint_keypair, keypair1.pubkey(), bank.last_id()), + bank.transfer( + 4, + &mint_keypair, + keypair1.pubkey(), + bank.active_fork().last_id() + ), Ok(_) ); assert_matches!( - bank.transfer(4, &mint_keypair, keypair2.pubkey(), bank.last_id()), + bank.transfer( + 4, + &mint_keypair, + keypair2.pubkey(), + bank.active_fork().last_id() + ), Ok(_) ); // construct an Entry whose 2nd transaction would cause a lock conflict with previous entry let entry_1_to_mint = next_entry( - &bank.last_id(), + &bank.active_fork().last_id(), 1, vec![SystemTransaction::new_account( &keypair1, mint_keypair.pubkey(), 1, - bank.last_id(), + bank.active_fork().last_id(), 0, )], ); @@ -1555,25 +1082,32 @@ mod tests { &entry_1_to_mint.id, 1, vec![ - SystemTransaction::new_account(&keypair2, keypair3.pubkey(), 2, bank.last_id(), 0), // should be fine + SystemTransaction::new_account( + &keypair2, + keypair3.pubkey(), + 2, + bank.active_fork().last_id(), + 0, + ), // should be fine SystemTransaction::new_account( &keypair1, mint_keypair.pubkey(), 2, - bank.last_id(), + bank.active_fork().last_id(), 0, ), // will collide ], ); assert_eq!( - bank.par_process_entries(&[entry_1_to_mint, entry_2_to_3_mint_to_1]), + bank.active_fork() + .process_entries(&[entry_1_to_mint, entry_2_to_3_mint_to_1]), Ok(()) ); - assert_eq!(bank.get_balance(&keypair1.pubkey()), 1); - assert_eq!(bank.get_balance(&keypair2.pubkey()), 2); - assert_eq!(bank.get_balance(&keypair3.pubkey()), 2); + assert_eq!(bank.active_fork().get_balance_slow(&keypair1.pubkey()), 1); + assert_eq!(bank.active_fork().get_balance_slow(&keypair2.pubkey()), 2); + assert_eq!(bank.active_fork().get_balance_slow(&keypair3.pubkey()), 2); } #[test] fn test_par_process_entries_2_entries_par() { @@ -1585,23 +1119,48 @@ mod tests { let keypair4 = Keypair::new(); //load accounts - let tx = - SystemTransaction::new_account(&mint_keypair, keypair1.pubkey(), 1, bank.last_id(), 0); + let tx = SystemTransaction::new_account( + &mint_keypair, + keypair1.pubkey(), + 1, + bank.active_fork().last_id(), + 0, + ); assert_eq!(bank.process_transaction(&tx), Ok(())); - let tx = - SystemTransaction::new_account(&mint_keypair, keypair2.pubkey(), 1, bank.last_id(), 0); + let tx = SystemTransaction::new_account( + &mint_keypair, + keypair2.pubkey(), + 1, + bank.active_fork().last_id(), + 0, + ); assert_eq!(bank.process_transaction(&tx), Ok(())); // ensure bank can process 2 entries that do not have a common account and no tick is registered - let last_id = bank.last_id(); - let tx = SystemTransaction::new_account(&keypair1, keypair3.pubkey(), 1, bank.last_id(), 0); + let last_id = bank.active_fork().last_id(); + let tx = SystemTransaction::new_account( + &keypair1, + keypair3.pubkey(), + 1, + bank.active_fork().last_id(), + 0, + ); let entry_1 = next_entry(&last_id, 1, vec![tx]); - let tx = SystemTransaction::new_account(&keypair2, keypair4.pubkey(), 1, bank.last_id(), 0); + let tx = SystemTransaction::new_account( + &keypair2, + keypair4.pubkey(), + 1, + bank.active_fork().last_id(), + 0, + ); let entry_2 = next_entry(&entry_1.id, 1, vec![tx]); - assert_eq!(bank.par_process_entries(&[entry_1, entry_2]), Ok(())); - assert_eq!(bank.get_balance(&keypair3.pubkey()), 1); - assert_eq!(bank.get_balance(&keypair4.pubkey()), 1); - assert_eq!(bank.last_id(), last_id); + assert_eq!( + bank.active_fork().process_entries(&[entry_1, entry_2]), + Ok(()) + ); + assert_eq!(bank.active_fork().get_balance_slow(&keypair3.pubkey()), 1); + assert_eq!(bank.active_fork().get_balance_slow(&keypair4.pubkey()), 1); + assert_eq!(bank.active_fork().last_id(), last_id); } #[test] fn test_par_process_entries_2_entries_tick() { @@ -1613,33 +1172,50 @@ mod tests { let keypair4 = Keypair::new(); //load accounts - let tx = - SystemTransaction::new_account(&mint_keypair, keypair1.pubkey(), 1, bank.last_id(), 0); + let tx = SystemTransaction::new_account( + &mint_keypair, + keypair1.pubkey(), + 1, + bank.active_fork().last_id(), + 0, + ); assert_eq!(bank.process_transaction(&tx), Ok(())); - let tx = - SystemTransaction::new_account(&mint_keypair, keypair2.pubkey(), 1, bank.last_id(), 0); + let tx = SystemTransaction::new_account( + &mint_keypair, + keypair2.pubkey(), + 1, + bank.active_fork().last_id(), + 0, + ); assert_eq!(bank.process_transaction(&tx), Ok(())); - let last_id = bank.last_id(); + let last_id = bank.active_fork().last_id(); // ensure bank can process 2 entries that do not have a common account and tick is registered - let tx = SystemTransaction::new_account(&keypair2, keypair3.pubkey(), 1, bank.last_id(), 0); + let tx = SystemTransaction::new_account( + &keypair2, + keypair3.pubkey(), + 1, + bank.active_fork().last_id(), + 0, + ); let entry_1 = next_entry(&last_id, 1, vec![tx]); let tick = next_entry(&entry_1.id, 1, vec![]); let tx = SystemTransaction::new_account(&keypair1, keypair4.pubkey(), 1, tick.id, 0); let entry_2 = next_entry(&tick.id, 1, vec![tx]); assert_eq!( - bank.par_process_entries(&[entry_1.clone(), tick.clone(), entry_2.clone()]), + bank.active_fork() + .process_entries(&[entry_1.clone(), tick.clone(), entry_2.clone()]), Ok(()) ); - assert_eq!(bank.get_balance(&keypair3.pubkey()), 1); - assert_eq!(bank.get_balance(&keypair4.pubkey()), 1); - assert_eq!(bank.last_id(), tick.id); + assert_eq!(bank.active_fork().get_balance_slow(&keypair3.pubkey()), 1); + assert_eq!(bank.active_fork().get_balance_slow(&keypair4.pubkey()), 1); + assert_eq!(bank.active_fork().last_id(), tick.id); // ensure that an error is returned for an empty account (keypair2) let tx = SystemTransaction::new_account(&keypair2, keypair3.pubkey(), 1, tick.id, 0); let entry_3 = next_entry(&entry_2.id, 1, vec![tx]); assert_eq!( - bank.par_process_entries(&[entry_3]), + bank.active_fork().process_entries(&[entry_3]), Err(BankError::AccountNotFound) ); } @@ -1710,8 +1286,12 @@ mod tests { let (genesis_block, mint_keypair) = GenesisBlock::new(10_000); let bank = Arc::new(Bank::new(&genesis_block)); let (entry_sender, entry_receiver) = channel(); - let poh_recorder = - PohRecorder::new(bank.clone(), entry_sender, bank.last_id(), std::u64::MAX); + let poh_recorder = PohRecorder::new( + bank.clone(), + entry_sender, + bank.active_fork().last_id(), + std::u64::MAX, + ); let pubkey = Keypair::new().pubkey(); let transactions = vec![ @@ -1720,8 +1300,7 @@ mod tests { ]; let mut results = vec![Ok(()), Ok(())]; - bank.record_transactions(&transactions, &results, &poh_recorder) - .unwrap(); + BankFork::record_transactions(&transactions, &results, &poh_recorder).unwrap(); let entries = entry_receiver.recv().unwrap(); assert_eq!(entries[0].transactions.len(), transactions.len()); @@ -1730,42 +1309,17 @@ mod tests { 1, ProgramError::ResultWithNegativeTokens, )); - bank.record_transactions(&transactions, &results, &poh_recorder) - .unwrap(); + BankFork::record_transactions(&transactions, &results, &poh_recorder).unwrap(); let entries = entry_receiver.recv().unwrap(); assert_eq!(entries[0].transactions.len(), transactions.len()); // Other BankErrors should not be recorded results[0] = Err(BankError::AccountNotFound); - bank.record_transactions(&transactions, &results, &poh_recorder) - .unwrap(); + BankFork::record_transactions(&transactions, &results, &poh_recorder).unwrap(); let entries = entry_receiver.recv().unwrap(); assert_eq!(entries[0].transactions.len(), transactions.len() - 1); } - #[test] - fn test_bank_ignore_program_errors() { - let expected_results = vec![Ok(()), Ok(())]; - let results = vec![Ok(()), Ok(())]; - let updated_results = Bank::ignore_program_errors(results); - assert_eq!(updated_results, expected_results); - - let results = vec![ - Err(BankError::ProgramError( - 1, - ProgramError::ResultWithNegativeTokens, - )), - Ok(()), - ]; - let updated_results = Bank::ignore_program_errors(results); - assert_eq!(updated_results, expected_results); - - // Other BankErrors should not be ignored - let results = vec![Err(BankError::AccountNotFound), Ok(())]; - let updated_results = Bank::ignore_program_errors(results); - assert_ne!(updated_results, expected_results); - } - #[test] fn test_bank_storage() { solana_logger::setup(); @@ -1781,7 +1335,7 @@ mod tests { let x2 = x * 2; let storage_last_id = hash(&[x2]); - bank.register_tick(&last_id); + bank.active_fork().register_tick(&last_id); bank.transfer(10, &alice, jill.pubkey(), last_id).unwrap(); @@ -1831,11 +1385,11 @@ mod tests { let mut poh_recorder = PohRecorder::new( bank.clone(), entry_sender, - bank.last_id(), - bank.tick_height() + 1, + bank.active_fork().last_id(), + bank.active_fork().tick_height() + 1, ); - bank.process_and_record_transactions(&transactions, &poh_recorder) + bank.process_and_record_transactions(&transactions, Some(&poh_recorder)) .unwrap(); poh_recorder.tick().unwrap(); @@ -1846,7 +1400,7 @@ mod tests { for entry in entries { if !entry.is_tick() { assert_eq!(entry.transactions.len(), transactions.len()); - assert_eq!(bank.get_balance(&pubkey), 1); + assert_eq!(bank.active_fork().get_balance_slow(&pubkey), 1); } else { need_tick = false; } @@ -1862,11 +1416,11 @@ mod tests { )]; assert_eq!( - bank.process_and_record_transactions(&transactions, &poh_recorder), + bank.process_and_record_transactions(&transactions, Some(&poh_recorder)), Err(BankError::MaxHeightReached) ); - assert_eq!(bank.get_balance(&pubkey), 1); + assert_eq!(bank.active_fork().get_balance_slow(&pubkey), 1); } #[test] fn test_bank_pay_to_self() { @@ -1876,11 +1430,11 @@ mod tests { bank.transfer(1, &mint_keypair, key1.pubkey(), genesis_block.last_id()) .unwrap(); - assert_eq!(bank.get_balance(&key1.pubkey()), 1); + assert_eq!(bank.active_fork().get_balance_slow(&key1.pubkey()), 1); let tx = SystemTransaction::new_move(&key1, key1.pubkey(), 1, genesis_block.last_id(), 0); let res = bank.process_transactions(&vec![tx.clone()]); assert_eq!(res.len(), 1); - assert_eq!(bank.get_balance(&key1.pubkey()), 1); + assert_eq!(bank.active_fork().get_balance_slow(&key1.pubkey()), 1); res[0].clone().unwrap_err(); } } diff --git a/src/bank_delta.rs b/src/bank_delta.rs new file mode 100644 index 00000000000000..e27d0b991800d7 --- /dev/null +++ b/src/bank_delta.rs @@ -0,0 +1,344 @@ +use crate::accounts::{Accounts, ErrorCounters, InstructionAccounts, InstructionLoaders}; +use crate::bank::{BankError, BankSubscriptions, Result}; +use crate::counter::Counter; +use crate::last_id_queue::LastIdQueue; +use crate::rpc_pubsub::RpcSubscriptions; +use crate::status_cache::StatusCache; +use log::Level; +use solana_sdk::account::Account; +use solana_sdk::hash::Hash; +use solana_sdk::pubkey::Pubkey; +use solana_sdk::signature::Signature; +use solana_sdk::timing::duration_as_us; +use solana_sdk::transaction::Transaction; +use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering}; +use std::sync::{Arc, RwLock}; +use std::time::Instant; + +type BankStatusCache = StatusCache; + +#[derive(Default)] +pub struct BankDelta { + /// accounts database + pub accounts: Accounts, + /// entries + last_id_queue: RwLock, + /// status cache + status_cache: RwLock, + frozen: AtomicBool, + fork_id: AtomicUsize, +} + +impl std::fmt::Debug for BankDelta { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + write!(f, "BankDelta {{ fork_id: {} }}", self.fork_id()) + } +} + +impl BankDelta { + // last_id id is used by the status_cache to filter duplicate signatures + pub fn new(fork_id: u64, last_id: &Hash) -> Self { + BankDelta { + accounts: Accounts::default(), + last_id_queue: RwLock::new(LastIdQueue::default()), + status_cache: RwLock::new(StatusCache::new(last_id)), + frozen: AtomicBool::new(false), + fork_id: AtomicUsize::new(fork_id as usize), + } + } + /// Create an Bank using a deposit. + pub fn new_from_accounts(fork: u64, accounts: &[(Pubkey, Account)], last_id: &Hash) -> Self { + let bank_state = BankDelta::new(fork, last_id); + for (to, account) in accounts { + bank_state.accounts.store_slow(false, &to, &account); + } + bank_state + } + pub fn store_slow(&self, purge: bool, pubkey: &Pubkey, account: &Account) { + assert!(!self.frozen()); + self.accounts.store_slow(purge, pubkey, account) + } + + /// Forget all signatures. Useful for benchmarking. + pub fn clear_signatures(&self) { + assert!(!self.frozen()); + self.status_cache.write().unwrap().clear(); + } + + /// Return the last entry ID registered. + pub fn last_id(&self) -> Hash { + self.last_id_queue + .read() + .unwrap() + .last_id + .expect("no last_id has been set") + } + + pub fn transaction_count(&self) -> u64 { + self.accounts.transaction_count() + } + pub fn freeze(&self) { + info!( + "delta {} frozen at {}", + self.fork_id.load(Ordering::Relaxed), + self.last_id_queue.read().unwrap().tick_height + ); + + self.frozen.store(true, Ordering::Relaxed); + } + pub fn frozen(&self) -> bool { + self.frozen.load(Ordering::Relaxed) + } + + /// Looks through a list of tick heights and stakes, and finds the latest + /// tick that has achieved finality + pub fn get_confirmation_timestamp( + &self, + ticks_and_stakes: &mut [(u64, u64)], + supermajority_stake: u64, + ) -> Option { + let last_id_queue = self.last_id_queue.read().unwrap(); + last_id_queue.get_confirmation_timestamp(ticks_and_stakes, supermajority_stake) + } + pub fn get_signature_status(&self, signature: &Signature) -> Option> { + self.status_cache + .read() + .unwrap() + .get_signature_status(signature) + } + pub fn has_signature(&self, signature: &Signature) -> bool { + self.status_cache.read().unwrap().has_signature(signature) + } + + pub fn tick_height(&self) -> u64 { + self.last_id_queue.read().unwrap().tick_height + } + + /// Tell the bank which Entry IDs exist on the ledger. This function + /// assumes subsequent calls correspond to later entries, and will boot + /// the oldest ones once its internal cache is full. Once boot, the + /// bank will reject transactions using that `last_id`. + pub fn register_tick(&self, last_id: &Hash) { + assert!(!self.frozen()); + let mut last_id_queue = self.last_id_queue.write().unwrap(); + inc_new_counter_info!("bank-register_tick-registered", 1); + last_id_queue.register_tick(last_id) + } + pub fn lock_accounts(&self, txs: &[Transaction]) -> Vec> { + self.accounts.lock_accounts(txs) + } + pub fn unlock_accounts(&self, txs: &[Transaction], results: &[Result<()>]) { + self.accounts.unlock_accounts(txs, results) + } + pub fn check_age( + &self, + txs: &[Transaction], + lock_results: &[Result<()>], + max_age: usize, + error_counters: &mut ErrorCounters, + ) -> Vec> { + let last_ids = self.last_id_queue.read().unwrap(); + txs.iter() + .zip(lock_results.iter()) + .map(|(tx, lock_res)| { + if lock_res.is_ok() && !last_ids.check_entry_id_age(tx.last_id, max_age) { + error_counters.reserve_last_id += 1; + Err(BankError::LastIdNotFound) + } else { + lock_res.clone() + } + }) + .collect() + } + pub fn check_signatures( + &self, + txs: &[Transaction], + lock_results: Vec>, + error_counters: &mut ErrorCounters, + ) -> Vec> { + let status_cache = self.status_cache.read().unwrap(); + txs.iter() + .zip(lock_results.into_iter()) + .map(|(tx, lock_res)| { + if lock_res.is_ok() && status_cache.has_signature(&tx.signatures[0]) { + error_counters.duplicate_signature += 1; + Err(BankError::DuplicateSignature) + } else { + lock_res + } + }) + .collect() + } + + pub fn first_err(results: &[Result<()>]) -> Result<()> { + for r in results { + r.clone()?; + } + Ok(()) + } + + pub fn commit_transactions( + &self, + subscritpions: &Option>, + txs: &[Transaction], + loaded_accounts: &[Result<(InstructionAccounts, InstructionLoaders)>], + executed: &[Result<()>], + ) { + assert!(!self.frozen()); + let now = Instant::now(); + self.accounts + .store_accounts(true, txs, executed, loaded_accounts); + + // Check account subscriptions and send notifications + if let Some(subs) = subscritpions { + Self::send_account_notifications(subs, txs, executed, loaded_accounts); + } + + // once committed there is no way to unroll + let write_elapsed = now.elapsed(); + debug!( + "store: {}us txs_len={}", + duration_as_us(&write_elapsed), + txs.len(), + ); + self.update_transaction_statuses(txs, &executed); + if let Some(subs) = subscritpions { + Self::update_subscriptions(subs, txs, &executed); + } + } + fn send_account_notifications( + subscriptions: &RpcSubscriptions, + txs: &[Transaction], + res: &[Result<()>], + loaded: &[Result<(InstructionAccounts, InstructionLoaders)>], + ) { + for (i, raccs) in loaded.iter().enumerate() { + if res[i].is_err() || raccs.is_err() { + continue; + } + + let tx = &txs[i]; + let accs = raccs.as_ref().unwrap(); + for (key, account) in tx.account_keys.iter().zip(accs.0.iter()) { + subscriptions.check_account(&key, account); + } + } + } + fn update_subscriptions( + subscriptions: &RpcSubscriptions, + txs: &[Transaction], + res: &[Result<()>], + ) { + for (i, tx) in txs.iter().enumerate() { + subscriptions.check_signature(&tx.signatures[0], &res[i]); + } + } + + fn update_transaction_statuses(&self, txs: &[Transaction], res: &[Result<()>]) { + assert!(!self.frozen()); + let mut status_cache = self.status_cache.write().unwrap(); + for (i, tx) in txs.iter().enumerate() { + match &res[i] { + Ok(_) => status_cache.add(&tx.signatures[0]), + Err(BankError::LastIdNotFound) => (), + Err(BankError::DuplicateSignature) => (), + Err(BankError::AccountNotFound) => (), + Err(e) => { + status_cache.add(&tx.signatures[0]); + status_cache.save_failure_status(&tx.signatures[0], e.clone()); + } + } + } + } + + pub fn hash_internal_state(&self) -> Hash { + self.accounts.hash_internal_state() + } + pub fn set_genesis_last_id(&self, last_id: &Hash) { + assert!(!self.frozen()); + self.last_id_queue.write().unwrap().genesis_last_id(last_id) + } + + pub fn fork_id(&self) -> u64 { + self.fork_id.load(Ordering::Relaxed) as u64 + } + /// create a new fork for the bank state + pub fn fork(&self, fork_id: u64, last_id: &Hash) -> Self { + Self { + accounts: Accounts::default(), + last_id_queue: RwLock::new(self.last_id_queue.read().unwrap().fork()), + status_cache: RwLock::new(StatusCache::new(last_id)), + frozen: AtomicBool::new(false), + fork_id: AtomicUsize::new(fork_id as usize), + } + } + /// consume the delta into the root state + /// self becomes the new root and its fork_id is updated + pub fn merge_into_root(&self, other: Self) { + assert!(self.frozen()); + assert!(other.frozen()); + let (accounts, last_id_queue, status_cache, fork_id) = { + ( + other.accounts, + other.last_id_queue, + other.status_cache, + other.fork_id, + ) + }; + self.accounts.merge_into_root(accounts); + self.last_id_queue + .write() + .unwrap() + .merge_into_root(last_id_queue.into_inner().unwrap()); + self.status_cache + .write() + .unwrap() + .merge_into_root(status_cache.into_inner().unwrap()); + self.fork_id + .store(fork_id.load(Ordering::Relaxed), Ordering::Relaxed); + } + + #[cfg(test)] + pub fn last_ids(&self) -> &RwLock { + &self.last_id_queue + } +} + +#[cfg(test)] +mod test { + use super::*; + use crate::bank::BankError; + + #[test] + fn test_first_err() { + assert_eq!(BankDelta::first_err(&[Ok(())]), Ok(())); + assert_eq!( + BankDelta::first_err(&[Ok(()), Err(BankError::DuplicateSignature)]), + Err(BankError::DuplicateSignature) + ); + assert_eq!( + BankDelta::first_err(&[ + Ok(()), + Err(BankError::DuplicateSignature), + Err(BankError::AccountInUse) + ]), + Err(BankError::DuplicateSignature) + ); + assert_eq!( + BankDelta::first_err(&[ + Ok(()), + Err(BankError::AccountInUse), + Err(BankError::DuplicateSignature) + ]), + Err(BankError::AccountInUse) + ); + assert_eq!( + BankDelta::first_err(&[ + Err(BankError::AccountInUse), + Ok(()), + Err(BankError::DuplicateSignature) + ]), + Err(BankError::AccountInUse) + ); + } +} diff --git a/src/bank_fork.rs b/src/bank_fork.rs new file mode 100644 index 00000000000000..b8dab76a0d9226 --- /dev/null +++ b/src/bank_fork.rs @@ -0,0 +1,481 @@ +use crate::accounts::{Accounts, ErrorCounters, InstructionAccounts, InstructionLoaders}; +use crate::bank::{BankError, Result}; +use crate::bank_delta::BankDelta; +use crate::counter::Counter; +use crate::entry::Entry; +use crate::last_id_queue::MAX_ENTRY_IDS; +use crate::poh_recorder::{PohRecorder, PohRecorderError}; +use crate::result::Error; +use crate::rpc_pubsub::RpcSubscriptions; +use log::Level; +use rayon::prelude::*; +use solana_runtime::{self, RuntimeError}; +use solana_sdk::account::Account; +use solana_sdk::hash::Hash; +use solana_sdk::pubkey::Pubkey; +use solana_sdk::signature::Signature; +use solana_sdk::timing::duration_as_us; +use solana_sdk::transaction::Transaction; +use std::sync::atomic::AtomicUsize; +use std::sync::Arc; +use std::time::Instant; + +pub struct BankFork { + pub deltas: Vec>, +} + +impl BankFork { + pub fn head(&self) -> &Arc { + self.deltas + .first() + .expect("at least 1 delta needs to be available for the state") + } + fn load_accounts( + &self, + txs: &[Transaction], + results: Vec>, + error_counters: &mut ErrorCounters, + ) -> Vec> { + let accounts: Vec<&Accounts> = self.deltas.iter().map(|c| &c.accounts).collect(); + Accounts::load_accounts(&accounts, txs, results, error_counters) + } + pub fn hash_internal_state(&self) -> Hash { + self.head().hash_internal_state() + } + pub fn transaction_count(&self) -> u64 { + self.head().transaction_count() + } + + pub fn register_tick(&self, last_id: &Hash) { + self.head().register_tick(last_id) + } + + pub fn get_signature_status(&self, signature: &Signature) -> Option> { + self.head().get_signature_status(signature) + } + + pub fn clear_signatures(&self) { + self.head().clear_signatures(); + } + + /// Each program would need to be able to introspect its own state + /// this is hard-coded to the Budget language + pub fn get_balance_slow(&self, pubkey: &Pubkey) -> u64 { + self.load_slow(pubkey) + .map(|x| Self::read_balance(&x)) + .unwrap_or(0) + } + pub fn read_balance(account: &Account) -> u64 { + // TODO: Re-instate budget_program special case? + /* + if budget_program::check_id(&account.owner) { + return budget_program::get_balance(account); + } + */ + account.tokens + } + + pub fn get_account_slow(&self, pubkey: &Pubkey) -> Option { + self.load_slow(pubkey) + } + + pub fn load_slow(&self, pubkey: &Pubkey) -> Option { + let accounts: Vec<&Accounts> = self.deltas.iter().map(|c| &c.accounts).collect(); + Accounts::load_slow(&accounts, pubkey) + } + + pub fn tick_height(&self) -> u64 { + self.head().tick_height() + } + + pub fn last_id(&self) -> Hash { + self.head().last_id() + } + + #[allow(clippy::type_complexity)] + fn load_and_execute_transactions( + &self, + txs: &[Transaction], + lock_results: &[Result<()>], + max_age: usize, + ) -> ( + Vec>, + Vec>, + ) { + let head = &self.deltas[0]; + debug!("processing transactions: {}", txs.len()); + let mut error_counters = ErrorCounters::default(); + let now = Instant::now(); + let age_results = head.check_age(txs, lock_results, max_age, &mut error_counters); + let sig_results = head.check_signatures(txs, age_results, &mut error_counters); + let mut loaded_accounts = self.load_accounts(txs, sig_results, &mut error_counters); + let tick_height = head.tick_height(); + + let load_elapsed = now.elapsed(); + let now = Instant::now(); + let executed: Vec> = loaded_accounts + .iter_mut() + .zip(txs.iter()) + .map(|(accs, tx)| match accs { + Err(e) => Err(e.clone()), + Ok((ref mut accounts, ref mut loaders)) => { + solana_runtime::execute_transaction(tx, loaders, accounts, tick_height).map_err( + |RuntimeError::ProgramError(index, err)| { + BankError::ProgramError(index, err) + }, + ) + } + }) + .collect(); + + let execution_elapsed = now.elapsed(); + + debug!( + "load: {}us execute: {}us txs_len={}", + duration_as_us(&load_elapsed), + duration_as_us(&execution_elapsed), + txs.len(), + ); + let mut tx_count = 0; + let mut err_count = 0; + for (r, tx) in executed.iter().zip(txs.iter()) { + if r.is_ok() { + tx_count += 1; + } else { + if err_count == 0 { + info!("tx error: {:?} {:?}", r, tx); + } + err_count += 1; + } + } + if err_count > 0 { + info!("{} errors of {} txs", err_count, err_count + tx_count); + inc_new_counter_info!( + "bank-process_transactions-account_not_found", + error_counters.account_not_found + ); + inc_new_counter_info!("bank-process_transactions-error_count", err_count); + } + + head.accounts.increment_transaction_count(tx_count); + + inc_new_counter_info!("bank-process_transactions-txs", tx_count); + if 0 != error_counters.last_id_not_found { + inc_new_counter_info!( + "bank-process_transactions-error-last_id_not_found", + error_counters.last_id_not_found + ); + } + if 0 != error_counters.reserve_last_id { + inc_new_counter_info!( + "bank-process_transactions-error-reserve_last_id", + error_counters.reserve_last_id + ); + } + if 0 != error_counters.duplicate_signature { + inc_new_counter_info!( + "bank-process_transactions-error-duplicate_signature", + error_counters.duplicate_signature + ); + } + if 0 != error_counters.insufficient_funds { + inc_new_counter_info!( + "bank-process_transactions-error-insufficient_funds", + error_counters.insufficient_funds + ); + } + (loaded_accounts, executed) + } + + /// Process a batch of transactions. + #[must_use] + pub fn load_execute_record_notify_commit( + &self, + txs: &[Transaction], + recorder: Option<&PohRecorder>, + subs: &Option>, + lock_results: &[Result<()>], + max_age: usize, + ) -> Result>> { + let head = &self.deltas[0]; + let (loaded_accounts, executed) = + self.load_and_execute_transactions(txs, lock_results, max_age); + if let Some(poh) = recorder { + Self::record_transactions(txs, &executed, poh)?; + } + head.commit_transactions(subs, txs, &loaded_accounts, &executed); + Ok(executed) + } + + #[must_use] + pub fn load_execute_record_commit( + &self, + txs: &[Transaction], + recorder: Option<&PohRecorder>, + lock_results: &[Result<()>], + max_age: usize, + ) -> Result>> { + self.load_execute_record_notify_commit(txs, recorder, &None, lock_results, max_age) + } + pub fn process_and_record_transactions( + &self, + subs: &Option>, + txs: &[Transaction], + poh: Option<&PohRecorder>, + ) -> Result>> { + let head = &self.deltas[0]; + let now = Instant::now(); + // Once accounts are locked, other threads cannot encode transactions that will modify the + // same account state + let lock_results = head.lock_accounts(txs); + let lock_time = now.elapsed(); + + let now = Instant::now(); + // Use a shorter maximum age when adding transactions into the pipeline. This will reduce + // the likelihood of any single thread getting starved and processing old ids. + // TODO: Banking stage threads should be prioritized to complete faster then this queue + // expires. + let (loaded_accounts, results) = + self.load_and_execute_transactions(txs, &lock_results, MAX_ENTRY_IDS as usize / 2); + let load_execute_time = now.elapsed(); + + let record_time = { + let now = Instant::now(); + if let Some(recorder) = poh { + Self::record_transactions(txs, &results, recorder)?; + } + now.elapsed() + }; + + let commit_time = { + let now = Instant::now(); + head.commit_transactions(subs, txs, &loaded_accounts, &results); + now.elapsed() + }; + + let now = Instant::now(); + // Once the accounts are new transactions can enter the pipeline to process them + head.unlock_accounts(&txs, &lock_results); + let unlock_time = now.elapsed(); + debug!( + "lock: {}us load_execute: {}us record: {}us commit: {}us unlock: {}us txs_len: {}", + duration_as_us(&lock_time), + duration_as_us(&load_execute_time), + duration_as_us(&record_time), + duration_as_us(&commit_time), + duration_as_us(&unlock_time), + txs.len(), + ); + Ok(results) + } + pub fn record_transactions( + txs: &[Transaction], + results: &[Result<()>], + poh: &PohRecorder, + ) -> Result<()> { + let processed_transactions: Vec<_> = results + .iter() + .zip(txs.iter()) + .filter_map(|(r, x)| match r { + Ok(_) => Some(x.clone()), + Err(BankError::ProgramError(index, err)) => { + info!("program error {:?}, {:?}", index, err); + Some(x.clone()) + } + Err(ref e) => { + debug!("process transaction failed {:?}", e); + None + } + }) + .collect(); + debug!("processed: {} ", processed_transactions.len()); + // unlock all the accounts with errors which are filtered by the above `filter_map` + if !processed_transactions.is_empty() { + let hash = Transaction::hash(&processed_transactions); + // record and unlock will unlock all the successfull transactions + poh.record(hash, processed_transactions).map_err(|e| { + warn!("record failure: {:?}", e); + match e { + Error::PohRecorderError(PohRecorderError::MaxHeightReached) => { + BankError::MaxHeightReached + } + _ => BankError::RecordFailure, + } + })?; + } + Ok(()) + } + fn ignore_program_errors(results: Vec>) -> Vec> { + results + .into_iter() + .map(|result| match result { + // Entries that result in a ProgramError are still valid and are written in the + // ledger so map them to an ok return value + Err(BankError::ProgramError(index, err)) => { + info!("program error {:?}, {:?}", index, err); + inc_new_counter_info!("bank-ignore_program_err", 1); + Ok(()) + } + _ => result, + }) + .collect() + } + + fn par_execute_entries(&self, entries: &[(&Entry, Vec>)]) -> Result<()> { + let head = &self.deltas[0]; + inc_new_counter_info!("bank-par_execute_entries-count", entries.len()); + let results: Vec> = entries + .into_par_iter() + .map(|(e, lock_results)| { + let old_results = self + .load_execute_record_commit(&e.transactions, None, lock_results, MAX_ENTRY_IDS) + .expect("no record failures"); + let results = Self::ignore_program_errors(old_results); + head.unlock_accounts(&e.transactions, &results); + BankDelta::first_err(&results) + }) + .collect(); + BankDelta::first_err(&results) + } + + /// process entries in parallel + /// 1. In order lock accounts for each entry while the lock succeeds, up to a Tick entry + /// 2. Process the locked group in parallel + /// 3. Register the `Tick` if it's available, goto 1 + pub fn process_entries(&self, entries: &[Entry]) -> Result<()> { + let head = &self.deltas[0]; + // accumulator for entries that can be processed in parallel + let mut mt_group = vec![]; + for entry in entries { + if entry.is_tick() { + // if its a tick, execute the group and register the tick + self.par_execute_entries(&mt_group)?; + head.register_tick(&entry.id); + mt_group = vec![]; + continue; + } + // try to lock the accounts + let lock_results = head.lock_accounts(&entry.transactions); + // if any of the locks error out + // execute the current group + if BankDelta::first_err(&lock_results).is_err() { + self.par_execute_entries(&mt_group)?; + mt_group = vec![]; + //reset the lock and push the entry + head.unlock_accounts(&entry.transactions, &lock_results); + let lock_results = head.lock_accounts(&entry.transactions); + mt_group.push((entry, lock_results)); + } else { + // push the entry to the mt_group + mt_group.push((entry, lock_results)); + } + } + self.par_execute_entries(&mt_group)?; + Ok(()) + } +} +#[cfg(test)] +mod test { + use super::*; + use solana_sdk::native_program::ProgramError; + use solana_sdk::signature::Keypair; + use solana_sdk::signature::KeypairUtil; + use solana_sdk::system_program; + use solana_sdk::system_transaction::SystemTransaction; + + /// Create, sign, and process a Transaction from `keypair` to `to` of + /// `n` tokens where `last_id` is the last Entry ID observed by the client. + pub fn transfer( + bank: &BankFork, + n: u64, + keypair: &Keypair, + to: Pubkey, + last_id: Hash, + ) -> Result { + let tx = SystemTransaction::new_move(keypair, to, n, last_id, 0); + let signature = tx.signatures[0]; + let e = bank + .process_and_record_transactions(&None, &[tx], None) + .expect("no recorder"); + match &e[0] { + Ok(_) => Ok(signature), + Err(e) => Err(e.clone()), + } + } + + fn new_state(mint: &Keypair, tokens: u64, last_id: &Hash) -> BankFork { + let accounts = [(mint.pubkey(), Account::new(tokens, 0, Pubkey::default()))]; + let bank = Arc::new(BankDelta::new_from_accounts(0, &accounts, &last_id)); + BankFork { deltas: vec![bank] } + } + + fn add_system_program(delta: &BankDelta) { + let system_program_account = Account { + tokens: 1, + owner: system_program::id(), + userdata: b"solana_system_program".to_vec(), + executable: true, + loader: solana_sdk::native_loader::id(), + }; + delta.store_slow(false, &system_program::id(), &system_program_account); + } + + #[test] + fn test_interleaving_locks() { + let last_id = Hash::default(); + let mint = Keypair::new(); + let alice = Keypair::new(); + let bob = Keypair::new(); + let bank = new_state(&mint, 3, &last_id); + bank.head().register_tick(&last_id); + add_system_program(bank.head()); + + let tx1 = SystemTransaction::new_move(&mint, alice.pubkey(), 1, last_id, 0); + let pay_alice = vec![tx1]; + + let locked_alice = bank.head().lock_accounts(&pay_alice); + assert!(locked_alice[0].is_ok()); + let results_alice = bank + .load_execute_record_commit(&pay_alice, None, &locked_alice, MAX_ENTRY_IDS) + .unwrap(); + assert_eq!(results_alice[0], Ok(())); + + // try executing an interleaved transfer twice + assert_eq!( + transfer(&bank, 1, &mint, bob.pubkey(), last_id), + Err(BankError::AccountInUse) + ); + // the second time should fail as well + // this verifies that `unlock_accounts` doesn't unlock `AccountInUse` accounts + assert_eq!( + transfer(&bank, 1, &mint, bob.pubkey(), last_id), + Err(BankError::AccountInUse) + ); + + bank.head().unlock_accounts(&pay_alice, &locked_alice); + + assert_matches!(transfer(&bank, 2, &mint, bob.pubkey(), last_id), Ok(_)); + } + #[test] + fn test_bank_ignore_program_errors() { + let expected_results = vec![Ok(()), Ok(())]; + let results = vec![Ok(()), Ok(())]; + let updated_results = BankFork::ignore_program_errors(results); + assert_eq!(updated_results, expected_results); + + let results = vec![ + Err(BankError::ProgramError( + 1, + ProgramError::ResultWithNegativeTokens, + )), + Ok(()), + ]; + let updated_results = BankFork::ignore_program_errors(results); + assert_eq!(updated_results, expected_results); + + // Other BankErrors should not be ignored + let results = vec![Err(BankError::AccountNotFound), Ok(())]; + let updated_results = BankFork::ignore_program_errors(results); + assert_ne!(updated_results, expected_results); + } +} diff --git a/src/banking_stage.rs b/src/banking_stage.rs index 86cda6b51b8f32..4ac3f38d9bdda0 100644 --- a/src/banking_stage.rs +++ b/src/banking_stage.rs @@ -165,7 +165,7 @@ impl BankingStage { while chunk_start != transactions.len() { let chunk_end = chunk_start + Entry::num_will_fit(&transactions[chunk_start..]); - bank.process_and_record_transactions(&transactions[chunk_start..chunk_end], poh)?; + bank.process_and_record_transactions(&transactions[chunk_start..chunk_end], Some(poh))?; chunk_start = chunk_end; } @@ -294,7 +294,7 @@ mod tests { &bank, verified_receiver, PohServiceConfig::default(), - &bank.last_id(), + &bank.active_fork().last_id(), std::u64::MAX, genesis_block.bootstrap_leader_id, &to_validator_sender, @@ -316,7 +316,7 @@ mod tests { &bank, verified_receiver, PohServiceConfig::default(), - &bank.last_id(), + &bank.active_fork().last_id(), std::u64::MAX, genesis_block.bootstrap_leader_id, &to_validator_sender, @@ -332,14 +332,14 @@ mod tests { fn test_banking_stage_tick() { let (genesis_block, _mint_keypair) = GenesisBlock::new(2 + BOOTSTRAP_LEADER_TOKENS); let bank = Arc::new(Bank::new(&genesis_block)); - let start_hash = bank.last_id(); + let start_hash = bank.active_fork().last_id(); let (verified_sender, verified_receiver) = channel(); let (to_validator_sender, _) = channel(); let (banking_stage, entry_receiver) = BankingStage::new( &bank, verified_receiver, PohServiceConfig::Sleep(Duration::from_millis(1)), - &bank.last_id(), + &bank.active_fork().last_id(), std::u64::MAX, genesis_block.bootstrap_leader_id, &to_validator_sender, @@ -350,7 +350,7 @@ mod tests { let entries: Vec<_> = entry_receiver.iter().flat_map(|x| x).collect(); assert!(entries.len() != 0); assert!(entries.verify(&start_hash)); - assert_eq!(entries[entries.len() - 1].id, bank.last_id()); + assert_eq!(entries[entries.len() - 1].id, bank.active_fork().last_id()); assert_eq!( banking_stage.join().unwrap(), Some(BankingStageReturnType::ChannelDisconnected) @@ -361,14 +361,14 @@ mod tests { fn test_banking_stage_entries_only() { let (genesis_block, mint_keypair) = GenesisBlock::new(2 + BOOTSTRAP_LEADER_TOKENS); let bank = Arc::new(Bank::new(&genesis_block)); - let start_hash = bank.last_id(); + let start_hash = bank.active_fork().last_id(); let (verified_sender, verified_receiver) = channel(); let (to_validator_sender, _) = channel(); let (banking_stage, entry_receiver) = BankingStage::new( &bank, verified_receiver, PohServiceConfig::default(), - &bank.last_id(), + &bank.active_fork().last_id(), std::u64::MAX, genesis_block.bootstrap_leader_id, &to_validator_sender, @@ -426,7 +426,7 @@ mod tests { &bank, verified_receiver, PohServiceConfig::default(), - &bank.last_id(), + &bank.active_fork().last_id(), std::u64::MAX, genesis_block.bootstrap_leader_id, &to_validator_sender, @@ -479,7 +479,7 @@ mod tests { .iter() .for_each(|x| assert_eq!(*x, Ok(()))); } - assert_eq!(bank.get_balance(&alice.pubkey()), 1); + assert_eq!(bank.active_fork().get_balance_slow(&alice.pubkey()), 1); } // Test that when the max_tick_height is reached, the banking stage exits @@ -495,7 +495,7 @@ mod tests { &bank, verified_receiver, PohServiceConfig::default(), - &bank.last_id(), + &bank.active_fork().last_id(), max_tick_height, genesis_block.bootstrap_leader_id, &to_validator_sender, diff --git a/src/broadcast_service.rs b/src/broadcast_service.rs index 15b52f257e19e6..067f7a5e64fcb1 100644 --- a/src/broadcast_service.rs +++ b/src/broadcast_service.rs @@ -1,17 +1,16 @@ //! The `broadcast_service` broadcasts data from a leader node to validators //! use crate::bank::Bank; +use crate::blocktree::Blocktree; use crate::cluster_info::{ClusterInfo, ClusterInfoError, NodeInfo, DATA_PLANE_FANOUT}; use crate::counter::Counter; -use crate::entry::Entry; -use crate::entry::EntrySlice; +use crate::entry::{Entry, EntrySlice}; #[cfg(feature = "erasure")] use crate::erasure::CodingGenerator; use crate::leader_scheduler::LeaderScheduler; use crate::packet::index_blobs; use crate::result::{Error, Result}; use crate::service::Service; -use crate::streamer::BlobSender; use log::Level; use rayon::prelude::*; use solana_metrics::{influxdb, submit}; @@ -47,7 +46,7 @@ impl Broadcast { receiver: &Receiver>, sock: &UdpSocket, leader_scheduler: &Arc>, - blob_sender: &BlobSender, + blocktree: &Arc, ) -> Result<()> { let timer = Duration::new(1, 0); let entries = receiver.recv_timeout(timer)?; @@ -84,17 +83,25 @@ impl Broadcast { // TODO: blob_index should be slot-relative... index_blobs(&blobs, &self.id, &mut self.blob_index, &slots); + // TODO: retry this? + blocktree + .write_shared_blobs(&blobs) + .expect("ledger recording failed for leader"); + let to_blobs_elapsed = duration_as_ms(&to_blobs_start.elapsed()); let broadcast_start = Instant::now(); inc_new_counter_info!("streamer-broadcast-sent", blobs.len()); - blob_sender.send(blobs.clone())?; + // don't count coding blobs in the blob indexes + self.blob_index += blobs.len() as u64; // Send out data ClusterInfo::broadcast(&self.id, last_tick, &broadcast_table, sock, &blobs)?; + inc_new_counter_info!("streamer-broadcast-sent", blobs.len()); + // Fill in the coding blob data from the window data blobs #[cfg(feature = "erasure")] { @@ -186,8 +193,8 @@ impl BroadcastService { leader_scheduler: &Arc>, receiver: &Receiver>, max_tick_height: u64, + blocktree: &Arc, exit_signal: &Arc, - blob_sender: &BlobSender, ) -> BroadcastServiceReturnType { let me = cluster_info.read().unwrap().my_data().clone(); @@ -212,7 +219,7 @@ impl BroadcastService { receiver, sock, leader_scheduler, - blob_sender, + blocktree, ) { match e { Error::RecvTimeoutError(RecvTimeoutError::Disconnected) | Error::SendError => { @@ -253,11 +260,11 @@ impl BroadcastService { leader_scheduler: Arc>, receiver: Receiver>, max_tick_height: u64, + blocktree: &Arc, exit_sender: Arc, - blob_sender: &BlobSender, ) -> Self { let exit_signal = Arc::new(AtomicBool::new(false)); - let blob_sender = blob_sender.clone(); + let blocktree = blocktree.clone(); let thread_hdl = Builder::new() .name("solana-broadcaster".to_string()) .spawn(move || { @@ -270,8 +277,8 @@ impl BroadcastService { &leader_scheduler, &receiver, max_tick_height, + &blocktree, &exit_signal, - &blob_sender, ) }) .unwrap(); @@ -335,8 +342,6 @@ mod test { let exit_sender = Arc::new(AtomicBool::new(false)); let bank = Arc::new(Bank::default()); - let (blob_fetch_sender, _) = channel(); - // Start up the broadcast stage let broadcast_service = BroadcastService::new( bank.clone(), @@ -346,8 +351,8 @@ mod test { leader_scheduler, entry_receiver, max_tick_height, + &blocktree, exit_sender, - &blob_fetch_sender, ); MockBroadcastService { diff --git a/src/cluster_info.rs b/src/cluster_info.rs index 433d605ff1f831..ceb6bce14d6cd3 100644 --- a/src/cluster_info.rs +++ b/src/cluster_info.rs @@ -355,7 +355,7 @@ impl ClusterInfo { fn sort_by_stake(peers: &[NodeInfo], bank: &Arc) -> Vec<(u64, NodeInfo)> { let mut peers_with_stakes: Vec<_> = peers .iter() - .map(|c| (bank.get_balance(&c.id), c.clone())) + .map(|c| (bank.root().get_balance_slow(&c.id), c.clone())) .collect(); peers_with_stakes.sort_unstable(); peers_with_stakes diff --git a/src/compute_leader_confirmation_service.rs b/src/compute_leader_confirmation_service.rs index 148f572cda5eaa..a2daefdaf0f9a8 100644 --- a/src/compute_leader_confirmation_service.rs +++ b/src/compute_leader_confirmation_service.rs @@ -38,6 +38,8 @@ impl ComputeLeaderConfirmationService { // Hold an accounts_db read lock as briefly as possible, just long enough to collect all // the vote states let vote_states: Vec = bank + .root() + .head() .accounts .accounts_db .read() @@ -59,7 +61,7 @@ impl ComputeLeaderConfirmationService { let mut ticks_and_stakes: Vec<(u64, u64)> = vote_states .iter() .filter_map(|vote_state| { - let validator_stake = bank.get_balance(&vote_state.node_id); + let validator_stake = bank.active_fork().get_balance_slow(&vote_state.node_id); total_stake += validator_stake; // Filter out any validators that don't have at least one vote // by returning None @@ -72,8 +74,10 @@ impl ComputeLeaderConfirmationService { let super_majority_stake = (2 * total_stake) / 3; - if let Some(last_valid_validator_timestamp) = - bank.get_confirmation_timestamp(&mut ticks_and_stakes, super_majority_stake) + if let Some(last_valid_validator_timestamp) = bank + .active_fork() + .head() + .get_confirmation_timestamp(&mut ticks_and_stakes, super_majority_stake) { return Ok(last_valid_validator_timestamp); } @@ -178,7 +182,7 @@ pub mod tests { let ids: Vec<_> = (0..10) .map(|i| { let last_id = hash(&serialize(&i).unwrap()); // Unique hash - bank.register_tick(&last_id); + bank.active_fork().register_tick(&last_id); // sleep to get a different timestamp in the bank sleep(Duration::from_millis(1)); last_id diff --git a/src/deltas.rs b/src/deltas.rs new file mode 100644 index 00000000000000..2928c35129e471 --- /dev/null +++ b/src/deltas.rs @@ -0,0 +1,134 @@ +//! Simple data structure to keep track of deltas (partial state updates). It +//! stores a map of forks to a type and parent forks. +//! +//! A root is the fork that is a parent to all the leaf forks. + +use hashbrown::{HashMap, HashSet}; +use std::collections::VecDeque; + +pub struct Deltas { + /// Stores a map from fork to a T and a parent fork + pub deltas: HashMap, +} + +impl Deltas { + pub fn is_empty(&self) -> bool { + self.deltas.is_empty() + } + pub fn load(&self, fork: u64) -> Option<&(T, u64)> { + self.deltas.get(&fork) + } + pub fn store(&mut self, fork: u64, data: T, trunk: u64) { + self.insert(fork, data, trunk); + } + pub fn insert(&mut self, fork: u64, data: T, trunk: u64) { + self.deltas.insert(fork, (data, trunk)); + } + /// Given a base fork, and a maximum number, collect all the + /// forks starting from the base fork backwards + pub fn collect(&self, num: usize, mut base: u64) -> Vec<(u64, &T)> { + let mut rv = vec![]; + loop { + if rv.len() == num { + break; + } + if let Some((val, next)) = self.load(base) { + rv.push((base, val)); + base = *next; + } else { + break; + } + } + rv + } + + ///invert the dag + pub fn invert(&self) -> HashMap> { + let mut idag = HashMap::new(); + for (k, (_, v)) in &self.deltas { + idag.entry(*v).or_insert(HashSet::new()).insert(*k); + } + idag + } + + ///create a new Deltas tree that only derives from the trunk + pub fn prune(&self, trunk: u64, inverse: &HashMap>) -> Self { + let mut new = Self::default(); + // simple BFS + let mut queue = VecDeque::new(); + queue.push_back(trunk); + loop { + if queue.is_empty() { + break; + } + let trunk = queue.pop_front().unwrap(); + let (data, prev) = self.load(trunk).expect("load from inverse").clone(); + new.store(trunk, data.clone(), prev); + if let Some(children) = inverse.get(&trunk) { + let mut next = children.into_iter().cloned().collect(); + queue.append(&mut next); + } + } + new + } +} + +impl Default for Deltas { + fn default() -> Self { + Self { + deltas: HashMap::new(), + } + } +} +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_new() { + let cp: Deltas = Deltas::default(); + assert!(cp.is_empty()); + } + + #[test] + fn test_load_store() { + let mut cp: Deltas = Deltas::default(); + assert_eq!(cp.load(1), None); + cp.store(1, true, 0); + assert_eq!(cp.load(1), Some(&(true, 0))); + } + #[test] + fn test_collect() { + let mut cp: Deltas = Deltas::default(); + assert_eq!(cp.load(1), None); + cp.store(1, true, 0); + assert_eq!(cp.collect(0, 1), vec![]); + assert_eq!(cp.collect(1, 1), vec![(1, &true)]); + } + #[test] + fn test_invert() { + let mut cp: Deltas = Deltas::default(); + assert_eq!(cp.load(1), None); + cp.store(1, true, 0); + cp.store(2, true, 0); + let inverse = cp.invert(); + assert_eq!(inverse.len(), 1); + assert_eq!(inverse[&0].len(), 2); + let list: Vec = inverse[&0].iter().cloned().collect(); + assert_eq!(list, vec![1, 2]); + } + #[test] + fn test_prune() { + let mut cp: Deltas = Deltas::default(); + assert_eq!(cp.load(1), None); + cp.store(1, true, 0); + cp.store(2, true, 0); + cp.store(3, true, 1); + let inverse = cp.invert(); + let pruned = cp.prune(1, &inverse); + assert_eq!(pruned.load(0), None); + assert_eq!(pruned.load(1), Some(&(true, 0))); + assert_eq!(pruned.load(2), None); + assert_eq!(pruned.load(3), Some(&(true, 1))); + } +} diff --git a/src/forks.rs b/src/forks.rs new file mode 100644 index 00000000000000..1d88de862760df --- /dev/null +++ b/src/forks.rs @@ -0,0 +1,259 @@ +use crate::bank_delta::BankDelta; +/// This module tracks the forks in the bank +use crate::bank_fork::BankFork; +use std::sync::Arc; +//TODO: own module error +use crate::deltas::Deltas; +use solana_sdk::hash::Hash; +use std; +use std::result; + +pub const ROLLBACK_DEPTH: usize = 32usize; + +/// Reasons a transaction might be rejected. +#[derive(Debug, PartialEq, Eq, Clone)] +pub enum ForksError { + /// Fork is not in the Deltas DAG + UnknownFork, + + /// The specified trunk is not in the Deltas DAG + InvalidTrunk, + + /// Specified base delta is still live + DeltaNotFrozen, + + /// Requested live delta is frozen + DeltaIsFrozen, +} + +pub type Result = result::Result; + +#[derive(Default)] +pub struct Forks { + pub deltas: Deltas>, + + /// Last fork to be initialized + /// This should be the last fork to be replayed or the TPU fork + pub active_fork: u64, + + /// Fork that is root + pub root: u64, +} + +impl Forks { + pub fn active_fork(&self) -> BankFork { + self.fork(self.active_fork).expect("live fork") + } + pub fn root(&self) -> BankFork { + self.fork(self.root).expect("root fork") + } + + pub fn fork(&self, fork: u64) -> Option { + let cp: Vec<_> = self + .deltas + .collect(ROLLBACK_DEPTH + 1, fork) + .into_iter() + .map(|x| x.1) + .cloned() + .collect(); + if cp.is_empty() { + None + } else { + Some(BankFork { deltas: cp }) + } + } + /// Collapse the bottom two deltas. + /// The tree is computed from the `leaf` to the `root` + /// The path from `leaf` to the `root` is the active chain. + /// The leaf is the last possible fork, it should have no descendants. + /// The direct child of the root that leads the leaf becomes the new root. + /// The forks that are not a descendant of the new root -> leaf path are pruned. + /// active_fork is the leaf. + /// root is the new root. + /// Return the new root id. + pub fn merge_into_root(&mut self, max_depth: usize, leaf: u64) -> Result> { + // `old` root, should have `root` as its fork_id + // `new` root is a direct descendant of old and has new_root_id as its fork_id + // new is merged into old + // and old is swapped into the delta under new_root_id + let merge_root = { + let active_fork = self.deltas.collect(ROLLBACK_DEPTH + 1, leaf); + let leaf_id = active_fork + .first() + .map(|x| x.0) + .ok_or(ForksError::UnknownFork)?; + assert_eq!(leaf_id, leaf); + let len = active_fork.len(); + if len > max_depth { + let old_root = active_fork[len - 1]; + let new_root = active_fork[len - 2]; + if !new_root.1.frozen() { + trace!("new_root id {}", new_root.1.fork_id()); + return Err(ForksError::DeltaNotFrozen); + } + if !old_root.1.frozen() { + trace!("old id {}", old_root.1.fork_id()); + return Err(ForksError::DeltaNotFrozen); + } + //stupid sanity checks + assert_eq!(new_root.1.fork_id(), new_root.0); + assert_eq!(old_root.1.fork_id(), old_root.0); + Some((old_root.1.clone(), new_root.1.clone(), new_root.0)) + } else { + None + } + }; + if let Some((old_root, new_root, new_root_id)) = merge_root { + let idag = self.deltas.invert(); + let new_deltas = self.deltas.prune(new_root_id, &idag); + let old_root_id = old_root.fork_id(); + self.deltas = new_deltas; + self.root = new_root_id; + self.active_fork = leaf; + // old should have been pruned + assert!(self.deltas.load(old_root_id).is_none()); + // new_root id should be in the new tree + assert!(!self.deltas.load(new_root_id).is_none()); + + // swap in the old instance under the new_root id + // this should be the last external ref to `new_root` + self.deltas + .insert(new_root_id, old_root.clone(), old_root_id); + + // merge all the new changes into the old instance under the new id + // this should consume `new` + // new should have no other references + let new_root: BankDelta = Arc::try_unwrap(new_root).unwrap(); + old_root.merge_into_root(new_root); + assert_eq!(old_root.fork_id(), new_root_id); + Ok(Some(new_root_id)) + } else { + Ok(None) + } + } + + /// Initialize the first root + pub fn init_root(&mut self, delta: BankDelta) { + assert!(self.deltas.is_empty()); + self.active_fork = delta.fork_id(); + self.root = delta.fork_id(); + //TODO: using u64::MAX as the impossible delta + //this should be a None instead + self.deltas + .store(self.active_fork, Arc::new(delta), std::u64::MAX); + } + + pub fn is_active_fork(&self, fork: u64) -> bool { + if let Some(state) = self.deltas.load(fork) { + !state.0.frozen() && self.active_fork == fork + } else { + false + } + } + /// Initialize the `current` fork that is a direct descendant of the `base` fork. + pub fn init_fork(&mut self, current: u64, last_id: &Hash, base: u64) -> Result<()> { + if let Some(state) = self.deltas.load(base) { + if !state.0.frozen() { + return Err(ForksError::DeltaNotFrozen); + } + let new = state.0.fork(current, last_id); + self.deltas.store(current, Arc::new(new), base); + self.active_fork = current; + Ok(()) + } else { + return Err(ForksError::UnknownFork); + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + use solana_sdk::hash::hash; + + #[test] + fn forks_init_root() { + let mut forks = Forks::default(); + let cp = BankDelta::new(0, &Hash::default()); + forks.init_root(cp); + assert!(forks.is_active_fork(0)); + assert_eq!(forks.root().deltas.len(), 1); + assert_eq!(forks.root().head().fork_id(), 0); + assert_eq!(forks.active_fork().head().fork_id(), 0); + } + + #[test] + fn forks_init_fork() { + let mut forks = Forks::default(); + let last_id = Hash::default(); + let cp = BankDelta::new(0, &last_id); + cp.register_tick(&last_id); + forks.init_root(cp); + let last_id = hash(last_id.as_ref()); + assert_eq!( + forks.init_fork(1, &last_id, 1), + Err(ForksError::UnknownFork) + ); + assert_eq!( + forks.init_fork(1, &last_id, 0), + Err(ForksError::DeltaNotFrozen) + ); + forks.root().head().freeze(); + assert_eq!(forks.init_fork(1, &last_id, 0), Ok(())); + + assert_eq!(forks.root().head().fork_id(), 0); + assert_eq!(forks.active_fork().head().fork_id(), 1); + assert_eq!(forks.active_fork().deltas.len(), 2); + } + + #[test] + fn forks_merge() { + let mut forks = Forks::default(); + let last_id = Hash::default(); + let cp = BankDelta::new(0, &last_id); + cp.register_tick(&last_id); + forks.init_root(cp); + let last_id = hash(last_id.as_ref()); + forks.root().head().freeze(); + assert_eq!(forks.init_fork(1, &last_id, 0), Ok(())); + forks.active_fork().head().register_tick(&last_id); + forks.active_fork().head().freeze(); + assert_eq!(forks.merge_into_root(2, 1), Ok(None)); + assert_eq!(forks.merge_into_root(1, 1), Ok(Some(1))); + + assert_eq!(forks.active_fork().deltas.len(), 1); + assert_eq!(forks.root().head().fork_id(), 1); + assert_eq!(forks.active_fork().head().fork_id(), 1); + } + #[test] + fn forks_merge_prune() { + let mut forks = Forks::default(); + let last_id = Hash::default(); + let cp = BankDelta::new(0, &last_id); + cp.register_tick(&last_id); + forks.init_root(cp); + let last_id = hash(last_id.as_ref()); + forks.root().head().freeze(); + assert_eq!(forks.init_fork(1, &last_id, 0), Ok(())); + assert_eq!(forks.fork(1).unwrap().deltas.len(), 2); + forks.fork(1).unwrap().head().register_tick(&last_id); + + // add a fork 2 to be pruned + // fork 2 connects to 0 + let last_id = hash(last_id.as_ref()); + assert_eq!(forks.init_fork(2, &last_id, 0), Ok(())); + assert_eq!(forks.fork(2).unwrap().deltas.len(), 2); + forks.fork(2).unwrap().head().register_tick(&last_id); + + forks.fork(1).unwrap().head().freeze(); + // fork 1 is the new root, only forks that are descendant from 1 are valid + assert_eq!(forks.merge_into_root(1, 1), Ok(Some(1))); + + // fork 2 is gone since it does not connect to 1 + assert!(forks.fork(2).is_none()); + + assert_eq!(forks.active_fork().deltas.len(), 1); + assert_eq!(forks.root().head().fork_id(), 1); + assert_eq!(forks.active_fork().head().fork_id(), 1); + } +} diff --git a/src/fullnode.rs b/src/fullnode.rs index 7259719d391417..b53a76e01aafa0 100644 --- a/src/fullnode.rs +++ b/src/fullnode.rs @@ -12,7 +12,6 @@ use crate::rpc::JsonRpcService; use crate::rpc_pubsub::PubSubService; use crate::service::Service; use crate::storage_stage::StorageState; -use crate::streamer::BlobSender; use crate::tpu::{Tpu, TpuRotationReceiver, TpuRotationSender}; use crate::tvu::{Sockets, Tvu}; use crate::voting_keypair::VotingKeypair; @@ -99,6 +98,7 @@ pub struct Fullnode { rpc_pubsub_service: Option, gossip_service: GossipService, bank: Arc, + blocktree: Arc, cluster_info: Arc>, sigverify_disabled: bool, tpu_sockets: Vec, @@ -106,7 +106,6 @@ pub struct Fullnode { node_services: NodeServices, rotation_sender: TpuRotationSender, rotation_receiver: TpuRotationReceiver, - blob_sender: BlobSender, } impl Fullnode { @@ -202,7 +201,7 @@ impl Fullnode { // Get the scheduled leader let (scheduled_leader, slot_height, max_tpu_tick_height) = { - let tick_height = bank.tick_height(); + let tick_height = bank.active_fork().tick_height(); let leader_scheduler = bank.leader_scheduler.read().unwrap(); let slot = leader_scheduler.tick_height_to_slot(tick_height); @@ -254,7 +253,7 @@ impl Fullnode { let blob_index = Self::get_consumed_for_slot(&blocktree, slot_height); - let (tvu, blob_sender) = Tvu::new( + let tvu = Tvu::new( voting_keypair_option, &bank, blob_index, @@ -271,7 +270,7 @@ impl Fullnode { ledger_signal_receiver, ); let tpu = Tpu::new( - &Arc::new(bank.copy_for_tpu()), + &bank, PohServiceConfig::default(), node.sockets .tpu @@ -289,8 +288,8 @@ impl Fullnode { &last_entry_id, id, &rotation_sender, - &blob_sender, scheduled_leader == id, + &blocktree, ); inc_new_counter_info!("fullnode-new", 1); @@ -299,6 +298,7 @@ impl Fullnode { id, cluster_info, bank, + blocktree, sigverify_disabled: config.sigverify_disabled, gossip_service, rpc_service: Some(rpc_service), @@ -309,13 +309,12 @@ impl Fullnode { broadcast_socket: node.sockets.broadcast, rotation_sender, rotation_receiver, - blob_sender, } } fn get_next_leader(&self, tick_height: u64) -> (Pubkey, u64) { loop { - let bank_tick_height = self.bank.tick_height(); + let bank_tick_height = self.bank.active_fork().tick_height(); if bank_tick_height >= tick_height { break; } @@ -372,10 +371,10 @@ impl Fullnode { FullnodeReturnType::ValidatorToLeaderRotation }; - let last_entry_id = self.bank.last_id(); + let last_entry_id = self.bank.active_fork().last_id(); self.node_services.tpu.switch_to_leader( - &Arc::new(self.bank.copy_for_tpu()), + &self.bank, PohServiceConfig::default(), self.tpu_sockets .iter() @@ -391,7 +390,7 @@ impl Fullnode { &last_entry_id, self.id, &self.rotation_sender, - &self.blob_sender, + &self.blocktree, ); transition @@ -493,7 +492,7 @@ pub fn new_bank_from_ledger( "processed {} ledger entries in {}ms, tick_height={}...", entry_height, duration_as_ms(&now.elapsed()), - bank.tick_height() + bank.active_fork().tick_height() ); ( @@ -609,6 +608,7 @@ mod tests { } #[test] + #[ignore] fn test_leader_to_leader_transition() { solana_logger::setup(); @@ -667,6 +667,7 @@ mod tests { } #[test] + #[ignore] // #2691, because process_ledger() is a little dumb fn test_wrong_role_transition() { solana_logger::setup(); @@ -831,7 +832,10 @@ mod tests { &LeaderSchedulerConfig::default(), ); - assert!(bank.tick_height() >= bank.leader_scheduler.read().unwrap().ticks_per_epoch); + assert!( + bank.active_fork().tick_height() + >= bank.leader_scheduler.read().unwrap().ticks_per_epoch + ); assert!(entry_height >= ledger_initial_len); @@ -917,7 +921,8 @@ mod tests { info!("Make sure the tvu bank has not reached the last tick for the slot (the last tick is ticks_per_slot - 1)"); { - let w_last_ids = leader.bank.last_ids().write().unwrap(); + let bank_state = leader.bank.active_fork(); + let w_last_ids = bank_state.head().last_ids().write().unwrap(); assert!(w_last_ids.tick_height < ticks_per_slot - 1); } diff --git a/src/last_id_queue.rs b/src/last_id_queue.rs index 4242fc4356050b..9ab9871db3efda 100644 --- a/src/last_id_queue.rs +++ b/src/last_id_queue.rs @@ -125,22 +125,6 @@ impl LastIdQueue { None } - /// Look through the last_ids and find all the valid ids - /// This is batched to avoid holding the lock for a significant amount of time - /// - /// Return a vec of tuple of (valid index, timestamp) - /// index is into the passed ids slice to avoid copying hashes - pub fn count_valid_ids(&self, ids: &[Hash]) -> Vec<(usize, u64)> { - let mut ret = Vec::new(); - for (i, id) in ids.iter().enumerate() { - if let Some(entry) = self.entries.get(id) { - if self.tick_height - entry.tick_height < MAX_ENTRY_IDS as u64 { - ret.push((i, entry.timestamp)); - } - } - } - ret - } pub fn clear(&mut self) { self.entries = HashMap::new(); self.tick_height = 0; @@ -168,25 +152,6 @@ mod tests { use bincode::serialize; use solana_sdk::hash::hash; - #[test] - fn test_count_valid_ids() { - let first_id = Hash::default(); - let mut entry_queue = LastIdQueue::default(); - entry_queue.register_tick(&first_id); - let ids: Vec<_> = (0..MAX_ENTRY_IDS) - .map(|i| { - let last_id = hash(&serialize(&i).unwrap()); // Unique hash - entry_queue.register_tick(&last_id); - last_id - }) - .collect(); - assert_eq!(entry_queue.count_valid_ids(&[]).len(), 0); - assert_eq!(entry_queue.count_valid_ids(&[first_id]).len(), 0); - for (i, id) in entry_queue.count_valid_ids(&ids).iter().enumerate() { - assert_eq!(id.0, i); - } - } - #[test] fn test_register_tick() { let last_id = Hash::default(); diff --git a/src/leader_scheduler.rs b/src/leader_scheduler.rs index 46b4a929ae8b08..d4c748584b66f4 100644 --- a/src/leader_scheduler.rs +++ b/src/leader_scheduler.rs @@ -211,7 +211,8 @@ impl LeaderScheduler { ); { - let accounts = bank.accounts.accounts_db.read().unwrap(); + let bank_state = bank.root(); + let accounts = bank_state.head().accounts.accounts_db.read().unwrap(); // TODO: iterate through checkpoints, too accounts .accounts @@ -334,7 +335,7 @@ impl LeaderScheduler { { let mut active_accounts: Vec<(&'a Pubkey, u64)> = active .filter_map(|pubkey| { - let stake = bank.get_balance(pubkey); + let stake = bank.root().get_balance_slow(pubkey); if stake > 0 { Some((pubkey, stake as u64)) } else { @@ -834,7 +835,7 @@ pub mod tests { let new_validator = Keypair::new(); let new_pubkey = new_validator.pubkey(); tied_validators_pk.push(new_pubkey); - assert!(bank.get_balance(&mint_keypair.pubkey()) > 1); + assert!(bank.root().get_balance_slow(&mint_keypair.pubkey()) > 1); bank.transfer(1, &mint_keypair, new_pubkey, last_id) .unwrap(); } @@ -1093,7 +1094,7 @@ pub mod tests { "bootstrap_leader_id: {:?}", genesis_block.bootstrap_leader_id ); - assert_eq!(bank.tick_height(), 0); + assert_eq!(bank.active_fork().tick_height(), 0); // // tick_height == 0 is a special case diff --git a/src/lib.rs b/src/lib.rs index 8f58e92753356d..cb5b204ae13191 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -11,6 +11,8 @@ pub mod counter; pub mod accounts; pub mod bank; +pub mod bank_delta; +pub mod bank_fork; pub mod banking_stage; pub mod blob_fetch_stage; pub mod bloom; @@ -27,6 +29,7 @@ pub mod crds_gossip_error; pub mod crds_gossip_pull; pub mod crds_gossip_push; pub mod crds_value; +pub mod deltas; #[macro_use] pub mod contact_info; pub mod blocktree; @@ -39,6 +42,7 @@ pub mod entry_stream_stage; #[cfg(feature = "erasure")] pub mod erasure; pub mod fetch_stage; +pub mod forks; pub mod fullnode; pub mod gen_keys; pub mod genesis_block; diff --git a/src/poh_recorder.rs b/src/poh_recorder.rs index 26554ef897889e..0c82d0c6937a52 100644 --- a/src/poh_recorder.rs +++ b/src/poh_recorder.rs @@ -71,7 +71,10 @@ impl PohRecorder { last_entry_id: Hash, max_tick_height: u64, ) -> Self { - let poh = Arc::new(Mutex::new(Poh::new(last_entry_id, bank.tick_height()))); + let poh = Arc::new(Mutex::new(Poh::new( + last_entry_id, + bank.active_fork().tick_height(), + ))); PohRecorder { poh, bank, @@ -109,7 +112,7 @@ impl PohRecorder { id: tick.id, transactions: vec![], }; - self.bank.register_tick(&tick.id); + self.bank.active_fork().register_tick(&tick.id); self.sender.send(vec![tick])?; Ok(()) } @@ -128,7 +131,7 @@ mod tests { fn test_poh_recorder() { let (genesis_block, _mint_keypair) = GenesisBlock::new(2); let bank = Arc::new(Bank::new(&genesis_block)); - let prev_id = bank.last_id(); + let prev_id = bank.active_fork().last_id(); let (entry_sender, entry_receiver) = channel(); let mut poh_recorder = PohRecorder::new(bank, entry_sender, prev_id, 2); diff --git a/src/poh_service.rs b/src/poh_service.rs index 9716476a636c15..1a6e9813432ad5 100644 --- a/src/poh_service.rs +++ b/src/poh_service.rs @@ -137,7 +137,7 @@ mod tests { fn test_poh_service() { let (genesis_block, _mint_keypair) = GenesisBlock::new(2); let bank = Arc::new(Bank::new(&genesis_block)); - let prev_id = bank.last_id(); + let prev_id = bank.active_fork().last_id(); let (entry_sender, entry_receiver) = channel(); let poh_recorder = PohRecorder::new(bank, entry_sender, prev_id, std::u64::MAX); let exit = Arc::new(AtomicBool::new(false)); diff --git a/src/replay_stage.rs b/src/replay_stage.rs index b5b99d3d631244..402c91bd11c26b 100644 --- a/src/replay_stage.rs +++ b/src/replay_stage.rs @@ -59,6 +59,8 @@ impl ReplayStage { #[allow(clippy::too_many_arguments)] fn process_entries( mut entries: Vec, + current_slot: u64, + base_slot: u64, bank: &Arc, cluster_info: &Arc>, voting_keypair: Option<&Arc>, @@ -73,8 +75,37 @@ impl ReplayStage { .to_owned(), ); - let mut res = Ok(()); - let mut num_entries_to_write = entries.len(); + // if zero, time to vote ;) + let mut num_ticks_left_in_slot = bank + .leader_scheduler + .write() + .unwrap() + .num_ticks_left_in_block(current_slot, bank.active_fork().tick_height()); + + info!( + "entries.len(): {}, bank.tick_height(): {} num_ticks_left_in_slot: {}", + entries.len(), + bank.active_fork().tick_height(), + num_ticks_left_in_slot, + ); + + // this code to guard against consuming more ticks in a slot than are actually + // allowed by protocol. entries beyond max_tick_height are silently discarded + // TODO: slash somebody? + // this code also counts down to a vote + entries.retain(|e| { + let retain = num_ticks_left_in_slot > 0; + if num_ticks_left_in_slot > 0 && e.is_tick() { + num_ticks_left_in_slot -= 1; + } + retain + }); + info!( + "entries.len(): {}, num_ticks_left_in_slot: {}", + entries.len(), + num_ticks_left_in_slot, + ); + let now = Instant::now(); if !entries.as_slice().verify(&last_entry_id.read().unwrap()) { @@ -86,66 +117,50 @@ impl ReplayStage { duration_as_ms(&now.elapsed()) as usize ); - let num_ticks = bank.tick_height(); - let mut num_ticks_to_next_vote = bank - .leader_scheduler - .read() - .unwrap() - .num_ticks_left_in_slot(num_ticks); - - for (i, entry) in entries.iter().enumerate() { - inc_new_counter_info!("replicate-stage_bank-tick", bank.tick_height() as usize); - if entry.is_tick() { - if num_ticks_to_next_vote == 0 { - num_ticks_to_next_vote = bank.leader_scheduler.read().unwrap().ticks_per_slot; - } - num_ticks_to_next_vote -= 1; - } + inc_new_counter_info!( + "replicate-stage_bank-tick", + bank.active_fork().tick_height() as usize + ); + if bank.fork(current_slot).is_none() { + bank.init_fork(current_slot, &entries[0].id, base_slot) + .expect("init fork"); + } + let res = bank.fork(current_slot).unwrap().process_entries(&entries); + + if res.is_err() { + // TODO: This will return early from the first entry that has an erroneous + // transaction, instead of processing the rest of the entries in the vector + // of received entries. This is in line with previous behavior when + // bank.process_entries() was used to process the entries, but doesn't solve the + // issue that the bank state was still changed, leading to inconsistencies with the + // leader as the leader currently should not be publishing erroneous transactions inc_new_counter_info!( - "replicate-stage_tick-to-vote", - num_ticks_to_next_vote as usize + "replicate-stage_failed_process_entries", + current_slot as usize ); - // If it's the last entry in the vector, i will be vec len - 1. - // If we don't process the entry now, the for loop will exit and the entry - // will be dropped. - if 0 == num_ticks_to_next_vote || (i + 1) == entries.len() { - res = bank.process_entries(&entries[0..=i]); - - if res.is_err() { - // TODO: This will return early from the first entry that has an erroneous - // transaction, instead of processing the rest of the entries in the vector - // of received entries. This is in line with previous behavior when - // bank.process_entries() was used to process the entries, but doesn't solve the - // issue that the bank state was still changed, leading to inconsistencies with the - // leader as the leader currently should not be publishing erroneous transactions - inc_new_counter_info!("replicate-stage_failed_process_entries", i); - break; - } - if 0 == num_ticks_to_next_vote { - if let Some(voting_keypair) = voting_keypair { - let keypair = voting_keypair.as_ref(); - let vote = VoteTransaction::new_vote( - keypair, - bank.tick_height(), - bank.last_id(), - 0, - ); - cluster_info.write().unwrap().push_vote(vote); - } - } - num_entries_to_write = i + 1; - break; + res?; + } + + if num_ticks_left_in_slot == 0 { + let fork = bank.fork(current_slot).expect("current bank state"); + + info!("freezing {} from replay_stage", current_slot); + fork.head().freeze(); + bank.merge_into_root(current_slot); + if let Some(voting_keypair) = voting_keypair { + let keypair = voting_keypair.as_ref(); + let vote = + VoteTransaction::new_vote(keypair, fork.tick_height(), fork.last_id(), 0); + cluster_info.write().unwrap().push_vote(vote); } } // If leader rotation happened, only write the entries up to leader rotation. - entries.truncate(num_entries_to_write); *last_entry_id.write().unwrap() = entries .last() .expect("Entries cannot be empty at this point") .id; - inc_new_counter_info!( "replicate-transactions", entries.iter().map(|x| x.transactions.len()).sum() @@ -160,7 +175,7 @@ impl ReplayStage { } *current_blob_index += entries_len; - res?; + inc_new_counter_info!( "replicate_stage-duration", duration_as_ms(&now.elapsed()) as usize @@ -198,7 +213,7 @@ impl ReplayStage { let mut last_leader_id = Self::get_leader_for_next_tick(&bank); let mut prev_slot = None; let (mut current_slot, mut max_tick_height_for_slot) = { - let tick_height = bank.tick_height(); + let tick_height = bank.active_fork().tick_height(); let leader_scheduler = bank.leader_scheduler.read().unwrap(); let current_slot = leader_scheduler.tick_height_to_slot(tick_height + 1); let first_tick_in_current_slot = current_slot * leader_scheduler.ticks_per_slot; @@ -258,8 +273,16 @@ impl ReplayStage { let entry_len = entries.len(); // Fetch the next entries from the database if !entries.is_empty() { + // TODO: ledger provides from get_slot_entries() + let base_slot = match current_slot.expect("current_slot must exist") { + 0 => 0, + x => x - 1, + }; + if let Err(e) = Self::process_entries( entries, + current_slot.expect("current_slot must exist"), + base_slot, &bank, &cluster_info, voting_keypair.as_ref(), @@ -270,14 +293,23 @@ impl ReplayStage { error!("process_entries failed: {:?}", e); } - let current_tick_height = bank.tick_height(); + let current_tick_height = bank.active_fork().tick_height(); - // We've reached the end of a slot, reset our state and check + // we've reached the end of a slot, reset our state and check // for leader rotation + info!( + "max_tick_height_for_slot: {} current_tick_height: {}", + max_tick_height_for_slot, current_tick_height + ); if max_tick_height_for_slot == current_tick_height { // Check for leader rotation let leader_id = Self::get_leader_for_next_tick(&bank); + info!( + "leader_id: {} last_leader_id: {} my_id: {}", + leader_id, last_leader_id, my_id + ); + // TODO: Remove this soon once we boot the leader from ClusterInfo cluster_info.write().unwrap().set_leader(leader_id); @@ -331,7 +363,7 @@ impl ReplayStage { } fn get_leader_for_next_tick(bank: &Bank) -> Pubkey { - let tick_height = bank.tick_height(); + let tick_height = bank.active_fork().tick_height(); let leader_scheduler = bank.leader_scheduler.read().unwrap(); let slot = leader_scheduler.tick_height_to_slot(tick_height + 1); leader_scheduler @@ -550,7 +582,13 @@ mod test { ); let keypair = voting_keypair.as_ref(); - let vote = VoteTransaction::new_vote(keypair, bank.tick_height(), bank.last_id(), 0); + let vote = VoteTransaction::new_vote( + keypair, + bank.active_fork().tick_height(), + bank.active_fork().last_id(), + 0, + ); + cluster_info_me.write().unwrap().push_vote(vote); info!("Send ReplayStage an entry, should see it on the ledger writer receiver"); @@ -663,7 +701,12 @@ mod test { ); let keypair = voting_keypair.as_ref(); - let vote = VoteTransaction::new_vote(keypair, bank.tick_height(), bank.last_id(), 0); + let vote = VoteTransaction::new_vote( + keypair, + bank.active_fork().tick_height(), + bank.active_fork().last_id(), + 0, + ); cluster_info_me.write().unwrap().push_vote(vote); // Send enough ticks to trigger leader rotation @@ -713,6 +756,7 @@ mod test { #[test] fn test_replay_stage_poh_error_entry_receiver() { + solana_logger::setup(); // Set up dummy node to host a ReplayStage let my_keypair = Keypair::new(); let my_id = my_keypair.pubkey(); @@ -720,21 +764,29 @@ mod test { // Set up the cluster info let cluster_info_me = Arc::new(RwLock::new(ClusterInfo::new(my_node.info.clone()))); let (ledger_entry_sender, _ledger_entry_receiver) = channel(); - let last_entry_id = Hash::default(); + let genesis_block = GenesisBlock::new(10_000).0; + let last_entry_id = genesis_block.last_id(); + let mut current_blob_index = 0; - let mut last_id = Hash::default(); + let bank = Arc::new(Bank::new(&genesis_block)); + let mut entries = Vec::new(); - for _ in 0..5 { - let entry = Entry::new(&mut last_id, 0, 1, vec![]); //just ticks - last_id = entry.id; - entries.push(entry); + { + let mut last_id = last_entry_id; + for _ in 0..5 { + let entry = Entry::new(&last_id, 0, 1, vec![]); //just ticks + last_id = entry.id; + entries.push(entry); + } } let my_keypair = Arc::new(my_keypair); let voting_keypair = Arc::new(VotingKeypair::new_local(&my_keypair)); let res = ReplayStage::process_entries( entries.clone(), - &Arc::new(Bank::new(&GenesisBlock::new(10_000).0)), + 0, + 0, + &bank, &cluster_info_me, Some(&voting_keypair), &ledger_entry_sender, @@ -755,7 +807,9 @@ mod test { let res = ReplayStage::process_entries( entries.clone(), - &Arc::new(Bank::default()), + 0, + 0, + &bank, &cluster_info_me, Some(&voting_keypair), &ledger_entry_sender, diff --git a/src/retransmit_stage.rs b/src/retransmit_stage.rs index da8f4bb1a82b48..06ee7c55ec40ec 100644 --- a/src/retransmit_stage.rs +++ b/src/retransmit_stage.rs @@ -59,9 +59,9 @@ fn retransmit( } } else { //find my index (my ix is the same as the first node with smaller stake) - let my_index = peers - .iter() - .position(|ci| bank.get_balance(&ci.id) <= bank.get_balance(&my_id)); + let my_index = peers.iter().position(|ci| { + bank.root().get_balance_slow(&ci.id) <= bank.root().get_balance_slow(&my_id) + }); //find my layer let locality = ClusterInfo::localize( &layer_indices, diff --git a/src/rpc.rs b/src/rpc.rs index b70ae2a6e7fb50..11558f43d84cf8 100644 --- a/src/rpc.rs +++ b/src/rpc.rs @@ -260,7 +260,7 @@ impl RpcSol for RpcSolImpl { trace!("request_airdrop id={} tokens={}", id, tokens); let pubkey = verify_pubkey(id)?; - let last_id = meta.request_processor.read().unwrap().bank.last_id(); + let last_id = meta.request_processor.read().unwrap().bank.root().last_id(); let transaction = request_airdrop_transaction(&meta.drone_addr, &pubkey, tokens, last_id) .map_err(|err| { info!("request_airdrop_transaction failed: {:?}", err); @@ -371,25 +371,29 @@ impl JsonRpcRequestProcessor { /// Process JSON-RPC request items sent via JSON-RPC. pub fn get_account_info(&self, pubkey: Pubkey) -> Result { self.bank - .get_account(&pubkey) + .active_fork() + .get_account_slow(&pubkey) .ok_or_else(Error::invalid_request) } fn get_balance(&self, pubkey: Pubkey) -> Result { - let val = self.bank.get_balance(&pubkey); + let val = self.bank.active_fork().get_balance_slow(&pubkey); Ok(val) } fn get_confirmation_time(&self) -> Result { Ok(self.bank.confirmation_time()) } fn get_last_id(&self) -> Result { - let id = self.bank.last_id(); + //TODO: least likely to unroll? + let id = self.bank.root().last_id(); Ok(bs58::encode(id).into_string()) } pub fn get_signature_status(&self, signature: Signature) -> Option> { - self.bank.get_signature_status(&signature) + //TODO: which fork? + self.bank.active_fork().get_signature_status(&signature) } fn get_transaction_count(&self) -> Result { - Ok(self.bank.transaction_count() as u64) + //TODO: which fork? + Ok(self.bank.active_fork().transaction_count() as u64) } fn get_storage_mining_last_id(&self) -> Result { let id = self.storage_state.get_last_id(); @@ -466,7 +470,7 @@ mod tests { let (genesis_block, alice) = GenesisBlock::new(10_000); let bank = Bank::new(&genesis_block); - let last_id = bank.last_id(); + let last_id = bank.active_fork().last_id(); let tx = SystemTransaction::new_move(&alice, pubkey, 20, last_id, 0); bank.process_transaction(&tx).expect("process transaction"); @@ -540,7 +544,7 @@ mod tests { let request_processor = JsonRpcRequestProcessor::new(arc_bank.clone(), StorageState::default()); thread::spawn(move || { - let last_id = arc_bank.last_id(); + let last_id = arc_bank.active_fork().last_id(); let tx = SystemTransaction::new_move(&alice, bob_pubkey, 20, last_id, 0); arc_bank .process_transaction(&tx) diff --git a/src/rpc_pubsub.rs b/src/rpc_pubsub.rs index a1b05e96c6649b..968ceeaf83fde5 100644 --- a/src/rpc_pubsub.rs +++ b/src/rpc_pubsub.rs @@ -318,6 +318,7 @@ impl RpcSolPubSubImpl { .read() .unwrap() .bank + .active_fork() .get_signature_status(&signature); if status.is_none() { self.subscription @@ -424,7 +425,7 @@ mod tests { let bob_pubkey = bob.pubkey(); let bank = Bank::new(&genesis_block); let arc_bank = Arc::new(bank); - let last_id = arc_bank.last_id(); + let last_id = arc_bank.active_fork().last_id(); let rpc_bank = Arc::new(RwLock::new(RpcPubSubBank::new(arc_bank.clone()))); let rpc = RpcSolPubSubImpl::new(rpc_bank.clone()); @@ -457,7 +458,7 @@ mod tests { let bob_pubkey = Keypair::new().pubkey(); let bank = Bank::new(&genesis_block); let arc_bank = Arc::new(bank); - let last_id = arc_bank.last_id(); + let last_id = arc_bank.active_fork().last_id(); let (sender, _receiver) = mpsc::channel(1); let session = Arc::new(Session::new(sender)); @@ -511,7 +512,7 @@ mod tests { let executable = false; // TODO let bank = Bank::new(&genesis_block); let arc_bank = Arc::new(bank); - let last_id = arc_bank.last_id(); + let last_id = arc_bank.active_fork().last_id(); let rpc_bank = Arc::new(RwLock::new(RpcPubSubBank::new(arc_bank.clone()))); let rpc = RpcSolPubSubImpl::new(rpc_bank.clone()); @@ -552,7 +553,8 @@ mod tests { let string = receiver.poll(); let expected_userdata = arc_bank - .get_account(&contract_state.pubkey()) + .active_fork() + .get_account_slow(&contract_state.pubkey()) .unwrap() .userdata; @@ -593,7 +595,8 @@ mod tests { // Test signature confirmation notification #2 let string = receiver.poll(); let expected_userdata = arc_bank - .get_account(&contract_state.pubkey()) + .active_fork() + .get_account_slow(&contract_state.pubkey()) .unwrap() .userdata; let expected = json!({ @@ -632,7 +635,8 @@ mod tests { sleep(Duration::from_millis(200)); let expected_userdata = arc_bank - .get_account(&contract_state.pubkey()) + .active_fork() + .get_account_slow(&contract_state.pubkey()) .unwrap() .userdata; let expected = json!({ @@ -707,7 +711,7 @@ mod tests { let (genesis_block, mint_keypair) = GenesisBlock::new(100); let bank = Bank::new(&genesis_block); let alice = Keypair::new(); - let last_id = bank.last_id(); + let last_id = bank.active_fork().last_id(); let tx = SystemTransaction::new_program_account( &mint_keypair, alice.pubkey(), @@ -732,7 +736,10 @@ mod tests { .unwrap() .contains_key(&alice.pubkey())); - let account = bank.get_account(&alice.pubkey()).unwrap(); + let account = bank + .active_fork() + .get_account_slow(&alice.pubkey()) + .unwrap(); subscriptions.check_account(&alice.pubkey(), &account); let string = transport_receiver.poll(); if let Async::Ready(Some(response)) = string.unwrap() { @@ -752,7 +759,7 @@ mod tests { let (genesis_block, mint_keypair) = GenesisBlock::new(100); let bank = Bank::new(&genesis_block); let alice = Keypair::new(); - let last_id = bank.last_id(); + let last_id = bank.active_fork().last_id(); let tx = SystemTransaction::new_move(&mint_keypair, alice.pubkey(), 20, last_id, 0); let signature = tx.signatures[0]; bank.process_transaction(&tx).unwrap(); diff --git a/src/thin_client.rs b/src/thin_client.rs index ae19ff56169a92..6dd23b43732f14 100644 --- a/src/thin_client.rs +++ b/src/thin_client.rs @@ -504,7 +504,6 @@ mod tests { ); let mut client = mk_client(&leader_data); - let transaction_count = client.transaction_count(); assert_eq!(transaction_count, 0); diff --git a/src/tpu.rs b/src/tpu.rs index dc35d1a8419563..61bda3f9112fa1 100644 --- a/src/tpu.rs +++ b/src/tpu.rs @@ -3,6 +3,7 @@ use crate::bank::Bank; use crate::banking_stage::BankingStage; +use crate::blocktree::Blocktree; use crate::broadcast_service::BroadcastService; use crate::cluster_info::ClusterInfo; use crate::cluster_info_vote_listener::ClusterInfoVoteListener; @@ -10,7 +11,6 @@ use crate::fetch_stage::FetchStage; use crate::poh_service::PohServiceConfig; use crate::service::Service; use crate::sigverify_stage::SigVerifyStage; -use crate::streamer::BlobSender; use crate::tpu_forwarder::TpuForwarder; use solana_sdk::hash::Hash; use solana_sdk::pubkey::Pubkey; @@ -84,8 +84,8 @@ impl Tpu { last_entry_id: &Hash, leader_id: Pubkey, to_validator_sender: &TpuRotationSender, - blob_sender: &BlobSender, is_leader: bool, + blocktree: &Arc, ) -> Self { let mut tpu = Self { tpu_mode: None, @@ -105,7 +105,7 @@ impl Tpu { last_entry_id, leader_id, to_validator_sender, - blob_sender, + blocktree, ); } else { tpu.switch_to_forwarder(transactions_sockets, cluster_info); @@ -150,7 +150,7 @@ impl Tpu { last_entry_id: &Hash, leader_id: Pubkey, to_validator_sender: &TpuRotationSender, - blob_sender: &BlobSender, + blocktree: &Arc, ) { self.tpu_mode_close(); @@ -185,8 +185,8 @@ impl Tpu { bank.leader_scheduler.clone(), entry_receiver, max_tick_height, + blocktree, self.exit.clone(), - blob_sender, ); let svcs = LeaderServices::new( diff --git a/src/tvu.rs b/src/tvu.rs index 045ee4689d6755..3e430b158e77f1 100644 --- a/src/tvu.rs +++ b/src/tvu.rs @@ -21,7 +21,6 @@ use crate::replay_stage::ReplayStage; use crate::retransmit_stage::RetransmitStage; use crate::service::Service; use crate::storage_stage::{StorageStage, StorageState}; -use crate::streamer::BlobSender; use crate::tpu::{TpuReturnType, TpuRotationReceiver, TpuRotationSender}; use crate::voting_keypair::VotingKeypair; use solana_sdk::hash::Hash; @@ -79,7 +78,7 @@ impl Tvu { entry_stream: Option<&String>, ledger_signal_sender: SyncSender, ledger_signal_receiver: Receiver, - ) -> (Self, BlobSender) { + ) -> Self { let exit = Arc::new(AtomicBool::new(false)); let keypair: Arc = cluster_info .read() @@ -156,18 +155,15 @@ impl Tvu { &cluster_info, ); - ( - Tvu { - fetch_stage, - retransmit_stage, - replay_stage, - entry_stream_stage, - storage_stage, - exit, - last_entry_id: l_last_entry_id, - }, - blob_fetch_sender, - ) + Tvu { + fetch_stage, + retransmit_stage, + replay_stage, + entry_stream_stage, + storage_stage, + exit, + last_entry_id: l_last_entry_id, + } } #[cfg(test)] @@ -260,7 +256,7 @@ pub mod tests { let vote_account_keypair = Arc::new(Keypair::new()); let voting_keypair = VotingKeypair::new_local(&vote_account_keypair); let (sender, _receiver) = channel(); - let (tvu, _blob_sender) = Tvu::new( + let tvu = Tvu::new( Some(Arc::new(voting_keypair)), &bank, 0, @@ -347,7 +343,7 @@ pub mod tests { let vote_account_keypair = Arc::new(Keypair::new()); let voting_keypair = VotingKeypair::new_local(&vote_account_keypair); let (sender, _) = channel(); - let (tvu, _) = Tvu::new( + let tvu = Tvu::new( Some(Arc::new(voting_keypair)), &bank, 0, @@ -379,7 +375,7 @@ pub mod tests { for i in 0..num_transfers { let entry0 = Entry::new(&cur_hash, 0, i, vec![]); cur_hash = entry0.id; - bank.register_tick(&cur_hash); + bank.active_fork().register_tick(&cur_hash); let entry_tick0 = Entry::new(&cur_hash, 0, i + 1, vec![]); cur_hash = entry_tick0.id; @@ -390,11 +386,11 @@ pub mod tests { cur_hash, 0, ); - bank.register_tick(&cur_hash); + bank.active_fork().register_tick(&cur_hash); let entry_tick1 = Entry::new(&cur_hash, 0, i + 1, vec![]); cur_hash = entry_tick1.id; let entry1 = Entry::new(&cur_hash, 0, i + num_transfers, vec![tx0]); - bank.register_tick(&entry1.id); + bank.active_fork().register_tick(&entry1.id); let entry_tick2 = Entry::new(&entry1.id, 0, i + 1, vec![]); cur_hash = entry_tick2.id; @@ -428,10 +424,10 @@ pub mod tests { trace!("got msg"); } - let alice_balance = bank.get_balance(&mint_keypair.pubkey()); + let alice_balance = bank.active_fork().get_balance_slow(&mint_keypair.pubkey()); assert_eq!(alice_balance, alice_ref_balance); - let bob_balance = bank.get_balance(&bob_keypair.pubkey()); + let bob_balance = bank.active_fork().get_balance_slow(&bob_keypair.pubkey()); assert_eq!(bob_balance, starting_balance - alice_ref_balance); tvu.close().expect("close"); diff --git a/src/vote_signer_proxy.rs b/src/vote_signer_proxy.rs new file mode 100644 index 00000000000000..c0fb34b1e2c2c9 --- /dev/null +++ b/src/vote_signer_proxy.rs @@ -0,0 +1,251 @@ +//! The `vote_signer_proxy` votes on the `last_id` of the bank at a regular cadence + +use crate::bank::Bank; +use crate::bank_state::BankState; +use crate::cluster_info::ClusterInfo; +use crate::counter::Counter; +use crate::jsonrpc_core; +use crate::packet::SharedBlob; +use crate::result::{Error, Result}; +use crate::rpc_request::{RpcClient, RpcRequest}; +use crate::streamer::BlobSender; +use bincode::serialize; +use log::Level; +use solana_sdk::pubkey::Pubkey; +use solana_sdk::signature::{Keypair, KeypairUtil, Signature}; +use solana_sdk::transaction::Transaction; +use solana_sdk::vote_transaction::VoteTransaction; +use solana_vote_signer::rpc::LocalVoteSigner; +use solana_vote_signer::rpc::VoteSigner; +use std::net::SocketAddr; +use std::sync::atomic::AtomicUsize; +use std::sync::{Arc, RwLock}; + +#[derive(Debug, PartialEq, Eq)] +pub enum VoteError { + NoValidSupermajority, + NoLeader, + LeaderInfoNotFound, +} + +pub struct RemoteVoteSigner { + rpc_client: RpcClient, +} + +impl RemoteVoteSigner { + pub fn new(signer: SocketAddr) -> Self { + Self { + rpc_client: RpcClient::new_from_socket(signer), + } + } +} + +impl VoteSigner for RemoteVoteSigner { + fn register( + &self, + pubkey: Pubkey, + sig: &Signature, + msg: &[u8], + ) -> jsonrpc_core::Result { + let params = json!([pubkey, sig, msg]); + let resp = self + .rpc_client + .retry_make_rpc_request(1, &RpcRequest::RegisterNode, Some(params), 5) + .unwrap(); + let vote_account: Pubkey = serde_json::from_value(resp).unwrap(); + Ok(vote_account) + } + fn sign(&self, pubkey: Pubkey, sig: &Signature, msg: &[u8]) -> jsonrpc_core::Result { + let params = json!([pubkey, sig, msg]); + let resp = self + .rpc_client + .retry_make_rpc_request(1, &RpcRequest::SignVote, Some(params), 0) + .unwrap(); + let vote_signature: Signature = serde_json::from_value(resp).unwrap(); + Ok(vote_signature) + } + fn deregister(&self, pubkey: Pubkey, sig: &Signature, msg: &[u8]) -> jsonrpc_core::Result<()> { + let params = json!([pubkey, sig, msg]); + let _resp = self + .rpc_client + .retry_make_rpc_request(1, &RpcRequest::DeregisterNode, Some(params), 5) + .unwrap(); + Ok(()) + } +} + +impl KeypairUtil for VoteSignerProxy { + /// Return a local VoteSignerProxy with a new keypair. Used for unit-tests. + fn new() -> Self { + Self::new_local(&Arc::new(Keypair::new())) + } + + /// Return the public key of the keypair used to sign votes + fn pubkey(&self) -> Pubkey { + self.vote_account + } + + fn sign_message(&self, msg: &[u8]) -> Signature { + let sig = self.keypair.sign_message(msg); + self.signer.sign(self.keypair.pubkey(), &sig, &msg).unwrap() + } +} + +pub struct VoteSignerProxy { + keypair: Arc, + signer: Box, + vote_account: Pubkey, + last_leader: RwLock, + unsent_votes: RwLock>, +} + +impl VoteSignerProxy { + pub fn new_with_signer(keypair: &Arc, signer: Box) -> Self { + let msg = "Registering a new node"; + let sig = keypair.sign_message(msg.as_bytes()); + let vote_account = signer + .register(keypair.pubkey(), &sig, msg.as_bytes()) + .unwrap(); + Self { + keypair: keypair.clone(), + signer, + vote_account, + last_leader: RwLock::new(vote_account), + unsent_votes: RwLock::new(vec![]), + } + } + + pub fn new_local(keypair: &Arc) -> Self { + Self::new_with_signer(keypair, Box::new(LocalVoteSigner::default())) + } + + pub fn send_validator_vote( + &self, + bank: &BankState, + cluster_info: &Arc>, + vote_blob_sender: &BlobSender, + ) -> Result<()> { + { + let (leader, _) = bank.get_current_leader().unwrap(); + + let mut old_leader = self.last_leader.write().unwrap(); + + if leader != *old_leader { + *old_leader = leader; + self.unsent_votes.write().unwrap().clear(); + } + inc_new_counter_info!( + "validator-total_pending_votes", + self.unsent_votes.read().unwrap().len() + ); + } + + let tx = Transaction::vote_new(self, bank.tick_height(), bank.last_id(), 0); + + match VoteSignerProxy::get_leader_tpu(&bank, cluster_info) { + Ok(tpu) => { + self.unsent_votes.write().unwrap().retain(|old_tx| { + if let Ok(shared_blob) = self.new_signed_vote_blob(old_tx, tpu) { + inc_new_counter_info!("validator-pending_vote_sent", 1); + inc_new_counter_info!("validator-vote_sent", 1); + vote_blob_sender.send(vec![shared_blob]).unwrap(); + } + false + }); + if let Ok(shared_blob) = self.new_signed_vote_blob(&tx, tpu) { + inc_new_counter_info!("validator-vote_sent", 1); + vote_blob_sender.send(vec![shared_blob])?; + } + } + Err(_) => { + self.unsent_votes.write().unwrap().push(tx); + inc_new_counter_info!("validator-new_pending_vote", 1); + } + }; + + Ok(()) + } + + fn new_signed_vote_blob(&self, tx: &Transaction, leader_tpu: SocketAddr) -> Result { + let shared_blob = SharedBlob::default(); + { + let mut blob = shared_blob.write().unwrap(); + let bytes = serialize(&tx)?; + let len = bytes.len(); + blob.data[..len].copy_from_slice(&bytes); + blob.meta.set_addr(&leader_tpu); + blob.meta.size = len; + }; + + Ok(shared_blob) + } + + fn get_leader_tpu(bank: &Bank, cluster_info: &Arc>) -> Result { + let leader_id = match bank.get_current_leader() { + Some((leader_id, _)) => leader_id, + None => return Err(Error::VoteError(VoteError::NoLeader)), + }; + + let rcluster_info = cluster_info.read().unwrap(); + let leader_tpu = rcluster_info.lookup(leader_id).map(|leader| leader.tpu); + if let Some(leader_tpu) = leader_tpu { + Ok(leader_tpu) + } else { + Err(Error::VoteError(VoteError::LeaderInfoNotFound)) + } + } +} + +#[cfg(test)] +mod test { + use crate::bank::Bank; + use crate::cluster_info::{ClusterInfo, Node}; + use crate::genesis_block::GenesisBlock; + use crate::vote_signer_proxy::VoteSignerProxy; + use solana_sdk::signature::{Keypair, KeypairUtil}; + use std::sync::mpsc::channel; + use std::sync::{Arc, RwLock}; + use std::time::Duration; + + #[test] + pub fn test_pending_votes() { + solana_logger::setup(); + + let signer = VoteSignerProxy::new_local(&Arc::new(Keypair::new())); + + // Set up dummy node to host a ReplayStage + let my_keypair = Keypair::new(); + let my_id = my_keypair.pubkey(); + let my_node = Node::new_localhost_with_pubkey(my_id); + let cluster_info = Arc::new(RwLock::new(ClusterInfo::new(my_node.info.clone()))); + + let (genesis_block, _) = GenesisBlock::new_with_leader(10000, my_id, 500); + let bank = Bank::new(&genesis_block); + let (sender, receiver) = channel(); + + assert_eq!(signer.unsent_votes.read().unwrap().len(), 0); + signer + .send_validator_vote(&bank, &cluster_info, &sender) + .unwrap(); + assert_eq!(signer.unsent_votes.read().unwrap().len(), 1); + assert!(receiver.recv_timeout(Duration::from_millis(400)).is_err()); + + signer + .send_validator_vote(&bank, &cluster_info, &sender) + .unwrap(); + assert_eq!(signer.unsent_votes.read().unwrap().len(), 2); + assert!(receiver.recv_timeout(Duration::from_millis(400)).is_err()); + + bank.leader_scheduler + .write() + .unwrap() + .use_only_bootstrap_leader = true; + bank.leader_scheduler.write().unwrap().bootstrap_leader = my_id; + assert!(signer + .send_validator_vote(&bank, &cluster_info, &sender) + .is_ok()); + receiver.recv_timeout(Duration::from_millis(400)).unwrap(); + + assert_eq!(signer.unsent_votes.read().unwrap().len(), 0); + } +} diff --git a/src/window_service.rs b/src/window_service.rs index b301192b9c3407..4b61d25d95007c 100644 --- a/src/window_service.rs +++ b/src/window_service.rs @@ -207,17 +207,17 @@ mod test { Arc::new(RwLock::new(leader_schedule)), exit.clone(), ); + let num_blobs = 7; let t_responder = { let (s_responder, r_responder) = channel(); let blob_sockets: Vec> = leader_node.sockets.tvu.into_iter().map(Arc::new).collect(); let t_responder = responder("window_send_test", blob_sockets[0].clone(), r_responder); - let num_blobs_to_make = 10; let gossip_address = &leader_node.info.gossip; let msgs = make_consecutive_blobs( &me_id, - num_blobs_to_make, + num_blobs as u64, 0, Hash::default(), &gossip_address, @@ -229,21 +229,20 @@ mod test { t_responder }; - let max_attempts = 10; let mut num_attempts = 0; - loop { - assert!(num_attempts != max_attempts); + let mut received = 0; + while num_attempts < 10 && received < num_blobs { let mut q = r_retransmit.recv().unwrap(); while let Ok(mut nq) = r_retransmit.try_recv() { q.append(&mut nq); } - if q.len() != 10 { + received += q.len(); + if received < num_blobs { sleep(Duration::from_millis(100)); - } else { - break; } num_attempts += 1; } + assert_eq!(received, num_blobs); exit.store(true, Ordering::Relaxed); t_receiver.join().expect("join"); diff --git a/tests/multinode.rs b/tests/multinode.rs index 62b96f1dca4bdb..dd78d6b1c1e9e1 100644 --- a/tests/multinode.rs +++ b/tests/multinode.rs @@ -943,7 +943,7 @@ fn test_leader_to_validator_transition() { .0; assert_eq!( - bank.tick_height(), + bank.active_fork().tick_height(), 2 * fullnode_config.leader_scheduler_config.ticks_per_slot - 1 ); remove_dir_all(leader_ledger_path).unwrap();