From 574bae8fefc0ed256b55340b9d87b7689bcdf222 Mon Sep 17 00:00:00 2001 From: Justin Starry Date: Wed, 22 May 2024 08:20:50 -0500 Subject: [PATCH] feat: deserialize new epoch stakes bank snapshot field (#1397) --- runtime/src/bank/serde_snapshot.rs | 25 ++++++++++++ runtime/src/epoch_stakes.rs | 32 ++++++++++++++- runtime/src/serde_snapshot.rs | 12 +++++- runtime/src/stakes.rs | 62 ++++++++++++++++++++++-------- 4 files changed, 112 insertions(+), 19 deletions(-) diff --git a/runtime/src/bank/serde_snapshot.rs b/runtime/src/bank/serde_snapshot.rs index 4a90038e70a9b1..ce09be11f686cc 100644 --- a/runtime/src/bank/serde_snapshot.rs +++ b/runtime/src/bank/serde_snapshot.rs @@ -5,6 +5,9 @@ mod tests { bank::{ epoch_accounts_hash_utils, test_utils as bank_test_utils, Bank, EpochRewardStatus, }, + epoch_stakes::{ + EpochAuthorizedVoters, EpochStakes, NodeIdToVoteAccounts, VersionedEpochStakes, + }, genesis_utils::activate_all_features, runtime_config::RuntimeConfig, serde_snapshot::{ @@ -16,6 +19,7 @@ mod tests { self, create_tmp_accounts_dir_for_tests, get_storages_to_serialize, ArchiveFormat, StorageAndNextAccountsFileId, BANK_SNAPSHOT_PRE_FILENAME_EXTENSION, }, + stakes::Stakes, status_cache::StatusCache, }, solana_accounts_db::{ @@ -35,8 +39,10 @@ mod tests { hash::Hash, pubkey::Pubkey, signature::{Keypair, Signer}, + stake::state::Stake, }, std::{ + collections::HashMap, io::{Cursor, Read, Write}, ops::RangeFull, path::Path, @@ -369,6 +375,18 @@ mod tests { ) .unwrap(); + let mut new_epoch_stakes: HashMap = HashMap::new(); + new_epoch_stakes.insert( + 42, + VersionedEpochStakes::Current { + stakes: Stakes::::default(), + total_stake: 42, + node_id_to_vote_accounts: Arc::::default(), + epoch_authorized_voters: Arc::::default(), + }, + ); + bincode::serialize_into(&mut writer, &new_epoch_stakes).unwrap(); + // Deserialize let rdr = Cursor::new(&buf[..]); let mut reader = std::io::BufReader::new(&buf[rdr.position() as usize..]); @@ -402,6 +420,13 @@ mod tests { ) .unwrap(); + assert_eq!( + dbank.epoch_stakes(42), + Some(&EpochStakes::from( + new_epoch_stakes.get(&42).unwrap().clone() + )) + ); + assert_eq!( bank.fee_rate_governor.lamports_per_signature, dbank.fee_rate_governor.lamports_per_signature diff --git a/runtime/src/epoch_stakes.rs b/runtime/src/epoch_stakes.rs index 45197830831513..63f40218b482c3 100644 --- a/runtime/src/epoch_stakes.rs +++ b/runtime/src/epoch_stakes.rs @@ -1,7 +1,7 @@ use { - crate::stakes::StakesEnum, + crate::stakes::{Stakes, StakesEnum}, serde::{Deserialize, Serialize}, - solana_sdk::{clock::Epoch, pubkey::Pubkey}, + solana_sdk::{clock::Epoch, pubkey::Pubkey, stake::state::Stake}, solana_vote::vote_account::VoteAccountsHashMap, std::{collections::HashMap, sync::Arc}, }; @@ -124,6 +124,34 @@ impl EpochStakes { } } +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +pub(crate) enum VersionedEpochStakes { + Current { + stakes: Stakes, + total_stake: u64, + node_id_to_vote_accounts: Arc, + epoch_authorized_voters: Arc, + }, +} + +impl From for EpochStakes { + fn from(versioned: VersionedEpochStakes) -> Self { + let VersionedEpochStakes::Current { + stakes, + total_stake, + node_id_to_vote_accounts, + epoch_authorized_voters, + } = versioned; + + Self { + stakes: Arc::new(StakesEnum::Stakes(stakes)), + total_stake, + node_id_to_vote_accounts, + epoch_authorized_voters, + } + } +} + #[cfg(test)] pub(crate) mod tests { use { diff --git a/runtime/src/serde_snapshot.rs b/runtime/src/serde_snapshot.rs index 406b0569b66dc3..981f68a4eb83f6 100644 --- a/runtime/src/serde_snapshot.rs +++ b/runtime/src/serde_snapshot.rs @@ -6,7 +6,7 @@ use { builtins::BuiltinPrototype, Bank, BankFieldsToDeserialize, BankFieldsToSerialize, BankRc, }, - epoch_stakes::EpochStakes, + epoch_stakes::{EpochStakes, VersionedEpochStakes}, runtime_config::RuntimeConfig, serde_snapshot::storage::SerializableAccountStorageEntry, snapshot_utils::{ @@ -412,6 +412,16 @@ where let epoch_accounts_hash = ignore_eof_error(deserialize_from(&mut stream))?; bank_fields.epoch_accounts_hash = epoch_accounts_hash; + // If we deserialize the new epoch stakes, add all of the entries into the + // other deserialized map which could still have old epoch stakes entries + let new_epoch_stakes: HashMap = + ignore_eof_error(deserialize_from(&mut stream))?; + bank_fields.epoch_stakes.extend( + new_epoch_stakes + .into_iter() + .map(|(epoch, versioned_epoch_stakes)| (epoch, versioned_epoch_stakes.into())), + ); + Ok((bank_fields, accounts_db_fields)) } diff --git a/runtime/src/stakes.rs b/runtime/src/stakes.rs index 29212b62c5d220..e9c3fe870604dc 100644 --- a/runtime/src/stakes.rs +++ b/runtime/src/stakes.rs @@ -16,6 +16,7 @@ use { stake::state::{Delegation, StakeActivationStatus}, vote::state::VoteStateVersions, }, + solana_stake_program::stake_state::Stake, solana_vote::vote_account::{VoteAccount, VoteAccounts}, std::{ collections::HashMap, @@ -200,17 +201,19 @@ pub struct Stakes { } // For backward compatibility, we can only serialize and deserialize -// Stakes. However Bank caches Stakes. This type -// mismatch incurs a conversion cost at epoch boundary when updating -// EpochStakes. -// Below type allows EpochStakes to include either a Stakes or -// Stakes and so bypass the conversion cost between the two at the -// epoch boundary. +// Stakes in the old `epoch_stakes` bank snapshot field. However, +// Stakes entries are added to the bank's epoch stakes hashmap +// when crossing epoch boundaries and Stakes entries are added when +// starting up from bank snapshots that have the new epoch stakes field. By +// using this enum, the cost of converting all entries to Stakes is +// put off until serializing new snapshots. This helps avoid bogging down epoch +// boundaries and startup with the conversion overhead. #[cfg_attr(feature = "frozen-abi", derive(AbiExample))] -#[derive(Debug)] +#[derive(Debug, Clone)] pub enum StakesEnum { Accounts(Stakes), Delegations(Stakes), + Stakes(Stakes), } impl Stakes { @@ -483,6 +486,7 @@ impl StakesEnum { match self { StakesEnum::Accounts(stakes) => stakes.vote_accounts(), StakesEnum::Delegations(stakes) => stakes.vote_accounts(), + StakesEnum::Stakes(stakes) => stakes.vote_accounts(), } } @@ -490,6 +494,7 @@ impl StakesEnum { match self { StakesEnum::Accounts(stakes) => stakes.staked_nodes(), StakesEnum::Delegations(stakes) => stakes.staked_nodes(), + StakesEnum::Stakes(stakes) => stakes.staked_nodes(), } } } @@ -511,6 +516,33 @@ impl From> for Stakes { } } +impl From> for Stakes { + fn from(stakes: Stakes) -> Self { + let stake_delegations = stakes + .stake_delegations + .into_iter() + .map(|(pubkey, stake)| (pubkey, stake.delegation)) + .collect(); + Self { + vote_accounts: stakes.vote_accounts, + stake_delegations, + unused: stakes.unused, + epoch: stakes.epoch, + stake_history: stakes.stake_history, + } + } +} + +impl From for Stakes { + fn from(stakes: StakesEnum) -> Self { + match stakes { + StakesEnum::Accounts(stakes) => stakes.into(), + StakesEnum::Delegations(stakes) => stakes, + StakesEnum::Stakes(stakes) => stakes.into(), + } + } +} + impl From> for StakesEnum { fn from(stakes: Stakes) -> Self { Self::Accounts(stakes) @@ -533,15 +565,13 @@ impl PartialEq for StakesEnum { fn eq(&self, other: &StakesEnum) -> bool { match (self, other) { (Self::Accounts(stakes), Self::Accounts(other)) => stakes == other, - (Self::Accounts(stakes), Self::Delegations(other)) => { + (Self::Delegations(stakes), Self::Delegations(other)) => stakes == other, + (Self::Stakes(stakes), Self::Stakes(other)) => stakes == other, + (stakes, other) => { let stakes = Stakes::::from(stakes.clone()); - &stakes == other - } - (Self::Delegations(stakes), Self::Accounts(other)) => { let other = Stakes::::from(other.clone()); - stakes == &other + stakes == other } - (Self::Delegations(stakes), Self::Delegations(other)) => stakes == other, } } } @@ -559,11 +589,11 @@ pub(crate) mod serde_stakes_enum_compat { S: Serializer, { match stakes { - StakesEnum::Accounts(stakes) => { + StakesEnum::Delegations(stakes) => stakes.serialize(serializer), + stakes => { let stakes = Stakes::::from(stakes.clone()); stakes.serialize(serializer) } - StakesEnum::Delegations(stakes) => stakes.serialize(serializer), } } @@ -1101,7 +1131,7 @@ pub(crate) mod tests { assert!(stakes.vote_accounts.as_ref().len() >= 5); assert!(stakes.stake_delegations.len() >= 50); let other = match &*other.stakes { - StakesEnum::Accounts(_) => panic!("wrong type!"), + StakesEnum::Accounts(_) | StakesEnum::Stakes(_) => panic!("wrong type!"), StakesEnum::Delegations(delegations) => delegations, }; assert_eq!(other, &stakes)