* drop parents once merged (#2930)
* add bank.id() which can be used by BankForks, blocktree_processor * add bank.hash(), make hash_internal_state() private * add bank.freeze()/is_frozen(), also useful for blocktree_processor, eventual freeze()ing in replay
This commit is contained in:
@ -10,7 +10,7 @@ use crate::runtime::{self, RuntimeError};
|
|||||||
use crate::status_cache::StatusCache;
|
use crate::status_cache::StatusCache;
|
||||||
use bincode::serialize;
|
use bincode::serialize;
|
||||||
use hashbrown::HashMap;
|
use hashbrown::HashMap;
|
||||||
use log::{debug, info, Level};
|
use log::{debug, info, warn, Level};
|
||||||
use solana_metrics::counter::Counter;
|
use solana_metrics::counter::Counter;
|
||||||
use solana_sdk::account::Account;
|
use solana_sdk::account::Account;
|
||||||
use solana_sdk::bpf_loader;
|
use solana_sdk::bpf_loader;
|
||||||
@ -90,11 +90,17 @@ pub struct Bank {
|
|||||||
last_id_queue: RwLock<LastIdQueue>,
|
last_id_queue: RwLock<LastIdQueue>,
|
||||||
|
|
||||||
/// Previous checkpoint of this bank
|
/// Previous checkpoint of this bank
|
||||||
parent: Option<Arc<Bank>>,
|
parent: RwLock<Option<Arc<Bank>>>,
|
||||||
|
|
||||||
/// Hash of this Bank's state. Only meaningful after freezing it via `new_from_parent()`.
|
/// Hash of this Bank's state. Only meaningful after freezing.
|
||||||
hash: RwLock<Hash>,
|
hash: RwLock<Hash>,
|
||||||
|
|
||||||
|
/// Hash of this Bank's parent's state
|
||||||
|
parent_hash: Hash,
|
||||||
|
|
||||||
|
/// Bank fork id
|
||||||
|
id: u64,
|
||||||
|
|
||||||
/// The number of ticks in each slot.
|
/// The number of ticks in each slot.
|
||||||
ticks_per_slot: u64,
|
ticks_per_slot: u64,
|
||||||
|
|
||||||
@ -114,25 +120,56 @@ impl Bank {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Create a new bank that points to an immutable checkpoint of another bank.
|
/// Create a new bank that points to an immutable checkpoint of another bank.
|
||||||
pub fn new_from_parent(parent: &Arc<Bank>) -> Self {
|
pub fn new_from_parent_and_id(parent: &Arc<Bank>, id: u64) -> Self {
|
||||||
|
parent.freeze();
|
||||||
|
|
||||||
let mut bank = Self::default();
|
let mut bank = Self::default();
|
||||||
bank.last_id_queue = RwLock::new(parent.last_id_queue.read().unwrap().clone());
|
bank.last_id_queue = RwLock::new(parent.last_id_queue.read().unwrap().clone());
|
||||||
bank.ticks_per_slot = parent.ticks_per_slot;
|
bank.ticks_per_slot = parent.ticks_per_slot;
|
||||||
bank.slots_per_epoch = parent.slots_per_epoch;
|
bank.slots_per_epoch = parent.slots_per_epoch;
|
||||||
bank.stakers_slot_offset = parent.stakers_slot_offset;
|
bank.stakers_slot_offset = parent.stakers_slot_offset;
|
||||||
|
|
||||||
bank.parent = Some(parent.clone());
|
bank.id = id;
|
||||||
if *parent.hash.read().unwrap() == Hash::default() {
|
bank.parent = RwLock::new(Some(parent.clone()));
|
||||||
*parent.hash.write().unwrap() = parent.hash_internal_state();
|
bank.parent_hash = parent.hash();
|
||||||
}
|
|
||||||
bank
|
bank
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Create a new bank that points to an immutable checkpoint of another bank.
|
||||||
|
/// TODO: remove me in favor of _and_id(), id should not be an assumed value
|
||||||
|
pub fn new_from_parent(parent: &Arc<Bank>) -> Self {
|
||||||
|
Self::new_from_parent_and_id(parent, parent.id() + 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn id(&self) -> u64 {
|
||||||
|
self.id
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn hash(&self) -> Hash {
|
||||||
|
*self.hash.read().unwrap()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_frozen(&self) -> bool {
|
||||||
|
*self.hash.read().unwrap() != Hash::default()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn freeze(&self) {
|
||||||
|
let mut hash = self.hash.write().unwrap();
|
||||||
|
|
||||||
|
if *hash == Hash::default() {
|
||||||
|
// freeze is a one-way trip, idempotent
|
||||||
|
*hash = self.hash_internal_state();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// merge (i.e. pull) the parent's state up into this Bank,
|
/// merge (i.e. pull) the parent's state up into this Bank,
|
||||||
/// this Bank becomes a root
|
/// this Bank becomes a root
|
||||||
pub fn merge_parents(&mut self) {
|
pub fn merge_parents(&self) {
|
||||||
|
self.freeze();
|
||||||
|
|
||||||
let parents = self.parents();
|
let parents = self.parents();
|
||||||
self.parent = None;
|
*self.parent.write().unwrap() = None;
|
||||||
|
|
||||||
let parent_accounts: Vec<_> = parents.iter().map(|b| &b.accounts).collect();
|
let parent_accounts: Vec<_> = parents.iter().map(|b| &b.accounts).collect();
|
||||||
self.accounts.merge_parents(&parent_accounts);
|
self.accounts.merge_parents(&parent_accounts);
|
||||||
@ -149,11 +186,11 @@ impl Bank {
|
|||||||
|
|
||||||
/// Return the more recent checkpoint of this bank instance.
|
/// Return the more recent checkpoint of this bank instance.
|
||||||
pub fn parent(&self) -> Option<Arc<Bank>> {
|
pub fn parent(&self) -> Option<Arc<Bank>> {
|
||||||
self.parent.clone()
|
self.parent.read().unwrap().clone()
|
||||||
}
|
}
|
||||||
/// Returns whether this bank is the root
|
/// Returns whether this bank is the root
|
||||||
pub fn is_root(&self) -> bool {
|
pub fn is_root(&self) -> bool {
|
||||||
self.parent.is_none()
|
self.parent.read().unwrap().is_none()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn process_genesis_block(&mut self, genesis_block: &GenesisBlock) {
|
fn process_genesis_block(&mut self, genesis_block: &GenesisBlock) {
|
||||||
@ -264,6 +301,11 @@ impl Bank {
|
|||||||
/// the oldest ones once its internal cache is full. Once boot, the
|
/// the oldest ones once its internal cache is full. Once boot, the
|
||||||
/// bank will reject transactions using that `last_id`.
|
/// bank will reject transactions using that `last_id`.
|
||||||
pub fn register_tick(&self, last_id: &Hash) {
|
pub fn register_tick(&self, last_id: &Hash) {
|
||||||
|
if self.is_frozen() {
|
||||||
|
warn!("=========== FIXME: working on a frozen bank! ================");
|
||||||
|
}
|
||||||
|
// TODO: put this assert back in
|
||||||
|
// assert!(!self.is_frozen());
|
||||||
let current_tick_height = {
|
let current_tick_height = {
|
||||||
//atomic register and read the tick
|
//atomic register and read the tick
|
||||||
let mut last_id_queue = self.last_id_queue.write().unwrap();
|
let mut last_id_queue = self.last_id_queue.write().unwrap();
|
||||||
@ -289,6 +331,11 @@ impl Bank {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn lock_accounts(&self, txs: &[Transaction]) -> Vec<Result<()>> {
|
pub fn lock_accounts(&self, txs: &[Transaction]) -> Vec<Result<()>> {
|
||||||
|
if self.is_frozen() {
|
||||||
|
warn!("=========== FIXME: working on a frozen bank! ================");
|
||||||
|
}
|
||||||
|
// TODO: put this assert back in
|
||||||
|
// assert!(!self.is_frozen());
|
||||||
self.accounts.lock_accounts(txs)
|
self.accounts.lock_accounts(txs)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -481,6 +528,11 @@ impl Bank {
|
|||||||
loaded_accounts: &[Result<(InstructionAccounts, InstructionLoaders)>],
|
loaded_accounts: &[Result<(InstructionAccounts, InstructionLoaders)>],
|
||||||
executed: &[Result<()>],
|
executed: &[Result<()>],
|
||||||
) -> Vec<Result<()>> {
|
) -> Vec<Result<()>> {
|
||||||
|
if self.is_frozen() {
|
||||||
|
warn!("=========== FIXME: working on a frozen bank! ================");
|
||||||
|
}
|
||||||
|
// TODO: put this assert back in
|
||||||
|
// assert!(!self.is_frozen());
|
||||||
let now = Instant::now();
|
let now = Instant::now();
|
||||||
self.accounts
|
self.accounts
|
||||||
.store_accounts(self.is_root(), txs, executed, loaded_accounts);
|
.store_accounts(self.is_root(), txs, executed, loaded_accounts);
|
||||||
@ -612,20 +664,16 @@ impl Bank {
|
|||||||
|
|
||||||
/// Hash the `accounts` HashMap. This represents a validator's interpretation
|
/// Hash the `accounts` HashMap. This represents a validator's interpretation
|
||||||
/// of the delta of the ledger since the last vote and up to now
|
/// of the delta of the ledger since the last vote and up to now
|
||||||
pub fn hash_internal_state(&self) -> Hash {
|
fn hash_internal_state(&self) -> Hash {
|
||||||
// If there are no accounts, return the same hash as we did before
|
// If there are no accounts, return the same hash as we did before
|
||||||
// checkpointing.
|
// checkpointing.
|
||||||
let accounts = &self.accounts.accounts_db.read().unwrap().accounts;
|
let accounts = &self.accounts.accounts_db.read().unwrap().accounts;
|
||||||
let parent_hash = match &self.parent {
|
|
||||||
None => Hash::default(),
|
|
||||||
Some(parent) => *parent.hash.read().unwrap(),
|
|
||||||
};
|
|
||||||
if accounts.is_empty() {
|
if accounts.is_empty() {
|
||||||
return parent_hash;
|
return self.parent_hash;
|
||||||
}
|
}
|
||||||
|
|
||||||
let accounts_delta_hash = self.accounts.hash_internal_state();
|
let accounts_delta_hash = self.accounts.hash_internal_state();
|
||||||
extend_and_hash(&parent_hash, &serialize(&accounts_delta_hash).unwrap())
|
extend_and_hash(&self.parent_hash, &serialize(&accounts_delta_hash).unwrap())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn vote_states<F>(&self, cond: F) -> Vec<VoteState>
|
pub fn vote_states<F>(&self, cond: F) -> Vec<VoteState>
|
||||||
@ -1394,7 +1442,7 @@ mod tests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_hash_internal_state() {
|
fn test_bank_hash_internal_state() {
|
||||||
let (genesis_block, mint_keypair) = GenesisBlock::new(2_000);
|
let (genesis_block, mint_keypair) = GenesisBlock::new(2_000);
|
||||||
let bank0 = Bank::new(&genesis_block);
|
let bank0 = Bank::new(&genesis_block);
|
||||||
let bank1 = Bank::new(&genesis_block);
|
let bank1 = Bank::new(&genesis_block);
|
||||||
@ -1417,12 +1465,28 @@ mod tests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_hash_internal_state_parents() {
|
fn test_hash_internal_state_genesis() {
|
||||||
let bank0 = Bank::new(&GenesisBlock::new(10).0);
|
let bank0 = Bank::new(&GenesisBlock::new(10).0);
|
||||||
let bank1 = Bank::new(&GenesisBlock::new(20).0);
|
let bank1 = Bank::new(&GenesisBlock::new(20).0);
|
||||||
assert_ne!(bank0.hash_internal_state(), bank1.hash_internal_state());
|
assert_ne!(bank0.hash_internal_state(), bank1.hash_internal_state());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_bank_hash_internal_state_merge_parents() {
|
||||||
|
let bank0 = Arc::new(Bank::new(&GenesisBlock::new(10).0));
|
||||||
|
let bank1 = Bank::new_from_parent_and_id(&bank0, 1);
|
||||||
|
|
||||||
|
// no delta in bank1, hashes match
|
||||||
|
assert_eq!(bank0.hash_internal_state(), bank1.hash_internal_state());
|
||||||
|
|
||||||
|
// remove parent
|
||||||
|
bank1.merge_parents();
|
||||||
|
assert!(bank1.parents().is_empty());
|
||||||
|
|
||||||
|
// hash should still match
|
||||||
|
assert_eq!(bank0.hash(), bank1.hash());
|
||||||
|
}
|
||||||
|
|
||||||
/// Verifies that last ids and accounts are correctly referenced from parent
|
/// Verifies that last ids and accounts are correctly referenced from parent
|
||||||
#[test]
|
#[test]
|
||||||
fn test_bank_merge_parents() {
|
fn test_bank_merge_parents() {
|
||||||
@ -1439,7 +1503,7 @@ mod tests {
|
|||||||
0,
|
0,
|
||||||
);
|
);
|
||||||
assert_eq!(parent.process_transaction(&tx_move_mint_to_1), Ok(()));
|
assert_eq!(parent.process_transaction(&tx_move_mint_to_1), Ok(()));
|
||||||
let mut bank = Bank::new_from_parent(&parent);
|
let bank = Bank::new_from_parent(&parent);
|
||||||
let tx_move_1_to_2 =
|
let tx_move_1_to_2 =
|
||||||
SystemTransaction::new_move(&key1, key2.pubkey(), 1, genesis_block.last_id(), 0);
|
SystemTransaction::new_move(&key1, key2.pubkey(), 1, genesis_block.last_id(), 0);
|
||||||
assert_eq!(bank.process_transaction(&tx_move_1_to_2), Ok(()));
|
assert_eq!(bank.process_transaction(&tx_move_1_to_2), Ok(()));
|
||||||
|
Reference in New Issue
Block a user