agave_reserved_account_keys/
lib.rs

1//! Collection of reserved account keys that cannot be write-locked by transactions.
2//! New reserved account keys may be added as long as they specify a feature
3//! gate that transitions the key into read-only at an epoch boundary.
4#![cfg_attr(feature = "frozen-abi", feature(min_specialization))]
5#![cfg_attr(docsrs, feature(doc_auto_cfg))]
6use {
7    agave_feature_set::{self as feature_set, FeatureSet},
8    lazy_static::lazy_static,
9    solana_pubkey::Pubkey,
10    solana_sdk_ids::{
11        address_lookup_table, bpf_loader, bpf_loader_deprecated, bpf_loader_upgradeable,
12        compute_budget, config, ed25519_program, feature, loader_v4, native_loader,
13        secp256k1_program, secp256r1_program, stake, system_program, sysvar, vote,
14        zk_elgamal_proof_program, zk_token_proof_program,
15    },
16    std::collections::{HashMap, HashSet},
17};
18
19// ReservedAccountKeys is not serialized into or deserialized from bank
20// snapshots but the bank requires this trait to be implemented anyways.
21#[cfg(feature = "frozen-abi")]
22impl ::solana_frozen_abi::abi_example::AbiExample for ReservedAccountKeys {
23    fn example() -> Self {
24        // ReservedAccountKeys is not Serialize so just rely on Default.
25        ReservedAccountKeys::default()
26    }
27}
28
29/// `ReservedAccountKeys` holds the set of currently active/inactive
30/// account keys that are reserved by the protocol and may not be write-locked
31/// during transaction processing.
32#[derive(Debug, Clone, PartialEq)]
33pub struct ReservedAccountKeys {
34    /// Set of currently active reserved account keys
35    pub active: HashSet<Pubkey>,
36    /// Set of currently inactive reserved account keys that will be moved to the
37    /// active set when their feature id is activated
38    inactive: HashMap<Pubkey, Pubkey>,
39}
40
41impl Default for ReservedAccountKeys {
42    fn default() -> Self {
43        Self::new(&RESERVED_ACCOUNTS)
44    }
45}
46
47impl ReservedAccountKeys {
48    /// Compute a set of active / inactive reserved account keys from a list of
49    /// keys with a designated feature id. If a reserved account key doesn't
50    /// designate a feature id, it's already activated and should be inserted
51    /// into the active set. If it does have a feature id, insert the key and
52    /// its feature id into the inactive map.
53    fn new(reserved_accounts: &[ReservedAccount]) -> Self {
54        Self {
55            active: reserved_accounts
56                .iter()
57                .filter(|reserved| reserved.feature_id.is_none())
58                .map(|reserved| reserved.key)
59                .collect(),
60            inactive: reserved_accounts
61                .iter()
62                .filter_map(|ReservedAccount { key, feature_id }| {
63                    feature_id.as_ref().map(|feature_id| (*key, *feature_id))
64                })
65                .collect(),
66        }
67    }
68
69    /// Compute a set with all reserved keys active, regardless of whether their
70    /// feature was activated. This is not to be used by the runtime. Useful for
71    /// off-chain utilities that need to filter out reserved accounts.
72    pub fn new_all_activated() -> Self {
73        Self {
74            active: Self::all_keys_iter().copied().collect(),
75            inactive: HashMap::default(),
76        }
77    }
78
79    /// Returns whether the specified key is reserved
80    pub fn is_reserved(&self, key: &Pubkey) -> bool {
81        self.active.contains(key)
82    }
83
84    /// Move inactive reserved account keys to the active set if their feature
85    /// is active.
86    pub fn update_active_set(&mut self, feature_set: &FeatureSet) {
87        self.inactive.retain(|reserved_key, feature_id| {
88            if feature_set.is_active(feature_id) {
89                self.active.insert(*reserved_key);
90                false
91            } else {
92                true
93            }
94        });
95    }
96
97    /// Return an iterator over all active / inactive reserved keys. This is not
98    /// to be used by the runtime. Useful for off-chain utilities that need to
99    /// filter out reserved accounts.
100    pub fn all_keys_iter() -> impl Iterator<Item = &'static Pubkey> {
101        RESERVED_ACCOUNTS
102            .iter()
103            .map(|reserved_key| &reserved_key.key)
104    }
105
106    /// Return an empty set of reserved keys for visibility when using in
107    /// tests where the dynamic reserved key set is not available
108    pub fn empty_key_set() -> HashSet<Pubkey> {
109        HashSet::default()
110    }
111}
112
113/// `ReservedAccount` represents a reserved account that will not be
114/// write-lockable by transactions. If a feature id is set, the account will
115/// become read-only only after the feature has been activated.
116#[derive(Debug, Clone, Copy, Eq, PartialEq)]
117struct ReservedAccount {
118    key: Pubkey,
119    feature_id: Option<Pubkey>,
120}
121
122impl ReservedAccount {
123    fn new_pending(key: Pubkey, feature_id: Pubkey) -> Self {
124        Self {
125            key,
126            feature_id: Some(feature_id),
127        }
128    }
129
130    fn new_active(key: Pubkey) -> Self {
131        Self {
132            key,
133            feature_id: None,
134        }
135    }
136}
137
138// New reserved accounts should be added in alphabetical order and must specify
139// a feature id for activation. Reserved accounts cannot be removed from this
140// list without breaking consensus.
141lazy_static! {
142    static ref RESERVED_ACCOUNTS: Vec<ReservedAccount> = [
143        // builtin programs
144        ReservedAccount::new_pending(address_lookup_table::id(), feature_set::add_new_reserved_account_keys::id()),
145        ReservedAccount::new_active(bpf_loader::id()),
146        ReservedAccount::new_active(bpf_loader_deprecated::id()),
147        ReservedAccount::new_active(bpf_loader_upgradeable::id()),
148        ReservedAccount::new_pending(compute_budget::id(), feature_set::add_new_reserved_account_keys::id()),
149        ReservedAccount::new_active(config::id()),
150        ReservedAccount::new_pending(ed25519_program::id(), feature_set::add_new_reserved_account_keys::id()),
151        ReservedAccount::new_active(feature::id()),
152        ReservedAccount::new_pending(loader_v4::id(), feature_set::add_new_reserved_account_keys::id()),
153        ReservedAccount::new_pending(secp256k1_program::id(), feature_set::add_new_reserved_account_keys::id()),
154        ReservedAccount::new_pending(secp256r1_program::id(), feature_set::enable_secp256r1_precompile::id()),
155        #[allow(deprecated)]
156        ReservedAccount::new_active(stake::config::id()),
157        ReservedAccount::new_active(stake::id()),
158        ReservedAccount::new_active(system_program::id()),
159        ReservedAccount::new_active(vote::id()),
160        ReservedAccount::new_pending(zk_elgamal_proof_program::id(), feature_set::add_new_reserved_account_keys::id()),
161        ReservedAccount::new_pending(zk_token_proof_program::id(), feature_set::add_new_reserved_account_keys::id()),
162
163        // sysvars
164        ReservedAccount::new_active(sysvar::clock::id()),
165        ReservedAccount::new_pending(sysvar::epoch_rewards::id(), feature_set::add_new_reserved_account_keys::id()),
166        ReservedAccount::new_active(sysvar::epoch_schedule::id()),
167        #[allow(deprecated)]
168        ReservedAccount::new_active(sysvar::fees::id()),
169        ReservedAccount::new_active(sysvar::instructions::id()),
170        ReservedAccount::new_pending(sysvar::last_restart_slot::id(), feature_set::add_new_reserved_account_keys::id()),
171        #[allow(deprecated)]
172        ReservedAccount::new_active(sysvar::recent_blockhashes::id()),
173        ReservedAccount::new_active(sysvar::rent::id()),
174        ReservedAccount::new_active(sysvar::rewards::id()),
175        ReservedAccount::new_active(sysvar::slot_hashes::id()),
176        ReservedAccount::new_active(sysvar::slot_history::id()),
177        ReservedAccount::new_active(sysvar::stake_history::id()),
178
179        // other
180        ReservedAccount::new_active(native_loader::id()),
181        ReservedAccount::new_pending(sysvar::id(), feature_set::add_new_reserved_account_keys::id()),
182    ].to_vec();
183}
184
185#[cfg(test)]
186mod tests {
187    #![allow(deprecated)]
188    use {super::*, solana_message::legacy::BUILTIN_PROGRAMS_KEYS, solana_sysvar::ALL_IDS};
189
190    #[test]
191    fn test_is_reserved() {
192        let feature_id = Pubkey::new_unique();
193        let active_reserved_account = ReservedAccount::new_active(Pubkey::new_unique());
194        let pending_reserved_account =
195            ReservedAccount::new_pending(Pubkey::new_unique(), feature_id);
196        let reserved_account_keys =
197            ReservedAccountKeys::new(&[active_reserved_account, pending_reserved_account]);
198
199        assert!(
200            reserved_account_keys.is_reserved(&active_reserved_account.key),
201            "active reserved accounts should be inserted into the active set"
202        );
203        assert!(
204            !reserved_account_keys.is_reserved(&pending_reserved_account.key),
205            "pending reserved accounts should NOT be inserted into the active set"
206        );
207    }
208
209    #[test]
210    fn test_update_active_set() {
211        let feature_ids = [Pubkey::new_unique(), Pubkey::new_unique()];
212        let active_reserved_key = Pubkey::new_unique();
213        let pending_reserved_keys = [Pubkey::new_unique(), Pubkey::new_unique()];
214        let reserved_accounts = vec![
215            ReservedAccount::new_active(active_reserved_key),
216            ReservedAccount::new_pending(pending_reserved_keys[0], feature_ids[0]),
217            ReservedAccount::new_pending(pending_reserved_keys[1], feature_ids[1]),
218        ];
219
220        let mut reserved_account_keys = ReservedAccountKeys::new(&reserved_accounts);
221        assert!(reserved_account_keys.is_reserved(&active_reserved_key));
222        assert!(!reserved_account_keys.is_reserved(&pending_reserved_keys[0]));
223        assert!(!reserved_account_keys.is_reserved(&pending_reserved_keys[1]));
224
225        // Updating the active set with a default feature set should be a no-op
226        let previous_reserved_account_keys = reserved_account_keys.clone();
227        let mut feature_set = FeatureSet::default();
228        reserved_account_keys.update_active_set(&feature_set);
229        assert_eq!(reserved_account_keys, previous_reserved_account_keys);
230
231        // Updating the active set with an activated feature should also activate
232        // the corresponding reserved key from inactive to active
233        feature_set.active_mut().insert(feature_ids[0], 0);
234        reserved_account_keys.update_active_set(&feature_set);
235
236        assert!(reserved_account_keys.is_reserved(&active_reserved_key));
237        assert!(reserved_account_keys.is_reserved(&pending_reserved_keys[0]));
238        assert!(!reserved_account_keys.is_reserved(&pending_reserved_keys[1]));
239
240        // Update the active set again to ensure that the inactive map is
241        // properly retained
242        feature_set.active_mut().insert(feature_ids[1], 0);
243        reserved_account_keys.update_active_set(&feature_set);
244
245        assert!(reserved_account_keys.is_reserved(&active_reserved_key));
246        assert!(reserved_account_keys.is_reserved(&pending_reserved_keys[0]));
247        assert!(reserved_account_keys.is_reserved(&pending_reserved_keys[1]));
248    }
249
250    #[test]
251    fn test_static_list_compat() {
252        let mut static_set = HashSet::new();
253        static_set.extend(ALL_IDS.iter().cloned());
254        static_set.extend(BUILTIN_PROGRAMS_KEYS.iter().cloned());
255
256        let initial_active_set = ReservedAccountKeys::default().active;
257
258        assert_eq!(initial_active_set, static_set);
259    }
260}