Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: serialize new versioned epoch stakes bank snapshot field #2282

Merged
merged 4 commits into from
Jul 30, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 6 additions & 2 deletions runtime/src/bank.rs
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ use {
partitioned_epoch_rewards::{EpochRewardStatus, StakeRewards, VoteRewardsAccounts},
},
bank_forks::BankForks,
epoch_stakes::{EpochStakes, NodeVoteAccounts},
epoch_stakes::{split_epoch_stakes, EpochStakes, NodeVoteAccounts, VersionedEpochStakes},
installed_scheduler_pool::{BankWithScheduler, InstalledSchedulerRwLock},
runtime_config::RuntimeConfig,
serde_snapshot::BankIncrementalSnapshotPersistence,
Expand Down Expand Up @@ -504,6 +504,7 @@ pub struct BankFieldsToSerialize {
pub epoch_stakes: HashMap<Epoch, EpochStakes>,
pub is_delta: bool,
pub accounts_data_len: u64,
pub versioned_epoch_stakes: HashMap<u64, VersionedEpochStakes>,
}

// Can't derive PartialEq because RwLock doesn't implement PartialEq
Expand Down Expand Up @@ -647,6 +648,7 @@ impl BankFieldsToSerialize {
epoch_stakes: HashMap::default(),
is_delta: bool::default(),
accounts_data_len: u64::default(),
versioned_epoch_stakes: HashMap::default(),
}
}
}
Expand Down Expand Up @@ -1674,6 +1676,7 @@ impl Bank {

/// Return subset of bank fields representing serializable state
pub(crate) fn get_fields_to_serialize(&self) -> BankFieldsToSerialize {
let (epoch_stakes, versioned_epoch_stakes) = split_epoch_stakes(self.epoch_stakes.clone());
BankFieldsToSerialize {
blockhash_queue: self.blockhash_queue.read().unwrap().clone(),
ancestors: AncestorsForSerialization::from(&self.ancestors),
Expand Down Expand Up @@ -1702,9 +1705,10 @@ impl Bank {
epoch_schedule: self.epoch_schedule.clone(),
inflation: *self.inflation.read().unwrap(),
stakes: StakesEnum::from(self.stakes_cache.stakes().clone()),
epoch_stakes: self.epoch_stakes.clone(),
epoch_stakes,
is_delta: self.is_delta.load(Relaxed),
accounts_data_len: self.load_accounts_data_size(),
versioned_epoch_stakes,
}
}

Expand Down
113 changes: 73 additions & 40 deletions runtime/src/bank/serde_snapshot.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ mod tests {
create_tmp_accounts_dir_for_tests, get_storages_to_serialize, ArchiveFormat,
StorageAndNextAccountsFileId,
},
stakes::Stakes,
stakes::{Stakes, StakesEnum},
},
solana_accounts_db::{
account_storage::{AccountStorageMap, AccountStorageReference},
Expand All @@ -37,8 +37,8 @@ mod tests {
pubkey::Pubkey, stake::state::Stake,
},
std::{
collections::HashMap,
io::{BufReader, BufWriter, Cursor},
mem,
ops::RangeFull,
path::Path,
sync::{atomic::Ordering, Arc},
Expand Down Expand Up @@ -125,7 +125,7 @@ mod tests {
} else {
0
} + 2;
let bank2 = Bank::new_from_parent(bank0, &Pubkey::default(), bank2_slot);
let mut bank2 = Bank::new_from_parent(bank0, &Pubkey::default(), bank2_slot);

// Test new account
let key2 = Pubkey::new_unique();
Expand Down Expand Up @@ -159,25 +159,59 @@ mod tests {
epoch_accounts_hash
});

// Only if a bank was recently recreated from a snapshot will it have an epoch stakes entry
// of type "delegations" which cannot be serialized into the versioned epoch stakes map. Simulate
// this condition by replacing the epoch 0 stakes map of stake accounts with an epoch stakes map
// of delegations.
{
assert_eq!(bank2.epoch_stakes.len(), 2);
assert!(bank2
.epoch_stakes
.values()
.all(|epoch_stakes| matches!(epoch_stakes.stakes(), &StakesEnum::Accounts(_))));

let StakesEnum::Accounts(stake_accounts) =
bank2.epoch_stakes.remove(&0).unwrap().stakes().clone()
else {
panic!("expected the epoch 0 stakes entry to have stake accounts");
};

bank2.epoch_stakes.insert(
0,
EpochStakes::new(Arc::new(StakesEnum::Delegations(stake_accounts.into())), 0),
);
}

let mut buf = Vec::new();
let cursor = Cursor::new(&mut buf);
let mut writer = BufWriter::new(cursor);
serde_snapshot::serialize_bank_snapshot_into(
&mut writer,
bank2.get_fields_to_serialize(),
accounts_db.get_bank_hash_stats(bank2_slot).unwrap(),
accounts_db.get_accounts_delta_hash(bank2_slot).unwrap(),
expected_accounts_hash,
&get_storages_to_serialize(&bank2.get_snapshot_storages(None)),
ExtraFieldsToSerialize {
lamports_per_signature: bank2.fee_rate_governor.lamports_per_signature,
incremental_snapshot_persistence: expected_incremental_snapshot_persistence
.as_ref(),
epoch_accounts_hash: expected_epoch_accounts_hash,
},
accounts_db.write_version.load(Ordering::Acquire),
)
.unwrap();
{
let mut bank_fields = bank2.get_fields_to_serialize();
// Ensure that epoch_stakes and versioned_epoch_stakes are each
// serialized with at least one entry to verify that epoch stakes
// entries are combined correctly during deserialization
assert!(!bank_fields.epoch_stakes.is_empty());
assert!(!bank_fields.versioned_epoch_stakes.is_empty());

let versioned_epoch_stakes = mem::take(&mut bank_fields.versioned_epoch_stakes);
serde_snapshot::serialize_bank_snapshot_into(
&mut writer,
bank_fields,
accounts_db.get_bank_hash_stats(bank2_slot).unwrap(),
accounts_db.get_accounts_delta_hash(bank2_slot).unwrap(),
expected_accounts_hash,
&get_storages_to_serialize(&bank2.get_snapshot_storages(None)),
ExtraFieldsToSerialize {
lamports_per_signature: bank2.fee_rate_governor.lamports_per_signature,
incremental_snapshot_persistence: expected_incremental_snapshot_persistence
.as_ref(),
epoch_accounts_hash: expected_epoch_accounts_hash,
versioned_epoch_stakes,
},
accounts_db.write_version.load(Ordering::Acquire),
)
.unwrap();
}
drop(writer);

// Now deserialize the serialized bank and ensure it matches the original bank
Expand Down Expand Up @@ -236,6 +270,7 @@ mod tests {
dbank.get_epoch_accounts_hash_to_serialize(),
expected_epoch_accounts_hash,
);

assert_eq!(dbank, bank2);
}

Expand Down Expand Up @@ -266,6 +301,19 @@ mod tests {

// Set extra fields
bank.fee_rate_governor.lamports_per_signature = 7000;
// Note that epoch_stakes already has two epoch stakes entries for epochs 0 and 1
// which will also be serialized to the versioned epoch stakes extra field. Those
// entries are of type Stakes<StakeAccount> so add a new entry for Stakes<Stake>.
bank.epoch_stakes.insert(
42,
EpochStakes::from(VersionedEpochStakes::Current {
stakes: Stakes::<Stake>::default(),
total_stake: 42,
node_id_to_vote_accounts: Arc::<NodeIdToVoteAccounts>::default(),
epoch_authorized_voters: Arc::<EpochAuthorizedVoters>::default(),
}),
);
assert_eq!(bank.epoch_stakes.len(), 3);

// Serialize
let snapshot_storages = bank.get_snapshot_storages(None);
Expand All @@ -279,18 +327,6 @@ mod tests {
)
.unwrap();

let mut new_epoch_stakes: HashMap<u64, VersionedEpochStakes> = HashMap::new();
new_epoch_stakes.insert(
42,
VersionedEpochStakes::Current {
stakes: Stakes::<Stake>::default(),
total_stake: 42,
node_id_to_vote_accounts: Arc::<NodeIdToVoteAccounts>::default(),
epoch_authorized_voters: Arc::<EpochAuthorizedVoters>::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..]);
Expand Down Expand Up @@ -324,13 +360,7 @@ mod tests {
)
.unwrap();

assert_eq!(
dbank.epoch_stakes(42),
Some(&EpochStakes::from(
new_epoch_stakes.get(&42).unwrap().clone()
))
);

assert_eq!(bank.epoch_stakes, dbank.epoch_stakes);
assert_eq!(
bank.fee_rate_governor.lamports_per_signature,
dbank.fee_rate_governor.lamports_per_signature
Expand Down Expand Up @@ -506,7 +536,7 @@ mod tests {
#[cfg_attr(
feature = "frozen-abi",
derive(AbiExample),
frozen_abi(digest = "AMm4uzGQ6E7fj8MkDjUtFR7kYAjtUyWddXAPLjwaqKqV")
frozen_abi(digest = "CeNFPePrUfgJT2GNr7zYfMQVuJwGyU46bz1Skq7hAPht")
)]
#[derive(Serialize)]
pub struct BankAbiTestWrapper {
Expand All @@ -531,9 +561,11 @@ mod tests {
incremental_capitalization: u64::default(),
};

let mut bank_fields = bank.get_fields_to_serialize();
let versioned_epoch_stakes = std::mem::take(&mut bank_fields.versioned_epoch_stakes);
serde_snapshot::serialize_bank_snapshot_with(
serializer,
bank.get_fields_to_serialize(),
bank_fields,
BankHashStats::default(),
AccountsDeltaHash(Hash::new_unique()),
AccountsHash(Hash::new_unique()),
Expand All @@ -542,6 +574,7 @@ mod tests {
lamports_per_signature: bank.fee_rate_governor.lamports_per_signature,
incremental_snapshot_persistence: Some(&incremental_snapshot_persistence),
epoch_accounts_hash: Some(EpochAccountsHash::new(Hash::new_unique())),
versioned_epoch_stakes,
},
StoredMetaWriteVersion::default(),
)
Expand Down
Loading