From eda337346011f87803711f44d951d01e7ac68518 Mon Sep 17 00:00:00 2001 From: Justin Starry Date: Wed, 28 Feb 2024 04:19:06 +0000 Subject: [PATCH 1/3] Add reserved_account_keys module to sdk --- sdk/src/feature_set.rs | 5 + sdk/src/lib.rs | 1 + sdk/src/reserved_account_keys.rs | 202 +++++++++++++++++++++++++++++++ 3 files changed, 208 insertions(+) create mode 100644 sdk/src/reserved_account_keys.rs diff --git a/sdk/src/feature_set.rs b/sdk/src/feature_set.rs index 7d956bd13f405c..8536282cee8efe 100644 --- a/sdk/src/feature_set.rs +++ b/sdk/src/feature_set.rs @@ -736,6 +736,10 @@ pub mod allow_commission_decrease_at_any_time { solana_sdk::declare_id!("decoMktMcnmiq6t3u7g5BfgcQu91nKZr6RvMYf9z1Jb"); } +pub mod add_new_reserved_account_keys { + solana_sdk::declare_id!("8U4skmMVnF6k2kMvrWbQuRUT3qQSiTYpSjqmhmgfthZu"); +} + pub mod consume_blockstore_duplicate_proofs { solana_sdk::declare_id!("6YsBCejwK96GZCkJ6mkZ4b68oP63z2PLoQmWjC7ggTqZ"); } @@ -955,6 +959,7 @@ lazy_static! { (drop_legacy_shreds::id(), "drops legacy shreds #34328"), (allow_commission_decrease_at_any_time::id(), "Allow commission decrease at any time in epoch #33843"), (consume_blockstore_duplicate_proofs::id(), "consume duplicate proofs from blockstore in consensus #34372"), + (add_new_reserved_account_keys::id(), "add new unwritable reserved accounts #34899"), (index_erasure_conflict_duplicate_proofs::id(), "generate duplicate proofs for index and erasure conflicts #34360"), (merkle_conflict_duplicate_proofs::id(), "generate duplicate proofs for merkle root conflicts #34270"), (disable_bpf_loader_instructions::id(), "disable bpf loader management instructions #34194"), diff --git a/sdk/src/lib.rs b/sdk/src/lib.rs index ecc186f0494191..5b5c6acdcfe572 100644 --- a/sdk/src/lib.rs +++ b/sdk/src/lib.rs @@ -94,6 +94,7 @@ pub mod quic; pub mod recent_blockhashes_account; pub mod rent_collector; pub mod rent_debits; +pub mod reserved_account_keys; pub mod reward_info; pub mod reward_type; pub mod rpc_port; diff --git a/sdk/src/reserved_account_keys.rs b/sdk/src/reserved_account_keys.rs new file mode 100644 index 00000000000000..90ba8f3b816ae4 --- /dev/null +++ b/sdk/src/reserved_account_keys.rs @@ -0,0 +1,202 @@ +//! Collection of reserved account keys that cannot be write-locked by transactions. +//! New reserved account keys may be added as long as they specify a feature +//! gate that transitions the key into read-only at an epoch boundary. + +#![cfg(feature = "full")] + +use { + crate::{ + address_lookup_table, bpf_loader, bpf_loader_deprecated, bpf_loader_upgradeable, + compute_budget, config, ed25519_program, feature, + feature_set::{self, FeatureSet}, + loader_v4, native_loader, + pubkey::Pubkey, + secp256k1_program, stake, system_program, sysvar, vote, + }, + lazy_static::lazy_static, + std::collections::HashSet, +}; + +// Temporary until a zk token program module is added to the sdk +mod zk_token_proof_program { + solana_sdk::declare_id!("ZkTokenProof1111111111111111111111111111111"); +} + +pub struct ReservedAccountKeys; +impl ReservedAccountKeys { + /// Compute a set of all reserved keys, regardless of if they are active or not + pub fn active_and_inactive() -> HashSet { + RESERVED_ACCOUNT_KEYS + .iter() + .map(|reserved_key| reserved_key.key) + .collect() + } + + /// Compute the active set of reserved keys based on activated features + pub fn active(feature_set: &FeatureSet) -> HashSet { + Self::compute_active(&RESERVED_ACCOUNT_KEYS, feature_set) + } + + // Method only exists for unit testing + fn compute_active( + account_keys: &[ReservedAccountKey], + feature_set: &FeatureSet, + ) -> HashSet { + account_keys + .iter() + .filter(|reserved_key| reserved_key.is_active(feature_set)) + .map(|reserved_key| reserved_key.key) + .collect() + } + + /// Return an empty list of reserved keys for visibility when using in + /// places where the dynamic reserved key set is not available + pub fn empty() -> HashSet { + HashSet::default() + } +} + +/// `ReservedAccountKey` represents a reserved account key that will not be +/// write-lockable by transactions. If a feature id is set, the account key will +/// become read-only only after the feature has been activated. +#[derive(Debug, Clone, Eq, PartialEq)] +struct ReservedAccountKey { + key: Pubkey, + feature_id: Option, +} + +impl ReservedAccountKey { + fn new_pending(key: Pubkey, feature_id: Pubkey) -> Self { + Self { + key, + feature_id: Some(feature_id), + } + } + + fn new_active(key: Pubkey) -> Self { + Self { + key, + feature_id: None, + } + } + + /// Returns true if no feature id is set or if the feature id is activated + /// in the feature set. + fn is_active(&self, feature_set: &FeatureSet) -> bool { + self.feature_id + .map_or(true, |feature_id| feature_set.is_active(&feature_id)) + } +} + +// New reserved account keys should be added in alphabetical order and must +// specify a feature id for activation. +lazy_static! { + static ref RESERVED_ACCOUNT_KEYS: Vec = [ + // builtin programs + ReservedAccountKey::new_pending(address_lookup_table::program::id(), feature_set::add_new_reserved_account_keys::id()), + ReservedAccountKey::new_active(bpf_loader::id()), + ReservedAccountKey::new_active(bpf_loader_deprecated::id()), + ReservedAccountKey::new_active(bpf_loader_upgradeable::id()), + ReservedAccountKey::new_pending(compute_budget::id(), feature_set::add_new_reserved_account_keys::id()), + ReservedAccountKey::new_active(config::program::id()), + ReservedAccountKey::new_pending(ed25519_program::id(), feature_set::add_new_reserved_account_keys::id()), + ReservedAccountKey::new_active(feature::id()), + ReservedAccountKey::new_pending(loader_v4::id(), feature_set::add_new_reserved_account_keys::id()), + ReservedAccountKey::new_pending(secp256k1_program::id(), feature_set::add_new_reserved_account_keys::id()), + #[allow(deprecated)] + ReservedAccountKey::new_active(stake::config::id()), + ReservedAccountKey::new_active(stake::program::id()), + ReservedAccountKey::new_active(system_program::id()), + ReservedAccountKey::new_active(vote::program::id()), + ReservedAccountKey::new_pending(zk_token_proof_program::id(), feature_set::add_new_reserved_account_keys::id()), + + // sysvars + ReservedAccountKey::new_active(sysvar::clock::id()), + ReservedAccountKey::new_pending(sysvar::epoch_rewards::id(), feature_set::add_new_reserved_account_keys::id()), + ReservedAccountKey::new_active(sysvar::epoch_schedule::id()), + #[allow(deprecated)] + ReservedAccountKey::new_active(sysvar::fees::id()), + ReservedAccountKey::new_active(sysvar::instructions::id()), + ReservedAccountKey::new_pending(sysvar::last_restart_slot::id(), feature_set::add_new_reserved_account_keys::id()), + #[allow(deprecated)] + ReservedAccountKey::new_active(sysvar::recent_blockhashes::id()), + ReservedAccountKey::new_active(sysvar::rent::id()), + ReservedAccountKey::new_active(sysvar::rewards::id()), + ReservedAccountKey::new_active(sysvar::slot_hashes::id()), + ReservedAccountKey::new_active(sysvar::slot_history::id()), + ReservedAccountKey::new_active(sysvar::stake_history::id()), + + // other + ReservedAccountKey::new_active(native_loader::id()), + ReservedAccountKey::new_pending(sysvar::id(), feature_set::add_new_reserved_account_keys::id()), + ].to_vec(); +} + +#[cfg(test)] +mod tests { + use { + super::*, + solana_program::{message::legacy::BUILTIN_PROGRAMS_KEYS, sysvar::ALL_IDS}, + }; + + #[test] + fn test_reserved_account_key_is_active() { + let feature_id = Pubkey::new_unique(); + let old_reserved_account_key = ReservedAccountKey::new_active(Pubkey::new_unique()); + let new_reserved_account_key = + ReservedAccountKey::new_pending(Pubkey::new_unique(), feature_id); + + let mut feature_set = FeatureSet::default(); + assert!( + old_reserved_account_key.is_active(&feature_set), + "if not feature id is set, the key should be active" + ); + assert!( + !new_reserved_account_key.is_active(&feature_set), + "if feature id is set but not in the feature set, the key should not be active" + ); + + feature_set.active.insert(feature_id, 0); + assert!( + new_reserved_account_key.is_active(&feature_set), + "if feature id is set and is in the feature set, the key should be active" + ); + } + + #[test] + fn test_reserved_account_keys_compute_active() { + let feature_id = Pubkey::new_unique(); + let key0 = Pubkey::new_unique(); + let key1 = Pubkey::new_unique(); + let test_account_keys = vec![ + ReservedAccountKey::new_active(key0), + ReservedAccountKey::new_pending(key1, feature_id), + ReservedAccountKey::new_pending(Pubkey::new_unique(), Pubkey::new_unique()), + ]; + + let mut feature_set = FeatureSet::default(); + assert_eq!( + HashSet::from_iter([key0].into_iter()), + ReservedAccountKeys::compute_active(&test_account_keys, &feature_set), + "should only contain keys without feature ids" + ); + + feature_set.active.insert(feature_id, 0); + assert_eq!( + HashSet::from_iter([key0, key1].into_iter()), + ReservedAccountKeys::compute_active(&test_account_keys, &feature_set), + "should only contain keys without feature ids or with active feature ids" + ); + } + + #[test] + fn test_static_list_compat() { + let mut static_set = HashSet::new(); + static_set.extend(ALL_IDS.iter().cloned()); + static_set.extend(BUILTIN_PROGRAMS_KEYS.iter().cloned()); + + let initial_dynamic_set = ReservedAccountKeys::active(&FeatureSet::default()); + + assert_eq!(initial_dynamic_set, static_set); + } +} From c827554d7d3bc8cd51924b0ed781031fd19f3265 Mon Sep 17 00:00:00 2001 From: Justin Starry Date: Tue, 5 Mar 2024 09:21:50 +0000 Subject: [PATCH 2/3] clarify comments --- sdk/src/reserved_account_keys.rs | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/sdk/src/reserved_account_keys.rs b/sdk/src/reserved_account_keys.rs index 90ba8f3b816ae4..acfe008b4a65df 100644 --- a/sdk/src/reserved_account_keys.rs +++ b/sdk/src/reserved_account_keys.rs @@ -17,21 +17,13 @@ use { std::collections::HashSet, }; -// Temporary until a zk token program module is added to the sdk +// Inline zk token program id since it isn't available in the sdk mod zk_token_proof_program { solana_sdk::declare_id!("ZkTokenProof1111111111111111111111111111111"); } pub struct ReservedAccountKeys; impl ReservedAccountKeys { - /// Compute a set of all reserved keys, regardless of if they are active or not - pub fn active_and_inactive() -> HashSet { - RESERVED_ACCOUNT_KEYS - .iter() - .map(|reserved_key| reserved_key.key) - .collect() - } - /// Compute the active set of reserved keys based on activated features pub fn active(feature_set: &FeatureSet) -> HashSet { Self::compute_active(&RESERVED_ACCOUNT_KEYS, feature_set) @@ -49,6 +41,16 @@ impl ReservedAccountKeys { .collect() } + /// Compute a set of all reserved keys, regardless of if they are active or not. + /// Since this method doesn't take into account which reserved keys are active, + /// it should not be used by the runtime. + pub fn active_and_inactive() -> HashSet { + RESERVED_ACCOUNT_KEYS + .iter() + .map(|reserved_key| reserved_key.key) + .collect() + } + /// Return an empty list of reserved keys for visibility when using in /// places where the dynamic reserved key set is not available pub fn empty() -> HashSet { From d341e165778a8708eccff3c065338fe9ee9a9bba Mon Sep 17 00:00:00 2001 From: Justin Starry Date: Wed, 13 Mar 2024 02:01:43 +0000 Subject: [PATCH 3/3] feedback --- sdk/src/reserved_account_keys.rs | 262 ++++++++++++++++++------------- 1 file changed, 157 insertions(+), 105 deletions(-) diff --git a/sdk/src/reserved_account_keys.rs b/sdk/src/reserved_account_keys.rs index acfe008b4a65df..2102949b240f49 100644 --- a/sdk/src/reserved_account_keys.rs +++ b/sdk/src/reserved_account_keys.rs @@ -14,7 +14,7 @@ use { secp256k1_program, stake, system_program, sysvar, vote, }, lazy_static::lazy_static, - std::collections::HashSet, + std::collections::{HashMap, HashSet}, }; // Inline zk token program id since it isn't available in the sdk @@ -22,52 +22,100 @@ mod zk_token_proof_program { solana_sdk::declare_id!("ZkTokenProof1111111111111111111111111111111"); } -pub struct ReservedAccountKeys; +/// `ReservedAccountKeys` holds the set of currently active/inactive +/// account keys that are reserved by the protocol and may not be write-locked +/// during transaction processing. +#[derive(Debug, Clone, PartialEq)] +pub struct ReservedAccountKeys { + /// Set of currently active reserved account keys + pub active: HashSet, + /// Set of currently inactive reserved account keys that will be moved to the + /// active set when their feature id is activated + inactive: HashMap, +} + +impl Default for ReservedAccountKeys { + fn default() -> Self { + Self::new(&RESERVED_ACCOUNTS) + } +} + impl ReservedAccountKeys { - /// Compute the active set of reserved keys based on activated features - pub fn active(feature_set: &FeatureSet) -> HashSet { - Self::compute_active(&RESERVED_ACCOUNT_KEYS, feature_set) + /// Compute a set of active / inactive reserved account keys from a list of + /// keys with a designated feature id. If a reserved account key doesn't + /// designate a feature id, it's already activated and should be inserted + /// into the active set. If it does have a feature id, insert the key and + /// its feature id into the inactive map. + fn new(reserved_accounts: &[ReservedAccount]) -> Self { + Self { + active: reserved_accounts + .iter() + .filter(|reserved| reserved.feature_id.is_none()) + .map(|reserved| reserved.key) + .collect(), + inactive: reserved_accounts + .iter() + .filter_map(|ReservedAccount { key, feature_id }| { + feature_id.as_ref().map(|feature_id| (*key, *feature_id)) + }) + .collect(), + } } - // Method only exists for unit testing - fn compute_active( - account_keys: &[ReservedAccountKey], - feature_set: &FeatureSet, - ) -> HashSet { - account_keys - .iter() - .filter(|reserved_key| reserved_key.is_active(feature_set)) - .map(|reserved_key| reserved_key.key) - .collect() + /// Compute a set with all reserved keys active, regardless of whether their + /// feature was activated. This is not to be used by the runtime. Useful for + /// off-chain utilities that need to filter out reserved accounts. + pub fn new_all_activated() -> Self { + Self { + active: Self::all_keys_iter().copied().collect(), + inactive: HashMap::default(), + } + } + + /// Returns whether the specified key is reserved + pub fn is_reserved(&self, key: &Pubkey) -> bool { + self.active.contains(key) + } + + /// Move inactive reserved account keys to the active set if their feature + /// is active. + pub fn update_active_set(&mut self, feature_set: &FeatureSet) { + self.inactive.retain(|reserved_key, feature_id| { + if feature_set.is_active(feature_id) { + self.active.insert(*reserved_key); + false + } else { + true + } + }); } - /// Compute a set of all reserved keys, regardless of if they are active or not. - /// Since this method doesn't take into account which reserved keys are active, - /// it should not be used by the runtime. - pub fn active_and_inactive() -> HashSet { - RESERVED_ACCOUNT_KEYS + /// Return an iterator over all active / inactive reserved keys. This is not + /// to be used by the runtime. Useful for off-chain utilities that need to + /// filter out reserved accounts. + pub fn all_keys_iter() -> impl Iterator { + RESERVED_ACCOUNTS .iter() - .map(|reserved_key| reserved_key.key) - .collect() + .map(|reserved_key| &reserved_key.key) } - /// Return an empty list of reserved keys for visibility when using in - /// places where the dynamic reserved key set is not available - pub fn empty() -> HashSet { + /// Return an empty set of reserved keys for visibility when using in + /// tests where the dynamic reserved key set is not available + pub fn empty_key_set() -> HashSet { HashSet::default() } } -/// `ReservedAccountKey` represents a reserved account key that will not be -/// write-lockable by transactions. If a feature id is set, the account key will +/// `ReservedAccount` represents a reserved account that will not be +/// write-lockable by transactions. If a feature id is set, the account will /// become read-only only after the feature has been activated. -#[derive(Debug, Clone, Eq, PartialEq)] -struct ReservedAccountKey { +#[derive(Debug, Clone, Copy, Eq, PartialEq)] +struct ReservedAccount { key: Pubkey, feature_id: Option, } -impl ReservedAccountKey { +impl ReservedAccount { fn new_pending(key: Pubkey, feature_id: Pubkey) -> Self { Self { key, @@ -81,56 +129,50 @@ impl ReservedAccountKey { feature_id: None, } } - - /// Returns true if no feature id is set or if the feature id is activated - /// in the feature set. - fn is_active(&self, feature_set: &FeatureSet) -> bool { - self.feature_id - .map_or(true, |feature_id| feature_set.is_active(&feature_id)) - } } -// New reserved account keys should be added in alphabetical order and must -// specify a feature id for activation. +// New reserved accounts should be added in alphabetical order and must specify +// a feature id for activation. Reserved accounts cannot be removed from this +// list without breaking consensus. lazy_static! { - static ref RESERVED_ACCOUNT_KEYS: Vec = [ + static ref RESERVED_ACCOUNTS: Vec = [ // builtin programs - ReservedAccountKey::new_pending(address_lookup_table::program::id(), feature_set::add_new_reserved_account_keys::id()), - ReservedAccountKey::new_active(bpf_loader::id()), - ReservedAccountKey::new_active(bpf_loader_deprecated::id()), - ReservedAccountKey::new_active(bpf_loader_upgradeable::id()), - ReservedAccountKey::new_pending(compute_budget::id(), feature_set::add_new_reserved_account_keys::id()), - ReservedAccountKey::new_active(config::program::id()), - ReservedAccountKey::new_pending(ed25519_program::id(), feature_set::add_new_reserved_account_keys::id()), - ReservedAccountKey::new_active(feature::id()), - ReservedAccountKey::new_pending(loader_v4::id(), feature_set::add_new_reserved_account_keys::id()), - ReservedAccountKey::new_pending(secp256k1_program::id(), feature_set::add_new_reserved_account_keys::id()), + ReservedAccount::new_pending(address_lookup_table::program::id(), feature_set::add_new_reserved_account_keys::id()), + ReservedAccount::new_active(bpf_loader::id()), + ReservedAccount::new_active(bpf_loader_deprecated::id()), + ReservedAccount::new_active(bpf_loader_upgradeable::id()), + ReservedAccount::new_pending(compute_budget::id(), feature_set::add_new_reserved_account_keys::id()), + ReservedAccount::new_active(config::program::id()), + ReservedAccount::new_pending(ed25519_program::id(), feature_set::add_new_reserved_account_keys::id()), + ReservedAccount::new_active(feature::id()), + ReservedAccount::new_pending(loader_v4::id(), feature_set::add_new_reserved_account_keys::id()), + ReservedAccount::new_pending(secp256k1_program::id(), feature_set::add_new_reserved_account_keys::id()), #[allow(deprecated)] - ReservedAccountKey::new_active(stake::config::id()), - ReservedAccountKey::new_active(stake::program::id()), - ReservedAccountKey::new_active(system_program::id()), - ReservedAccountKey::new_active(vote::program::id()), - ReservedAccountKey::new_pending(zk_token_proof_program::id(), feature_set::add_new_reserved_account_keys::id()), + ReservedAccount::new_active(stake::config::id()), + ReservedAccount::new_active(stake::program::id()), + ReservedAccount::new_active(system_program::id()), + ReservedAccount::new_active(vote::program::id()), + ReservedAccount::new_pending(zk_token_proof_program::id(), feature_set::add_new_reserved_account_keys::id()), // sysvars - ReservedAccountKey::new_active(sysvar::clock::id()), - ReservedAccountKey::new_pending(sysvar::epoch_rewards::id(), feature_set::add_new_reserved_account_keys::id()), - ReservedAccountKey::new_active(sysvar::epoch_schedule::id()), + ReservedAccount::new_active(sysvar::clock::id()), + ReservedAccount::new_pending(sysvar::epoch_rewards::id(), feature_set::add_new_reserved_account_keys::id()), + ReservedAccount::new_active(sysvar::epoch_schedule::id()), #[allow(deprecated)] - ReservedAccountKey::new_active(sysvar::fees::id()), - ReservedAccountKey::new_active(sysvar::instructions::id()), - ReservedAccountKey::new_pending(sysvar::last_restart_slot::id(), feature_set::add_new_reserved_account_keys::id()), + ReservedAccount::new_active(sysvar::fees::id()), + ReservedAccount::new_active(sysvar::instructions::id()), + ReservedAccount::new_pending(sysvar::last_restart_slot::id(), feature_set::add_new_reserved_account_keys::id()), #[allow(deprecated)] - ReservedAccountKey::new_active(sysvar::recent_blockhashes::id()), - ReservedAccountKey::new_active(sysvar::rent::id()), - ReservedAccountKey::new_active(sysvar::rewards::id()), - ReservedAccountKey::new_active(sysvar::slot_hashes::id()), - ReservedAccountKey::new_active(sysvar::slot_history::id()), - ReservedAccountKey::new_active(sysvar::stake_history::id()), + ReservedAccount::new_active(sysvar::recent_blockhashes::id()), + ReservedAccount::new_active(sysvar::rent::id()), + ReservedAccount::new_active(sysvar::rewards::id()), + ReservedAccount::new_active(sysvar::slot_hashes::id()), + ReservedAccount::new_active(sysvar::slot_history::id()), + ReservedAccount::new_active(sysvar::stake_history::id()), // other - ReservedAccountKey::new_active(native_loader::id()), - ReservedAccountKey::new_pending(sysvar::id(), feature_set::add_new_reserved_account_keys::id()), + ReservedAccount::new_active(native_loader::id()), + ReservedAccount::new_pending(sysvar::id(), feature_set::add_new_reserved_account_keys::id()), ].to_vec(); } @@ -142,53 +184,63 @@ mod tests { }; #[test] - fn test_reserved_account_key_is_active() { + fn test_is_reserved() { let feature_id = Pubkey::new_unique(); - let old_reserved_account_key = ReservedAccountKey::new_active(Pubkey::new_unique()); - let new_reserved_account_key = - ReservedAccountKey::new_pending(Pubkey::new_unique(), feature_id); + let active_reserved_account = ReservedAccount::new_active(Pubkey::new_unique()); + let pending_reserved_account = + ReservedAccount::new_pending(Pubkey::new_unique(), feature_id); + let reserved_account_keys = + ReservedAccountKeys::new(&[active_reserved_account, pending_reserved_account]); - let mut feature_set = FeatureSet::default(); assert!( - old_reserved_account_key.is_active(&feature_set), - "if not feature id is set, the key should be active" + reserved_account_keys.is_reserved(&active_reserved_account.key), + "active reserved accounts should be inserted into the active set" ); assert!( - !new_reserved_account_key.is_active(&feature_set), - "if feature id is set but not in the feature set, the key should not be active" - ); - - feature_set.active.insert(feature_id, 0); - assert!( - new_reserved_account_key.is_active(&feature_set), - "if feature id is set and is in the feature set, the key should be active" + !reserved_account_keys.is_reserved(&pending_reserved_account.key), + "pending reserved accounts should NOT be inserted into the active set" ); } #[test] - fn test_reserved_account_keys_compute_active() { - let feature_id = Pubkey::new_unique(); - let key0 = Pubkey::new_unique(); - let key1 = Pubkey::new_unique(); - let test_account_keys = vec![ - ReservedAccountKey::new_active(key0), - ReservedAccountKey::new_pending(key1, feature_id), - ReservedAccountKey::new_pending(Pubkey::new_unique(), Pubkey::new_unique()), + fn test_update_active_set() { + let feature_ids = [Pubkey::new_unique(), Pubkey::new_unique()]; + let active_reserved_key = Pubkey::new_unique(); + let pending_reserved_keys = [Pubkey::new_unique(), Pubkey::new_unique()]; + let reserved_accounts = vec![ + ReservedAccount::new_active(active_reserved_key), + ReservedAccount::new_pending(pending_reserved_keys[0], feature_ids[0]), + ReservedAccount::new_pending(pending_reserved_keys[1], feature_ids[1]), ]; + let mut reserved_account_keys = ReservedAccountKeys::new(&reserved_accounts); + assert!(reserved_account_keys.is_reserved(&active_reserved_key)); + assert!(!reserved_account_keys.is_reserved(&pending_reserved_keys[0])); + assert!(!reserved_account_keys.is_reserved(&pending_reserved_keys[1])); + + // Updating the active set with a default feature set should be a no-op + let previous_reserved_account_keys = reserved_account_keys.clone(); let mut feature_set = FeatureSet::default(); - assert_eq!( - HashSet::from_iter([key0].into_iter()), - ReservedAccountKeys::compute_active(&test_account_keys, &feature_set), - "should only contain keys without feature ids" - ); + reserved_account_keys.update_active_set(&feature_set); + assert_eq!(reserved_account_keys, previous_reserved_account_keys); - feature_set.active.insert(feature_id, 0); - assert_eq!( - HashSet::from_iter([key0, key1].into_iter()), - ReservedAccountKeys::compute_active(&test_account_keys, &feature_set), - "should only contain keys without feature ids or with active feature ids" - ); + // Updating the active set with an activated feature should also activate + // the corresponding reserved key from inactive to active + feature_set.active.insert(feature_ids[0], 0); + reserved_account_keys.update_active_set(&feature_set); + + assert!(reserved_account_keys.is_reserved(&active_reserved_key)); + assert!(reserved_account_keys.is_reserved(&pending_reserved_keys[0])); + assert!(!reserved_account_keys.is_reserved(&pending_reserved_keys[1])); + + // Update the active set again to ensure that the inactive map is + // properly retained + feature_set.active.insert(feature_ids[1], 0); + reserved_account_keys.update_active_set(&feature_set); + + assert!(reserved_account_keys.is_reserved(&active_reserved_key)); + assert!(reserved_account_keys.is_reserved(&pending_reserved_keys[0])); + assert!(reserved_account_keys.is_reserved(&pending_reserved_keys[1])); } #[test] @@ -197,8 +249,8 @@ mod tests { static_set.extend(ALL_IDS.iter().cloned()); static_set.extend(BUILTIN_PROGRAMS_KEYS.iter().cloned()); - let initial_dynamic_set = ReservedAccountKeys::active(&FeatureSet::default()); + let initial_active_set = ReservedAccountKeys::default().active; - assert_eq!(initial_dynamic_set, static_set); + assert_eq!(initial_active_set, static_set); } }