implements copy-on-write for vote-accounts (#19362)

Bank::vote_accounts redundantly clones vote-accounts HashMap even though
an immutable reference will suffice:
https://github.com/solana-labs/solana/blob/95c998a19/runtime/src/bank.rs#L5174-L5186

This commit implements copy-on-write semantics for vote-accounts by
wrapping the underlying HashMap in Arc<...>.
This commit is contained in:
behzad nouri
2021-08-30 15:54:01 +00:00
committed by GitHub
parent f19ff84593
commit 8ad52fa095
11 changed files with 215 additions and 175 deletions

View File

@ -2252,25 +2252,21 @@ impl Bank {
) -> Option<UnixTimestamp> {
let mut get_timestamp_estimate_time = Measure::start("get_timestamp_estimate");
let slots_per_epoch = self.epoch_schedule().slots_per_epoch;
let recent_timestamps =
self.vote_accounts()
.into_iter()
.filter_map(|(pubkey, (_, account))| {
let vote_state = account.vote_state();
let vote_state = vote_state.as_ref().ok()?;
let slot_delta = self.slot().checked_sub(vote_state.last_timestamp.slot)?;
if slot_delta <= slots_per_epoch {
Some((
pubkey,
(
vote_state.last_timestamp.slot,
vote_state.last_timestamp.timestamp,
),
))
} else {
None
}
});
let vote_accounts = self.vote_accounts();
let recent_timestamps = vote_accounts.iter().filter_map(|(pubkey, (_, account))| {
let vote_state = account.vote_state();
let vote_state = vote_state.as_ref().ok()?;
let slot_delta = self.slot().checked_sub(vote_state.last_timestamp.slot)?;
(slot_delta <= slots_per_epoch).then(|| {
(
*pubkey,
(
vote_state.last_timestamp.slot,
vote_state.last_timestamp.timestamp,
),
)
})
});
let slot_duration = Duration::from_nanos(self.ns_per_slot as u64);
let epoch = self.epoch_schedule().get_epoch(self.slot());
let stakes = self.epoch_vote_accounts(epoch)?;
@ -3716,24 +3712,25 @@ impl Bank {
//
// Ref: collect_fees
#[allow(clippy::needless_collect)]
fn distribute_rent_to_validators<I>(&self, vote_accounts: I, rent_to_be_distributed: u64)
where
I: IntoIterator<Item = (Pubkey, (u64, VoteAccount))>,
{
fn distribute_rent_to_validators(
&self,
vote_accounts: &HashMap<Pubkey, (/*stake:*/ u64, VoteAccount)>,
rent_to_be_distributed: u64,
) {
let mut total_staked = 0;
// Collect the stake associated with each validator.
// Note that a validator may be present in this vector multiple times if it happens to have
// more than one staked vote account somehow
let mut validator_stakes = vote_accounts
.into_iter()
.iter()
.filter_map(|(_vote_pubkey, (staked, account))| {
if staked == 0 {
if *staked == 0 {
None
} else {
total_staked += staked;
total_staked += *staked;
let node_pubkey = account.vote_state().as_ref().ok()?.node_pubkey;
Some((node_pubkey, staked))
Some((node_pubkey, *staked))
}
})
.collect::<Vec<(Pubkey, u64)>>();
@ -3848,7 +3845,7 @@ impl Bank {
return;
}
self.distribute_rent_to_validators(self.vote_accounts(), rent_to_be_distributed);
self.distribute_rent_to_validators(&self.vote_accounts(), rent_to_be_distributed);
}
fn collect_rent(
@ -5198,26 +5195,15 @@ impl Bank {
/// current vote accounts for this bank along with the stake
/// attributed to each account
/// Note: This clones the entire vote-accounts hashmap. For a single
/// account lookup use get_vote_account instead.
pub fn vote_accounts(&self) -> Vec<(Pubkey, (/*stake:*/ u64, VoteAccount))> {
self.stakes
.read()
.unwrap()
.vote_accounts()
.iter()
.map(|(k, v)| (*k, v.clone()))
.collect()
pub fn vote_accounts(&self) -> Arc<HashMap<Pubkey, (/*stake:*/ u64, VoteAccount)>> {
let stakes = self.stakes.read().unwrap();
Arc::from(stakes.vote_accounts())
}
/// Vote account for the given vote account pubkey along with the stake.
pub fn get_vote_account(&self, vote_account: &Pubkey) -> Option<(/*stake:*/ u64, VoteAccount)> {
self.stakes
.read()
.unwrap()
.vote_accounts()
.get(vote_account)
.cloned()
let stakes = self.stakes.read().unwrap();
stakes.vote_accounts().get(vote_account).cloned()
}
/// Get the EpochStakes for a given epoch
@ -5239,9 +5225,8 @@ impl Bank {
&self,
epoch: Epoch,
) -> Option<&HashMap<Pubkey, (u64, VoteAccount)>> {
self.epoch_stakes
.get(&epoch)
.map(|epoch_stakes| Stakes::vote_accounts(epoch_stakes.stakes()))
let epoch_stakes = self.epoch_stakes.get(&epoch)?.stakes();
Some(epoch_stakes.vote_accounts().as_ref())
}
/// Get the fixed authorized voter for the given vote account for the
@ -6679,7 +6664,7 @@ pub(crate) mod tests {
let bank = Bank::new_for_tests(&genesis_config);
let old_validator_lamports = bank.get_balance(&validator_pubkey);
bank.distribute_rent_to_validators(bank.vote_accounts(), RENT_TO_BE_DISTRIBUTED);
bank.distribute_rent_to_validators(&bank.vote_accounts(), RENT_TO_BE_DISTRIBUTED);
let new_validator_lamports = bank.get_balance(&validator_pubkey);
assert_eq!(
new_validator_lamports,
@ -6693,7 +6678,7 @@ pub(crate) mod tests {
let bank = std::panic::AssertUnwindSafe(Bank::new_for_tests(&genesis_config));
let old_validator_lamports = bank.get_balance(&validator_pubkey);
let new_validator_lamports = std::panic::catch_unwind(|| {
bank.distribute_rent_to_validators(bank.vote_accounts(), RENT_TO_BE_DISTRIBUTED);
bank.distribute_rent_to_validators(&bank.vote_accounts(), RENT_TO_BE_DISTRIBUTED);
bank.get_balance(&validator_pubkey)
});
@ -9531,7 +9516,7 @@ pub(crate) mod tests {
bank.process_transaction(&transaction).unwrap();
let vote_accounts = bank.vote_accounts().into_iter().collect::<HashMap<_, _>>();
let vote_accounts = bank.vote_accounts();
assert_eq!(vote_accounts.len(), 2);
@ -9906,7 +9891,10 @@ pub(crate) mod tests {
}
// Non-native loader accounts can not be used for instruction processing
assert!(bank.stakes.read().unwrap().vote_accounts().is_empty());
{
let stakes = bank.stakes.read().unwrap();
assert!(stakes.vote_accounts().as_ref().is_empty());
}
assert!(bank.stakes.read().unwrap().stake_delegations().is_empty());
assert_eq!(bank.calculate_capitalization(true), bank.capitalization());
@ -9916,13 +9904,19 @@ pub(crate) mod tests {
.fetch_add(vote_account.lamports() + stake_account.lamports(), Relaxed);
bank.store_account(&vote_id, &vote_account);
bank.store_account(&stake_id, &stake_account);
assert!(!bank.stakes.read().unwrap().vote_accounts().is_empty());
{
let stakes = bank.stakes.read().unwrap();
assert!(!stakes.vote_accounts().as_ref().is_empty());
}
assert!(!bank.stakes.read().unwrap().stake_delegations().is_empty());
assert_eq!(bank.calculate_capitalization(true), bank.capitalization());
bank.add_builtin("mock_program1", vote_id, mock_ix_processor);
bank.add_builtin("mock_program2", stake_id, mock_ix_processor);
assert!(bank.stakes.read().unwrap().vote_accounts().is_empty());
{
let stakes = bank.stakes.read().unwrap();
assert!(stakes.vote_accounts().as_ref().is_empty());
}
assert!(bank.stakes.read().unwrap().stake_delegations().is_empty());
assert_eq!(bank.calculate_capitalization(true), bank.capitalization());
assert_eq!(
@ -9942,7 +9936,10 @@ pub(crate) mod tests {
bank.update_accounts_hash();
let new_hash = bank.get_accounts_hash();
assert_eq!(old_hash, new_hash);
assert!(bank.stakes.read().unwrap().vote_accounts().is_empty());
{
let stakes = bank.stakes.read().unwrap();
assert!(stakes.vote_accounts().as_ref().is_empty());
}
assert!(bank.stakes.read().unwrap().stake_delegations().is_empty());
assert_eq!(bank.calculate_capitalization(true), bank.capitalization());
assert_eq!(

View File

@ -24,9 +24,9 @@ pub struct EpochStakes {
impl EpochStakes {
pub fn new(stakes: &Stakes, leader_schedule_epoch: Epoch) -> Self {
let epoch_vote_accounts = Stakes::vote_accounts(stakes);
let epoch_vote_accounts = stakes.vote_accounts();
let (total_stake, node_id_to_vote_accounts, epoch_authorized_voters) =
Self::parse_epoch_vote_accounts(epoch_vote_accounts, leader_schedule_epoch);
Self::parse_epoch_vote_accounts(epoch_vote_accounts.as_ref(), leader_schedule_epoch);
Self {
stakes: Arc::new(stakes.clone()),
total_stake,
@ -52,7 +52,8 @@ impl EpochStakes {
}
pub fn vote_account_stake(&self, vote_account: &Pubkey) -> u64 {
Stakes::vote_accounts(&self.stakes)
self.stakes
.vote_accounts()
.get(vote_account)
.map(|(stake, _)| *stake)
.unwrap_or(0)

View File

@ -196,8 +196,8 @@ impl Stakes {
}
}
pub fn vote_accounts(&self) -> &HashMap<Pubkey, (u64, VoteAccount)> {
self.vote_accounts.as_ref()
pub fn vote_accounts(&self) -> &VoteAccounts {
&self.vote_accounts
}
pub fn stake_delegations(&self) -> &HashMap<Pubkey, Delegation> {

View File

@ -31,7 +31,7 @@ struct VoteAccountInner {
#[derive(Debug, AbiExample)]
pub struct VoteAccounts {
vote_accounts: HashMap<Pubkey, (/*stake:*/ u64, VoteAccount)>,
vote_accounts: Arc<HashMap<Pubkey, (/*stake:*/ u64, VoteAccount)>>,
// Inner Arc is meant to implement copy-on-write semantics as opposed to
// sharing mutations (hence RwLock<Arc<...>> instead of Arc<RwLock<...>>).
staked_nodes: RwLock<
@ -46,7 +46,7 @@ pub struct VoteAccounts {
}
impl VoteAccount {
pub fn lamports(&self) -> u64 {
pub(crate) fn lamports(&self) -> u64 {
self.0.account.lamports
}
@ -83,37 +83,43 @@ impl VoteAccounts {
self.staked_nodes.read().unwrap().clone()
}
pub fn iter(&self) -> impl Iterator<Item = (&Pubkey, &(u64, VoteAccount))> {
pub fn get(&self, pubkey: &Pubkey) -> Option<&(/*stake:*/ u64, VoteAccount)> {
self.vote_accounts.get(pubkey)
}
pub(crate) fn iter(&self) -> impl Iterator<Item = (&Pubkey, &(u64, VoteAccount))> {
self.vote_accounts.iter()
}
pub fn insert(&mut self, pubkey: Pubkey, (stake, vote_account): (u64, VoteAccount)) {
pub(crate) fn insert(&mut self, pubkey: Pubkey, (stake, vote_account): (u64, VoteAccount)) {
self.add_node_stake(stake, &vote_account);
if let Some((stake, vote_account)) =
self.vote_accounts.insert(pubkey, (stake, vote_account))
{
let vote_accounts = Arc::make_mut(&mut self.vote_accounts);
if let Some((stake, vote_account)) = vote_accounts.insert(pubkey, (stake, vote_account)) {
self.sub_node_stake(stake, &vote_account);
}
}
pub fn remove(&mut self, pubkey: &Pubkey) -> Option<(u64, VoteAccount)> {
let value = self.vote_accounts.remove(pubkey);
if let Some((stake, ref vote_account)) = value {
pub(crate) fn remove(&mut self, pubkey: &Pubkey) -> Option<(u64, VoteAccount)> {
let vote_accounts = Arc::make_mut(&mut self.vote_accounts);
let entry = vote_accounts.remove(pubkey);
if let Some((stake, ref vote_account)) = entry {
self.sub_node_stake(stake, vote_account);
}
value
entry
}
pub fn add_stake(&mut self, pubkey: &Pubkey, delta: u64) {
if let Some((stake, vote_account)) = self.vote_accounts.get_mut(pubkey) {
pub(crate) fn add_stake(&mut self, pubkey: &Pubkey, delta: u64) {
let vote_accounts = Arc::make_mut(&mut self.vote_accounts);
if let Some((stake, vote_account)) = vote_accounts.get_mut(pubkey) {
*stake += delta;
let vote_account = vote_account.clone();
self.add_node_stake(delta, &vote_account);
}
}
pub fn sub_stake(&mut self, pubkey: &Pubkey, delta: u64) {
if let Some((stake, vote_account)) = self.vote_accounts.get_mut(pubkey) {
pub(crate) fn sub_stake(&mut self, pubkey: &Pubkey, delta: u64) {
let vote_accounts = Arc::make_mut(&mut self.vote_accounts);
if let Some((stake, vote_account)) = vote_accounts.get_mut(pubkey) {
*stake = stake
.checked_sub(delta)
.expect("subtraction value exceeds account's stake");
@ -221,7 +227,7 @@ impl PartialEq<VoteAccountInner> for VoteAccountInner {
impl Default for VoteAccounts {
fn default() -> Self {
Self {
vote_accounts: HashMap::default(),
vote_accounts: Arc::default(),
staked_nodes: RwLock::default(),
staked_nodes_once: Once::new(),
}
@ -257,8 +263,8 @@ impl PartialEq<VoteAccounts> for VoteAccounts {
type VoteAccountsHashMap = HashMap<Pubkey, (/*stake:*/ u64, VoteAccount)>;
impl From<VoteAccountsHashMap> for VoteAccounts {
fn from(vote_accounts: VoteAccountsHashMap) -> Self {
impl From<Arc<VoteAccountsHashMap>> for VoteAccounts {
fn from(vote_accounts: Arc<VoteAccountsHashMap>) -> Self {
Self {
vote_accounts,
staked_nodes: RwLock::default(),
@ -273,12 +279,18 @@ impl AsRef<VoteAccountsHashMap> for VoteAccounts {
}
}
impl From<&VoteAccounts> for Arc<VoteAccountsHashMap> {
fn from(vote_accounts: &VoteAccounts) -> Self {
Arc::clone(&vote_accounts.vote_accounts)
}
}
impl FromIterator<(Pubkey, (/*stake:*/ u64, VoteAccount))> for VoteAccounts {
fn from_iter<I>(iter: I) -> Self
where
I: IntoIterator<Item = (Pubkey, (u64, VoteAccount))>,
{
Self::from(HashMap::from_iter(iter))
Self::from(Arc::new(HashMap::from_iter(iter)))
}
}
@ -297,7 +309,7 @@ impl<'de> Deserialize<'de> for VoteAccounts {
D: Deserializer<'de>,
{
let vote_accounts = VoteAccountsHashMap::deserialize(deserializer)?;
Ok(Self::from(vote_accounts))
Ok(Self::from(Arc::new(vote_accounts)))
}
}
@ -430,7 +442,7 @@ mod tests {
let mut rng = rand::thread_rng();
let vote_accounts_hash_map: HashMap<Pubkey, (u64, VoteAccount)> =
new_rand_vote_accounts(&mut rng, 64).take(1024).collect();
let vote_accounts = VoteAccounts::from(vote_accounts_hash_map.clone());
let vote_accounts = VoteAccounts::from(Arc::new(vote_accounts_hash_map.clone()));
assert!(vote_accounts.staked_nodes().len() > 32);
assert_eq!(
bincode::serialize(&vote_accounts).unwrap(),
@ -452,12 +464,12 @@ mod tests {
let data = bincode::serialize(&vote_accounts_hash_map).unwrap();
let vote_accounts: VoteAccounts = bincode::deserialize(&data).unwrap();
assert!(vote_accounts.staked_nodes().len() > 32);
assert_eq!(vote_accounts.vote_accounts, vote_accounts_hash_map);
assert_eq!(*vote_accounts.vote_accounts, vote_accounts_hash_map);
let data = bincode::options()
.serialize(&vote_accounts_hash_map)
.unwrap();
let vote_accounts: VoteAccounts = bincode::options().deserialize(&data).unwrap();
assert_eq!(vote_accounts.vote_accounts, vote_accounts_hash_map);
assert_eq!(*vote_accounts.vote_accounts, vote_accounts_hash_map);
}
#[test]
@ -542,4 +554,38 @@ mod tests {
}
}
}
// Asserts that returned vote-accounts are copy-on-write references.
#[test]
fn test_vote_accounts_cow() {
let mut rng = rand::thread_rng();
let mut accounts = new_rand_vote_accounts(&mut rng, 64);
// Add vote accounts.
let mut vote_accounts = VoteAccounts::default();
for (pubkey, (stake, vote_account)) in (&mut accounts).take(1024) {
vote_accounts.insert(pubkey, (stake, vote_account));
}
let vote_accounts_hashmap = Arc::<VoteAccountsHashMap>::from(&vote_accounts);
assert_eq!(vote_accounts_hashmap, vote_accounts.vote_accounts);
assert!(Arc::ptr_eq(
&vote_accounts_hashmap,
&vote_accounts.vote_accounts
));
let (pubkey, (more_stake, vote_account)) =
accounts.find(|(_, (stake, _))| *stake != 0).unwrap();
vote_accounts.insert(pubkey, (more_stake, vote_account.clone()));
assert!(!Arc::ptr_eq(
&vote_accounts_hashmap,
&vote_accounts.vote_accounts
));
assert_ne!(vote_accounts_hashmap, vote_accounts.vote_accounts);
let other = (more_stake, vote_account);
for (pk, value) in vote_accounts.iter() {
if *pk != pubkey {
assert_eq!(value, &vote_accounts_hashmap[pk]);
} else {
assert_eq!(value, &other);
}
}
}
}