Skip to content

Commit 1f9cbb0

Browse files
authored
fix: remove expensive versioned epoch stakes clone (#2453)
* fix: remove expensive versioned epoch stakes clone * Add custom partialeq impl for dcou
1 parent 2892b26 commit 1f9cbb0

File tree

6 files changed

+131
-20
lines changed

6 files changed

+131
-20
lines changed

runtime/src/bank/serde_snapshot.rs

+4-3
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,8 @@ mod tests {
66
epoch_accounts_hash_utils, test_utils as bank_test_utils, Bank, EpochRewardStatus,
77
},
88
epoch_stakes::{
9-
EpochAuthorizedVoters, EpochStakes, NodeIdToVoteAccounts, VersionedEpochStakes,
9+
EpochAuthorizedVoters, EpochStakes, NodeIdToVoteAccounts, StakesSerdeWrapper,
10+
VersionedEpochStakes,
1011
},
1112
genesis_utils::activate_all_features,
1213
runtime_config::RuntimeConfig,
@@ -306,7 +307,7 @@ mod tests {
306307
bank.epoch_stakes.insert(
307308
42,
308309
EpochStakes::from(VersionedEpochStakes::Current {
309-
stakes: Stakes::<Stake>::default(),
310+
stakes: StakesSerdeWrapper::Stake(Stakes::<Stake>::default()),
310311
total_stake: 42,
311312
node_id_to_vote_accounts: Arc::<NodeIdToVoteAccounts>::default(),
312313
epoch_authorized_voters: Arc::<EpochAuthorizedVoters>::default(),
@@ -535,7 +536,7 @@ mod tests {
535536
#[cfg_attr(
536537
feature = "frozen-abi",
537538
derive(AbiExample),
538-
frozen_abi(digest = "CeNFPePrUfgJT2GNr7zYfMQVuJwGyU46bz1Skq7hAPht")
539+
frozen_abi(digest = "7a6C1oFtgZiMtZig7FbX9289xn55QadQ962rX61Gheef")
539540
)]
540541
#[derive(Serialize)]
541542
pub struct BankAbiTestWrapper {

runtime/src/epoch_stakes.rs

+93-11
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,11 @@
11
use {
2-
crate::stakes::{Stakes, StakesEnum},
3-
serde::{Deserialize, Serialize},
2+
crate::{
3+
stake_account::StakeAccount,
4+
stakes::{Stakes, StakesEnum},
5+
},
6+
serde::{Deserialize, Deserializer, Serialize, Serializer},
47
solana_sdk::{clock::Epoch, pubkey::Pubkey, stake::state::Stake},
8+
solana_stake_program::stake_state::Delegation,
59
solana_vote::vote_account::VoteAccountsHashMap,
610
std::{collections::HashMap, sync::Arc},
711
};
@@ -131,16 +135,78 @@ impl EpochStakes {
131135
}
132136

133137
#[cfg_attr(feature = "frozen-abi", derive(AbiExample, AbiEnumVisitor))]
134-
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
138+
#[cfg_attr(feature = "dev-context-only-utils", derive(PartialEq))]
139+
#[derive(Debug, Clone, Serialize, Deserialize)]
135140
pub enum VersionedEpochStakes {
136141
Current {
137-
stakes: Stakes<Stake>,
142+
stakes: StakesSerdeWrapper,
138143
total_stake: u64,
139144
node_id_to_vote_accounts: Arc<NodeIdToVoteAccounts>,
140145
epoch_authorized_voters: Arc<EpochAuthorizedVoters>,
141146
},
142147
}
143148

149+
/// Wrapper struct with custom serialization to support serializing
150+
/// `Stakes<StakeAccount>` as `Stakes<Stake>` without doing a full deep clone of
151+
/// the stake data. Serialization works by building a `Stakes<&Stake>` map which
152+
/// borrows `&Stake` from `StakeAccount` entries in `Stakes<StakeAccount>`. Note
153+
/// that `Stakes<&Stake>` still copies `Pubkey` keys so the `Stakes<&Stake>`
154+
/// data structure still allocates a fair amount of memory but the memory only
155+
/// remains allocated during serialization.
156+
#[cfg_attr(feature = "frozen-abi", derive(AbiExample, AbiEnumVisitor))]
157+
#[derive(Debug, Clone)]
158+
pub enum StakesSerdeWrapper {
159+
Stake(Stakes<Stake>),
160+
Account(Stakes<StakeAccount<Delegation>>),
161+
}
162+
163+
#[cfg(feature = "dev-context-only-utils")]
164+
impl PartialEq<Self> for StakesSerdeWrapper {
165+
fn eq(&self, other: &Self) -> bool {
166+
match (self, other) {
167+
(Self::Stake(stakes), Self::Stake(other)) => stakes == other,
168+
(Self::Account(stakes), Self::Account(other)) => stakes == other,
169+
(Self::Stake(stakes), Self::Account(other)) => {
170+
stakes == &Stakes::<Stake>::from(other.clone())
171+
}
172+
(Self::Account(stakes), Self::Stake(other)) => {
173+
other == &Stakes::<Stake>::from(stakes.clone())
174+
}
175+
}
176+
}
177+
}
178+
179+
impl From<StakesSerdeWrapper> for StakesEnum {
180+
fn from(stakes: StakesSerdeWrapper) -> Self {
181+
match stakes {
182+
StakesSerdeWrapper::Stake(stakes) => Self::Stakes(stakes),
183+
StakesSerdeWrapper::Account(stakes) => Self::Accounts(stakes),
184+
}
185+
}
186+
}
187+
188+
impl Serialize for StakesSerdeWrapper {
189+
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
190+
where
191+
S: Serializer,
192+
{
193+
match self {
194+
Self::Stake(stakes) => stakes.serialize(serializer),
195+
Self::Account(stakes) => Stakes::<&Stake>::from(stakes).serialize(serializer),
196+
}
197+
}
198+
}
199+
200+
impl<'de> Deserialize<'de> for StakesSerdeWrapper {
201+
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
202+
where
203+
D: Deserializer<'de>,
204+
{
205+
let stakes = Stakes::<Stake>::deserialize(deserializer)?;
206+
Ok(Self::Stake(stakes))
207+
}
208+
}
209+
144210
impl From<VersionedEpochStakes> for EpochStakes {
145211
fn from(versioned: VersionedEpochStakes) -> Self {
146212
let VersionedEpochStakes::Current {
@@ -151,7 +217,7 @@ impl From<VersionedEpochStakes> for EpochStakes {
151217
} = versioned;
152218

153219
Self {
154-
stakes: Arc::new(StakesEnum::Stakes(stakes)),
220+
stakes: Arc::new(stakes.into()),
155221
total_stake,
156222
node_id_to_vote_accounts,
157223
epoch_authorized_voters,
@@ -196,7 +262,7 @@ pub(crate) fn split_epoch_stakes(
196262
versioned_epoch_stakes.insert(
197263
epoch,
198264
VersionedEpochStakes::Current {
199-
stakes: Stakes::<Stake>::from(stakes.clone()),
265+
stakes: StakesSerdeWrapper::Account(stakes.clone()),
200266
total_stake,
201267
node_id_to_vote_accounts,
202268
epoch_authorized_voters,
@@ -207,7 +273,7 @@ pub(crate) fn split_epoch_stakes(
207273
versioned_epoch_stakes.insert(
208274
epoch,
209275
VersionedEpochStakes::Current {
210-
stakes: stakes.clone(),
276+
stakes: StakesSerdeWrapper::Stake(stakes.clone()),
211277
total_stake,
212278
node_id_to_vote_accounts,
213279
epoch_authorized_voters,
@@ -426,7 +492,7 @@ pub(crate) mod tests {
426492
assert_eq!(
427493
versioned.get(&epoch),
428494
Some(&VersionedEpochStakes::Current {
429-
stakes: Stakes::<Stake>::from(test_stakes),
495+
stakes: StakesSerdeWrapper::Account(test_stakes),
430496
total_stake: epoch_stakes.total_stake,
431497
node_id_to_vote_accounts: epoch_stakes.node_id_to_vote_accounts,
432498
epoch_authorized_voters: epoch_stakes.epoch_authorized_voters,
@@ -455,7 +521,7 @@ pub(crate) mod tests {
455521
assert_eq!(
456522
versioned.get(&epoch),
457523
Some(&VersionedEpochStakes::Current {
458-
stakes: test_stakes,
524+
stakes: StakesSerdeWrapper::Stake(test_stakes),
459525
total_stake: epoch_stakes.total_stake,
460526
node_id_to_vote_accounts: epoch_stakes.node_id_to_vote_accounts,
461527
epoch_authorized_voters: epoch_stakes.epoch_authorized_voters,
@@ -506,8 +572,24 @@ pub(crate) mod tests {
506572
assert!(old.contains_key(&epoch1));
507573

508574
assert_eq!(versioned.len(), 2);
509-
assert!(versioned.contains_key(&epoch2));
510-
assert!(versioned.contains_key(&epoch3));
575+
assert_eq!(
576+
versioned.get(&epoch2),
577+
Some(&VersionedEpochStakes::Current {
578+
stakes: StakesSerdeWrapper::Account(Stakes::default()),
579+
total_stake: 200,
580+
node_id_to_vote_accounts: Arc::default(),
581+
epoch_authorized_voters: Arc::default(),
582+
})
583+
);
584+
assert_eq!(
585+
versioned.get(&epoch3),
586+
Some(&VersionedEpochStakes::Current {
587+
stakes: StakesSerdeWrapper::Stake(Stakes::default()),
588+
total_stake: 300,
589+
node_id_to_vote_accounts: Arc::default(),
590+
epoch_authorized_voters: Arc::default(),
591+
})
592+
);
511593
}
512594

513595
#[test]

runtime/src/serde_snapshot.rs

+4-3
Original file line numberDiff line numberDiff line change
@@ -389,7 +389,8 @@ where
389389
/// added to this struct a minor release before they are added to the serialize
390390
/// struct.
391391
#[cfg_attr(feature = "frozen-abi", derive(AbiExample))]
392-
#[derive(Clone, Debug, Deserialize, PartialEq)]
392+
#[cfg_attr(feature = "dev-context-only-utils", derive(PartialEq))]
393+
#[derive(Clone, Debug, Deserialize)]
393394
struct ExtraFieldsToDeserialize {
394395
#[serde(deserialize_with = "default_on_eof")]
395396
lamports_per_signature: u64,
@@ -408,8 +409,8 @@ struct ExtraFieldsToDeserialize {
408409
/// be added to the deserialize struct a minor release before they are added to
409410
/// this one.
410411
#[cfg_attr(feature = "frozen-abi", derive(AbiExample))]
411-
#[cfg_attr(feature = "dev-context-only-utils", derive(Default))]
412-
#[derive(Debug, Serialize, PartialEq)]
412+
#[cfg_attr(feature = "dev-context-only-utils", derive(Default, PartialEq))]
413+
#[derive(Debug, Serialize)]
413414
pub struct ExtraFieldsToSerialize<'a> {
414415
pub lamports_per_signature: u64,
415416
pub incremental_snapshot_persistence: Option<&'a BankIncrementalSnapshotPersistence>,

runtime/src/stake_account.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -55,10 +55,10 @@ impl StakeAccount<Delegation> {
5555
}
5656

5757
#[inline]
58-
pub(crate) fn stake(&self) -> Stake {
58+
pub(crate) fn stake(&self) -> &Stake {
5959
// Safe to unwrap here because StakeAccount<Delegation> will always
6060
// only wrap a stake-state.
61-
self.stake_state.stake().unwrap()
61+
self.stake_state.stake_ref().unwrap()
6262
}
6363
}
6464

runtime/src/stakes.rs

+21-1
Original file line numberDiff line numberDiff line change
@@ -544,12 +544,15 @@ impl From<Stakes<StakeAccount>> for Stakes<Delegation> {
544544
}
545545
}
546546

547+
/// This conversion is very memory intensive so should only be used in
548+
/// development contexts.
549+
#[cfg(feature = "dev-context-only-utils")]
547550
impl From<Stakes<StakeAccount>> for Stakes<Stake> {
548551
fn from(stakes: Stakes<StakeAccount>) -> Self {
549552
let stake_delegations = stakes
550553
.stake_delegations
551554
.into_iter()
552-
.map(|(pubkey, stake_account)| (pubkey, stake_account.stake()))
555+
.map(|(pubkey, stake_account)| (pubkey, *stake_account.stake()))
553556
.collect();
554557
Self {
555558
vote_accounts: stakes.vote_accounts,
@@ -561,6 +564,23 @@ impl From<Stakes<StakeAccount>> for Stakes<Stake> {
561564
}
562565
}
563566

567+
impl<'a> From<&'a Stakes<StakeAccount>> for Stakes<&'a Stake> {
568+
fn from(stakes: &'a Stakes<StakeAccount>) -> Self {
569+
let stake_delegations = stakes
570+
.stake_delegations
571+
.iter()
572+
.map(|(pubkey, stake_account)| (*pubkey, stake_account.stake()))
573+
.collect();
574+
Self {
575+
vote_accounts: stakes.vote_accounts.clone(),
576+
stake_delegations,
577+
unused: stakes.unused,
578+
epoch: stakes.epoch,
579+
stake_history: stakes.stake_history.clone(),
580+
}
581+
}
582+
}
583+
564584
impl From<Stakes<Stake>> for Stakes<Delegation> {
565585
fn from(stakes: Stakes<Stake>) -> Self {
566586
let stake_delegations = stakes

sdk/program/src/stake/state.rs

+7
Original file line numberDiff line numberDiff line change
@@ -213,6 +213,13 @@ impl StakeStateV2 {
213213
}
214214
}
215215

216+
pub fn stake_ref(&self) -> Option<&Stake> {
217+
match self {
218+
StakeStateV2::Stake(_meta, stake, _stake_flags) => Some(stake),
219+
_ => None,
220+
}
221+
}
222+
216223
pub fn delegation(&self) -> Option<Delegation> {
217224
match self {
218225
StakeStateV2::Stake(_meta, stake, _stake_flags) => Some(stake.delegation),

0 commit comments

Comments
 (0)