solana_feature_gate_interface/
lib.rs

1//! Runtime features.
2//!
3//! Runtime features provide a mechanism for features to be simultaneously activated across the
4//! network. Since validators may choose when to upgrade, features must remain dormant until a
5//! sufficient majority of the network is running a version that would support a given feature.
6//!
7//! Feature activation is accomplished by:
8//! 1. Activation is requested by the feature authority, who issues a transaction to create the
9//!    feature account. The newly created feature account will have the value of
10//!    `Feature::default()`
11//! 2. When the next epoch is entered the runtime will check for new activation requests and
12//!    active them.  When this occurs, the activation slot is recorded in the feature account
13
14pub use solana_sdk_ids::feature::{check_id, id, ID};
15#[cfg(feature = "bincode")]
16use {
17    solana_account::{AccountSharedData, ReadableAccount, WritableAccount},
18    solana_account_info::AccountInfo,
19    solana_instruction::Instruction,
20    solana_program_error::ProgramError,
21    solana_pubkey::Pubkey,
22    solana_rent::Rent,
23    solana_system_interface::instruction as system_instruction,
24};
25
26#[cfg_attr(
27    feature = "serde",
28    derive(serde_derive::Deserialize, serde_derive::Serialize)
29)]
30#[derive(Default, Debug, PartialEq, Eq)]
31pub struct Feature {
32    pub activated_at: Option<u64>,
33}
34
35impl Feature {
36    pub const fn size_of() -> usize {
37        9 // see test_feature_size_of.
38    }
39
40    #[cfg(feature = "bincode")]
41    pub fn from_account_info(account_info: &AccountInfo) -> Result<Self, ProgramError> {
42        if *account_info.owner != id() {
43            return Err(ProgramError::InvalidAccountOwner);
44        }
45        if account_info.data_len() < Feature::size_of() {
46            return Err(ProgramError::InvalidAccountData);
47        }
48        bincode::deserialize(&account_info.data.borrow())
49            .map_err(|_| ProgramError::InvalidAccountData)
50    }
51}
52
53#[cfg(feature = "bincode")]
54/// Activate a feature
55pub fn activate(feature_id: &Pubkey, funding_address: &Pubkey, rent: &Rent) -> Vec<Instruction> {
56    activate_with_lamports(
57        feature_id,
58        funding_address,
59        rent.minimum_balance(Feature::size_of()),
60    )
61}
62
63#[cfg(feature = "bincode")]
64pub fn activate_with_lamports(
65    feature_id: &Pubkey,
66    funding_address: &Pubkey,
67    lamports: u64,
68) -> Vec<Instruction> {
69    vec![
70        system_instruction::transfer(funding_address, feature_id, lamports),
71        system_instruction::allocate(feature_id, Feature::size_of() as u64),
72        system_instruction::assign(feature_id, &id()),
73    ]
74}
75
76#[cfg(feature = "bincode")]
77pub fn from_account<T: ReadableAccount>(account: &T) -> Option<Feature> {
78    if account.owner() != &id() || account.data().len() < Feature::size_of() {
79        None
80    } else {
81        bincode::deserialize(account.data()).ok()
82    }
83}
84
85#[cfg(feature = "bincode")]
86pub fn to_account(feature: &Feature, account: &mut AccountSharedData) -> Option<()> {
87    bincode::serialize_into(account.data_as_mut_slice(), feature).ok()
88}
89
90#[cfg(feature = "bincode")]
91pub fn create_account(feature: &Feature, lamports: u64) -> AccountSharedData {
92    let data_len = Feature::size_of().max(bincode::serialized_size(feature).unwrap() as usize);
93    let mut account = AccountSharedData::new(lamports, data_len, &id());
94    to_account(feature, &mut account).unwrap();
95    account
96}
97
98#[cfg(test)]
99mod test {
100    use super::*;
101
102    #[test]
103    fn test_feature_size_of() {
104        assert_eq!(Feature::size_of() as u64, {
105            let feature = Feature {
106                activated_at: Some(0),
107            };
108            bincode::serialized_size(&feature).unwrap()
109        });
110        assert!(
111            Feature::size_of() >= bincode::serialized_size(&Feature::default()).unwrap() as usize
112        );
113        assert_eq!(Feature::default(), Feature { activated_at: None });
114
115        let features = [
116            Feature {
117                activated_at: Some(0),
118            },
119            Feature {
120                activated_at: Some(u64::MAX),
121            },
122        ];
123        for feature in &features {
124            assert_eq!(
125                Feature::size_of(),
126                bincode::serialized_size(feature).unwrap() as usize
127            );
128        }
129    }
130
131    #[test]
132    fn feature_from_account_info_none() {
133        let key = Pubkey::new_unique();
134        let mut lamports = 42;
135
136        let mut good_data = vec![0; Feature::size_of()];
137        let mut small_data = vec![0; Feature::size_of() - 1]; // Too small
138
139        assert_eq!(
140            Feature::from_account_info(&AccountInfo::new(
141                &key,
142                false,
143                false,
144                &mut lamports,
145                &mut good_data,
146                &id(),
147                false,
148                u64::MAX,
149            )),
150            Ok(Feature { activated_at: None })
151        );
152        assert_eq!(
153            Feature::from_account_info(&AccountInfo::new(
154                &key,
155                false,
156                false,
157                &mut lamports,
158                &mut small_data, // Too small
159                &id(),
160                false,
161                u64::MAX,
162            )),
163            Err(ProgramError::InvalidAccountData),
164        );
165        assert_eq!(
166            Feature::from_account_info(&AccountInfo::new(
167                &key,
168                false,
169                false,
170                &mut lamports,
171                &mut good_data,
172                &Pubkey::new_unique(), // Wrong owner
173                false,
174                u64::MAX,
175            )),
176            Err(ProgramError::InvalidAccountOwner),
177        );
178    }
179
180    #[test]
181    fn feature_deserialize_none() {
182        assert_eq!(
183            from_account(&AccountSharedData::new(42, Feature::size_of(), &id())),
184            Some(Feature { activated_at: None })
185        );
186        assert_eq!(
187            from_account(&AccountSharedData::new(
188                42,
189                Feature::size_of() - 1, // Too small
190                &id()
191            )),
192            None,
193        );
194        assert_eq!(
195            from_account(&AccountSharedData::new(
196                42,
197                Feature::size_of(),
198                &Pubkey::new_unique(), // Wrong owner
199            )),
200            None,
201        );
202    }
203}