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