diff --git a/runtime/src/bank.rs b/runtime/src/bank.rs index 0f5e43ddea891c..4efac319401a1c 100644 --- a/runtime/src/bank.rs +++ b/runtime/src/bank.rs @@ -2296,8 +2296,7 @@ impl Bank { .fold( HashSet::default, |mut voter_pubkeys, (_stake_pubkey, stake_account)| { - let delegation = stake_account.delegation(); - voter_pubkeys.insert(delegation.voter_pubkey); + voter_pubkeys.insert(stake_account.delegation().voter_pubkey); voter_pubkeys }, ) @@ -2361,7 +2360,7 @@ impl Bank { }; if let Some(reward_calc_tracer) = reward_calc_tracer.as_ref() { let delegation = - InflationPointCalculationEvent::Delegation(delegation, solana_vote_program); + InflationPointCalculationEvent::Delegation(*delegation, solana_vote_program); let event = RewardCalculationEvent::Staking(stake_pubkey, &delegation); reward_calc_tracer(&event); } diff --git a/runtime/src/bank/partitioned_epoch_rewards/calculation.rs b/runtime/src/bank/partitioned_epoch_rewards/calculation.rs index 67cd54944d4d57..9d929accb5cdb1 100644 --- a/runtime/src/bank/partitioned_epoch_rewards/calculation.rs +++ b/runtime/src/bank/partitioned_epoch_rewards/calculation.rs @@ -378,10 +378,9 @@ impl Bank { let stake_pubkey = **stake_pubkey; let stake_account = (*stake_account).to_owned(); - let delegation = stake_account.delegation(); + let vote_pubkey = stake_account.delegation().voter_pubkey; let (mut stake_account, stake_state) = <(AccountSharedData, StakeStateV2)>::from(stake_account); - let vote_pubkey = delegation.voter_pubkey; let vote_account = get_vote_account(&vote_pubkey)?; if vote_account.owner() != &solana_vote_program { return None; @@ -501,8 +500,7 @@ impl Bank { stake_delegations .par_iter() .map(|(_stake_pubkey, stake_account)| { - let delegation = stake_account.delegation(); - let vote_pubkey = delegation.voter_pubkey; + let vote_pubkey = stake_account.delegation().voter_pubkey; let Some(vote_account) = get_vote_account(&vote_pubkey) else { return 0; diff --git a/runtime/src/bank/serde_snapshot.rs b/runtime/src/bank/serde_snapshot.rs index c4f716071064f1..2765af884b5c41 100644 --- a/runtime/src/bank/serde_snapshot.rs +++ b/runtime/src/bank/serde_snapshot.rs @@ -488,7 +488,7 @@ mod tests { #[cfg_attr( feature = "frozen-abi", derive(AbiExample), - frozen_abi(digest = "6riNuebfnAUpS2e3GYb5G8udH5PoEtep48ULchLjRDCB") + frozen_abi(digest = "AzsxaiGEcf3Bxwna5DPS3w5DP4JjyxPDfHnYTvUfyZ5f") )] #[derive(Serialize)] pub struct BankAbiTestWrapper { diff --git a/runtime/src/stake_account.rs b/runtime/src/stake_account.rs index ea4ed6dd0f624c..d735e2fe7a02c9 100644 --- a/runtime/src/stake_account.rs +++ b/runtime/src/stake_account.rs @@ -48,10 +48,10 @@ impl StakeAccount { impl StakeAccount { #[inline] - pub(crate) fn delegation(&self) -> Delegation { + pub(crate) fn delegation(&self) -> &Delegation { // Safe to unwrap here because StakeAccount will always // only wrap a stake-state which is a delegation. - self.stake_state.delegation().unwrap() + self.stake_state.delegation_ref().unwrap() } } diff --git a/runtime/src/stakes.rs b/runtime/src/stakes.rs index 27ec9d683ced9e..53e563889d67f5 100644 --- a/runtime/src/stakes.rs +++ b/runtime/src/stakes.rs @@ -26,6 +26,9 @@ use { thiserror::Error, }; +mod serde_stakes; +pub(crate) use serde_stakes::serde_stakes_enum_compat; + #[derive(Debug, Error)] pub enum Error { #[error("Invalid delegation: {0}")] @@ -267,7 +270,7 @@ impl Stakes { let stake_account = StakeAccount::try_from(stake_account)?; // Sanity check that the delegation is consistent with what is // stored in the account. - if stake_account.delegation() == *delegation { + if stake_account.delegation() == delegation { map.insert(*pubkey, stake_account); Ok(map) } else { @@ -499,12 +502,15 @@ impl StakesEnum { } } +/// This conversion is very memory intensive so should only be used in +/// development contexts. +#[cfg(feature = "dev-context-only-utils")] impl From> for Stakes { fn from(stakes: Stakes) -> Self { let stake_delegations = stakes .stake_delegations .into_iter() - .map(|(pubkey, stake_account)| (pubkey, stake_account.delegation())) + .map(|(pubkey, stake_account)| (pubkey, *stake_account.delegation())) .collect(); Self { vote_accounts: stakes.vote_accounts, @@ -516,6 +522,9 @@ impl From> for Stakes { } } +/// This conversion is memory intensive so should only be used in development +/// contexts. +#[cfg(feature = "dev-context-only-utils")] impl From> for Stakes { fn from(stakes: Stakes) -> Self { let stake_delegations = stakes @@ -533,6 +542,9 @@ impl From> for Stakes { } } +/// This conversion is memory intensive so should only be used in development +/// contexts. +#[cfg(feature = "dev-context-only-utils")] impl From for Stakes { fn from(stakes: StakesEnum) -> Self { match stakes { @@ -576,36 +588,6 @@ impl PartialEq for StakesEnum { } } -// In order to maintain backward compatibility, the StakesEnum in EpochStakes -// and SerializableVersionedBank should be serialized as Stakes. -pub(crate) mod serde_stakes_enum_compat { - use { - super::*, - serde::{Deserialize, Deserializer, Serialize, Serializer}, - }; - - pub(crate) fn serialize(stakes: &StakesEnum, serializer: S) -> Result - where - S: Serializer, - { - match stakes { - StakesEnum::Delegations(stakes) => stakes.serialize(serializer), - stakes => { - let stakes = Stakes::::from(stakes.clone()); - stakes.serialize(serializer) - } - } - } - - pub(crate) fn deserialize<'de, D>(deserializer: D) -> Result, D::Error> - where - D: Deserializer<'de>, - { - let stakes = Stakes::::deserialize(deserializer)?; - Ok(Arc::new(StakesEnum::Delegations(stakes))) - } -} - fn refresh_vote_accounts( thread_pool: &ThreadPool, epoch: Epoch, @@ -651,7 +633,6 @@ fn refresh_vote_accounts( pub(crate) mod tests { use { super::*, - rand::Rng, rayon::ThreadPoolBuilder, solana_sdk::{account::WritableAccount, pubkey::Pubkey, rent::Rent, stake}, solana_stake_program::stake_state, @@ -1078,63 +1059,4 @@ pub(crate) mod tests { ); } } - - #[test] - fn test_serde_stakes_enum_compat() { - #[derive(Debug, PartialEq, Deserialize, Serialize)] - struct Dummy { - head: String, - #[serde(with = "serde_stakes_enum_compat")] - stakes: Arc, - tail: String, - } - let mut rng = rand::thread_rng(); - let stakes_cache = StakesCache::new(Stakes { - unused: rng.gen(), - epoch: rng.gen(), - ..Stakes::default() - }); - for _ in 0..rng.gen_range(5usize..10) { - let vote_pubkey = solana_sdk::pubkey::new_rand(); - let vote_account = vote_state::create_account( - &vote_pubkey, - &solana_sdk::pubkey::new_rand(), // node_pubkey - rng.gen_range(0..101), // commission - rng.gen_range(0..1_000_000), // lamports - ); - stakes_cache.check_and_store(&vote_pubkey, &vote_account, None); - for _ in 0..rng.gen_range(10usize..20) { - let stake_pubkey = solana_sdk::pubkey::new_rand(); - let rent = Rent::with_slots_per_epoch(rng.gen()); - let stake_account = stake_state::create_account( - &stake_pubkey, // authorized - &vote_pubkey, - &vote_account, - &rent, - rng.gen_range(0..1_000_000), // lamports - ); - stakes_cache.check_and_store(&stake_pubkey, &stake_account, None); - } - } - let stakes: Stakes = stakes_cache.stakes().clone(); - assert!(stakes.vote_accounts.as_ref().len() >= 5); - assert!(stakes.stake_delegations.len() >= 50); - let dummy = Dummy { - head: String::from("dummy-head"), - stakes: Arc::new(StakesEnum::from(stakes.clone())), - tail: String::from("dummy-tail"), - }; - assert!(dummy.stakes.vote_accounts().as_ref().len() >= 5); - let data = bincode::serialize(&dummy).unwrap(); - let other: Dummy = bincode::deserialize(&data).unwrap(); - assert_eq!(other, dummy); - let stakes = Stakes::::from(stakes); - assert!(stakes.vote_accounts.as_ref().len() >= 5); - assert!(stakes.stake_delegations.len() >= 50); - let other = match &*other.stakes { - StakesEnum::Accounts(_) | StakesEnum::Stakes(_) => panic!("wrong type!"), - StakesEnum::Delegations(delegations) => delegations, - }; - assert_eq!(other, &stakes) - } } diff --git a/runtime/src/stakes/serde_stakes.rs b/runtime/src/stakes/serde_stakes.rs new file mode 100644 index 00000000000000..c96cef1b4327ae --- /dev/null +++ b/runtime/src/stakes/serde_stakes.rs @@ -0,0 +1,211 @@ +use { + super::{StakeAccount, Stakes, StakesEnum}, + crate::stake_history::StakeHistory, + im::HashMap as ImHashMap, + serde::{ser::SerializeMap, Serialize, Serializer}, + solana_sdk::{clock::Epoch, pubkey::Pubkey, stake::state::Delegation}, + solana_stake_program::stake_state::Stake, + solana_vote::vote_account::VoteAccounts, + std::sync::Arc, +}; + +// In order to maintain backward compatibility, the StakesEnum in EpochStakes +// and SerializableVersionedBank should be serialized as Stakes. +pub(crate) mod serde_stakes_enum_compat { + use { + super::*, + serde::{Deserialize, Deserializer, Serialize, Serializer}, + }; + + pub(crate) fn serialize(stakes: &StakesEnum, serializer: S) -> Result + where + S: Serializer, + { + match stakes { + StakesEnum::Delegations(stakes) => stakes.serialize(serializer), + StakesEnum::Stakes(stakes) => serialize_stakes_as_delegations(stakes, serializer), + StakesEnum::Accounts(stakes) => { + serialize_stake_accounts_as_delegations(stakes, serializer) + } + } + } + + pub(crate) fn deserialize<'de, D>(deserializer: D) -> Result, D::Error> + where + D: Deserializer<'de>, + { + let stakes = Stakes::::deserialize(deserializer)?; + Ok(Arc::new(StakesEnum::Delegations(stakes))) + } +} + +fn serialize_stakes_as_delegations( + stakes: &Stakes, + serializer: S, +) -> Result { + SerdeStakeVariantStakes::from(stakes.clone()).serialize(serializer) +} + +fn serialize_stake_accounts_as_delegations( + stakes: &Stakes, + serializer: S, +) -> Result { + SerdeStakeAccountVariantStakes::from(stakes.clone()).serialize(serializer) +} + +impl From> for SerdeStakeVariantStakes { + fn from(stakes: Stakes) -> Self { + let Stakes { + vote_accounts, + stake_delegations, + unused, + epoch, + stake_history, + } = stakes; + + Self { + vote_accounts, + stake_delegations: SerdeStakeMapWrapper(stake_delegations), + unused, + epoch, + stake_history, + } + } +} + +impl From> for SerdeStakeAccountVariantStakes { + fn from(stakes: Stakes) -> Self { + let Stakes { + vote_accounts, + stake_delegations, + unused, + epoch, + stake_history, + } = stakes; + + Self { + vote_accounts, + stake_delegations: SerdeStakeAccountMapWrapper(stake_delegations), + unused, + epoch, + stake_history, + } + } +} + +#[cfg_attr(feature = "frozen-abi", derive(AbiExample))] +#[derive(Serialize)] +struct SerdeStakeVariantStakes { + vote_accounts: VoteAccounts, + stake_delegations: SerdeStakeMapWrapper, + unused: u64, + epoch: Epoch, + stake_history: StakeHistory, +} + +#[cfg_attr(feature = "frozen-abi", derive(AbiExample))] +#[derive(Serialize)] +struct SerdeStakeAccountVariantStakes { + vote_accounts: VoteAccounts, + stake_delegations: SerdeStakeAccountMapWrapper, + unused: u64, + epoch: Epoch, + stake_history: StakeHistory, +} + +#[cfg_attr(feature = "frozen-abi", derive(AbiExample))] +struct SerdeStakeMapWrapper(ImHashMap); +impl Serialize for SerdeStakeMapWrapper { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + let mut s = serializer.serialize_map(Some(self.0.len()))?; + for (pubkey, stake) in self.0.iter() { + s.serialize_entry(pubkey, &stake.delegation)?; + } + s.end() + } +} + +#[cfg_attr(feature = "frozen-abi", derive(AbiExample))] +struct SerdeStakeAccountMapWrapper(ImHashMap); +impl Serialize for SerdeStakeAccountMapWrapper { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + let mut s = serializer.serialize_map(Some(self.0.len()))?; + for (pubkey, stake_account) in self.0.iter() { + s.serialize_entry(pubkey, stake_account.delegation())?; + } + s.end() + } +} + +#[cfg(test)] +mod tests { + use { + super::*, crate::stakes::StakesCache, rand::Rng, solana_sdk::rent::Rent, + solana_stake_program::stake_state, solana_vote_program::vote_state, + }; + + #[test] + fn test_serde_stakes_enum_compat() { + #[derive(Debug, PartialEq, Deserialize, Serialize)] + struct Dummy { + head: String, + #[serde(with = "serde_stakes_enum_compat")] + stakes: Arc, + tail: String, + } + let mut rng = rand::thread_rng(); + let stakes_cache = StakesCache::new(Stakes { + unused: rng.gen(), + epoch: rng.gen(), + ..Stakes::default() + }); + for _ in 0..rng.gen_range(5usize..10) { + let vote_pubkey = solana_sdk::pubkey::new_rand(); + let vote_account = vote_state::create_account( + &vote_pubkey, + &solana_sdk::pubkey::new_rand(), // node_pubkey + rng.gen_range(0..101), // commission + rng.gen_range(0..1_000_000), // lamports + ); + stakes_cache.check_and_store(&vote_pubkey, &vote_account, None); + for _ in 0..rng.gen_range(10usize..20) { + let stake_pubkey = solana_sdk::pubkey::new_rand(); + let rent = Rent::with_slots_per_epoch(rng.gen()); + let stake_account = stake_state::create_account( + &stake_pubkey, // authorized + &vote_pubkey, + &vote_account, + &rent, + rng.gen_range(0..1_000_000), // lamports + ); + stakes_cache.check_and_store(&stake_pubkey, &stake_account, None); + } + } + let stakes: Stakes = stakes_cache.stakes().clone(); + assert!(stakes.vote_accounts.as_ref().len() >= 5); + assert!(stakes.stake_delegations.len() >= 50); + let dummy = Dummy { + head: String::from("dummy-head"), + stakes: Arc::new(StakesEnum::from(stakes.clone())), + tail: String::from("dummy-tail"), + }; + assert!(dummy.stakes.vote_accounts().as_ref().len() >= 5); + let data = bincode::serialize(&dummy).unwrap(); + let other: Dummy = bincode::deserialize(&data).unwrap(); + assert_eq!(other, dummy); + let stakes = Stakes::::from(stakes); + assert!(stakes.vote_accounts.as_ref().len() >= 5); + assert!(stakes.stake_delegations.len() >= 50); + let other = match &*other.stakes { + StakesEnum::Accounts(_) | StakesEnum::Stakes(_) => panic!("wrong type!"), + StakesEnum::Delegations(delegations) => delegations, + }; + assert_eq!(other, &stakes) + } +} diff --git a/sdk/program/src/stake/state.rs b/sdk/program/src/stake/state.rs index 8a5f36c9211589..5c28a203be8e9d 100644 --- a/sdk/program/src/stake/state.rs +++ b/sdk/program/src/stake/state.rs @@ -220,6 +220,13 @@ impl StakeStateV2 { } } + pub fn delegation_ref(&self) -> Option<&Delegation> { + match self { + StakeStateV2::Stake(_meta, stake, _stake_flags) => Some(&stake.delegation), + _ => None, + } + } + pub fn authorized(&self) -> Option { match self { StakeStateV2::Stake(meta, _stake, _stake_flags) => Some(meta.authorized),