solana_runtime/
genesis_utils.rs

1#[allow(deprecated)]
2use solana_stake_interface::config::Config as StakeConfig;
3use {
4    crate::stake_utils,
5    agave_feature_set::{vote_state_v4, FeatureSet, FEATURE_NAMES},
6    agave_votor_messages::consensus_message::BLS_KEYPAIR_DERIVE_SEED,
7    bincode::serialize,
8    log::*,
9    solana_account::{
10        state_traits::StateMut, Account, AccountSharedData, ReadableAccount, WritableAccount,
11    },
12    solana_bls_signatures::{
13        keypair::Keypair as BLSKeypair, pubkey::PubkeyCompressed as BLSPubkeyCompressed,
14        Pubkey as BLSPubkey,
15    },
16    solana_cluster_type::ClusterType,
17    solana_config_interface::state::ConfigKeys,
18    solana_feature_gate_interface::{self as feature, Feature},
19    solana_fee_calculator::FeeRateGovernor,
20    solana_genesis_config::GenesisConfig,
21    solana_keypair::Keypair,
22    solana_native_token::LAMPORTS_PER_SOL,
23    solana_pubkey::Pubkey,
24    solana_rent::Rent,
25    solana_sdk_ids::{stake as stake_program, sysvar},
26    solana_seed_derivable::SeedDerivable,
27    solana_signer::Signer,
28    solana_stake_interface::state::{Authorized, Lockup, Meta, StakeStateV2},
29    solana_system_interface::program as system_program,
30    solana_sysvar::{
31        epoch_rewards::{self, EpochRewards},
32        SysvarSerialize,
33    },
34    solana_vote_interface::state::BLS_PUBLIC_KEY_COMPRESSED_SIZE,
35    solana_vote_program::vote_state,
36    std::borrow::Borrow,
37};
38
39// Default amount received by the validator
40const VALIDATOR_LAMPORTS: u64 = 42;
41
42// fun fact: rustc is very close to make this const fn.
43pub fn bootstrap_validator_stake_lamports() -> u64 {
44    Rent::default().minimum_balance(StakeStateV2::size_of())
45}
46
47// Number of lamports automatically used for genesis accounts
48pub const fn genesis_sysvar_and_builtin_program_lamports() -> u64 {
49    const NUM_BUILTIN_PROGRAMS: u64 = 6;
50    const NUM_PRECOMPILES: u64 = 2;
51    const STAKE_HISTORY_MIN_BALANCE: u64 = 114_979_200;
52    const CLOCK_SYSVAR_MIN_BALANCE: u64 = 1_169_280;
53    const RENT_SYSVAR_MIN_BALANCE: u64 = 1_009_200;
54    const EPOCH_SCHEDULE_SYSVAR_MIN_BALANCE: u64 = 1_120_560;
55    const RECENT_BLOCKHASHES_SYSVAR_MIN_BALANCE: u64 = 42_706_560;
56
57    STAKE_HISTORY_MIN_BALANCE
58        + CLOCK_SYSVAR_MIN_BALANCE
59        + RENT_SYSVAR_MIN_BALANCE
60        + EPOCH_SCHEDULE_SYSVAR_MIN_BALANCE
61        + RECENT_BLOCKHASHES_SYSVAR_MIN_BALANCE
62        + NUM_BUILTIN_PROGRAMS
63        + NUM_PRECOMPILES
64}
65
66pub struct ValidatorVoteKeypairs {
67    pub node_keypair: Keypair,
68    pub vote_keypair: Keypair,
69    pub stake_keypair: Keypair,
70}
71
72impl ValidatorVoteKeypairs {
73    pub fn new(node_keypair: Keypair, vote_keypair: Keypair, stake_keypair: Keypair) -> Self {
74        Self {
75            node_keypair,
76            vote_keypair,
77            stake_keypair,
78        }
79    }
80
81    pub fn new_rand() -> Self {
82        Self {
83            node_keypair: Keypair::new(),
84            vote_keypair: Keypair::new(),
85            stake_keypair: Keypair::new(),
86        }
87    }
88}
89
90pub struct GenesisConfigInfo {
91    pub genesis_config: GenesisConfig,
92    pub mint_keypair: Keypair,
93    pub voting_keypair: Keypair,
94    pub validator_pubkey: Pubkey,
95}
96
97pub fn create_genesis_config(mint_lamports: u64) -> GenesisConfigInfo {
98    // Note that zero lamports for validator stake will result in stake account
99    // not being stored in accounts-db but still cached in bank stakes. This
100    // causes discrepancy between cached stakes accounts in bank and
101    // accounts-db which in particular will break snapshots test.
102    create_genesis_config_with_leader(
103        mint_lamports,
104        &solana_pubkey::new_rand(), // validator_pubkey
105        0,                          // validator_stake_lamports
106    )
107}
108
109pub fn create_genesis_config_with_vote_accounts(
110    mint_lamports: u64,
111    voting_keypairs: &[impl Borrow<ValidatorVoteKeypairs>],
112    stakes: Vec<u64>,
113) -> GenesisConfigInfo {
114    create_genesis_config_with_vote_accounts_and_cluster_type(
115        mint_lamports,
116        voting_keypairs,
117        stakes,
118        ClusterType::Development,
119        &FeatureSet::all_enabled(),
120        false,
121    )
122}
123
124pub fn create_genesis_config_with_alpenglow_vote_accounts(
125    mint_lamports: u64,
126    voting_keypairs: &[impl Borrow<ValidatorVoteKeypairs>],
127    stakes: Vec<u64>,
128) -> GenesisConfigInfo {
129    create_genesis_config_with_vote_accounts_and_cluster_type(
130        mint_lamports,
131        voting_keypairs,
132        stakes,
133        ClusterType::Development,
134        &FeatureSet::all_enabled(),
135        true,
136    )
137}
138
139pub fn create_genesis_config_with_vote_accounts_and_cluster_type(
140    mint_lamports: u64,
141    voting_keypairs: &[impl Borrow<ValidatorVoteKeypairs>],
142    stakes: Vec<u64>,
143    cluster_type: ClusterType,
144    feature_set: &FeatureSet,
145    is_alpenglow: bool,
146) -> GenesisConfigInfo {
147    assert!(!voting_keypairs.is_empty());
148    assert_eq!(voting_keypairs.len(), stakes.len());
149
150    let mint_keypair = Keypair::new();
151    let voting_keypair = voting_keypairs[0].borrow().vote_keypair.insecure_clone();
152
153    let validator_pubkey = voting_keypairs[0].borrow().node_keypair.pubkey();
154    let validator_bls_pubkey = if is_alpenglow {
155        let bls_keypair = BLSKeypair::derive_from_signer(
156            &voting_keypairs[0].borrow().vote_keypair,
157            BLS_KEYPAIR_DERIVE_SEED,
158        )
159        .unwrap();
160        Some(bls_pubkey_to_compressed_bytes(&bls_keypair.public))
161    } else {
162        None
163    };
164    let genesis_config = create_genesis_config_with_leader_ex(
165        mint_lamports,
166        &mint_keypair.pubkey(),
167        &validator_pubkey,
168        &voting_keypairs[0].borrow().vote_keypair.pubkey(),
169        &voting_keypairs[0].borrow().stake_keypair.pubkey(),
170        validator_bls_pubkey,
171        stakes[0],
172        VALIDATOR_LAMPORTS,
173        FeeRateGovernor::new(0, 0), // most tests can't handle transaction fees
174        Rent::free(),               // most tests don't expect rent
175        cluster_type,
176        feature_set,
177        vec![],
178    );
179
180    let mut genesis_config_info = GenesisConfigInfo {
181        genesis_config,
182        mint_keypair,
183        voting_keypair,
184        validator_pubkey,
185    };
186
187    for (validator_voting_keypairs, stake) in voting_keypairs[1..].iter().zip(&stakes[1..]) {
188        let node_pubkey = validator_voting_keypairs.borrow().node_keypair.pubkey();
189        let vote_pubkey = validator_voting_keypairs.borrow().vote_keypair.pubkey();
190        let stake_pubkey = validator_voting_keypairs.borrow().stake_keypair.pubkey();
191
192        // Create accounts
193        let node_account = Account::new(VALIDATOR_LAMPORTS, 0, &system_program::id());
194        let bls_pubkey_compressed = if is_alpenglow {
195            let bls_keypair = BLSKeypair::derive_from_signer(
196                &validator_voting_keypairs.borrow().vote_keypair,
197                BLS_KEYPAIR_DERIVE_SEED,
198            )
199            .unwrap();
200            Some(bls_pubkey_to_compressed_bytes(&bls_keypair.public))
201        } else {
202            None
203        };
204        let vote_account = if feature_set.is_active(&vote_state_v4::id()) {
205            // Vote state v4 feature active. Create a v4 account.
206            vote_state::create_v4_account_with_authorized(
207                &node_pubkey,
208                &vote_pubkey,
209                &vote_pubkey,
210                bls_pubkey_compressed,
211                0,
212                *stake,
213            )
214        } else {
215            // Vote state v4 feature inactive. Create a v3 account.
216            if bls_pubkey_compressed.is_some() {
217                warn!(
218                    "BLS pubkey provided but vote_state_v4 feature is not active. BLS pubkey will \
219                     be ignored."
220                );
221            }
222            vote_state::create_v3_account_with_authorized(
223                &node_pubkey,
224                &vote_pubkey,
225                &vote_pubkey,
226                0,
227                *stake,
228            )
229        };
230        let stake_account = Account::from(stake_utils::create_stake_account(
231            &stake_pubkey,
232            &vote_pubkey,
233            &vote_account,
234            &genesis_config_info.genesis_config.rent,
235            *stake,
236        ));
237
238        let vote_account = Account::from(vote_account);
239
240        // Put newly created accounts into genesis
241        genesis_config_info.genesis_config.accounts.extend(vec![
242            (node_pubkey, node_account),
243            (vote_pubkey, vote_account),
244            (stake_pubkey, stake_account),
245        ]);
246    }
247
248    genesis_config_info
249}
250
251pub fn create_genesis_config_with_leader(
252    mint_lamports: u64,
253    validator_pubkey: &Pubkey,
254    validator_stake_lamports: u64,
255) -> GenesisConfigInfo {
256    // Use deterministic keypair so we don't get confused by randomness in tests
257    let mint_keypair = Keypair::from_seed(&[
258        0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24,
259        25, 26, 27, 28, 29, 30, 31,
260    ])
261    .unwrap();
262
263    create_genesis_config_with_leader_with_mint_keypair(
264        mint_keypair,
265        mint_lamports,
266        validator_pubkey,
267        validator_stake_lamports,
268    )
269}
270
271pub fn create_genesis_config_with_leader_with_mint_keypair(
272    mint_keypair: Keypair,
273    mint_lamports: u64,
274    validator_pubkey: &Pubkey,
275    validator_stake_lamports: u64,
276) -> GenesisConfigInfo {
277    // Use deterministic keypair so we don't get confused by randomness in tests
278    let voting_keypair = Keypair::from_seed(&[
279        32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54,
280        55, 56, 57, 58, 59, 60, 61, 62, 63,
281    ])
282    .unwrap();
283
284    let genesis_config = create_genesis_config_with_leader_ex(
285        mint_lamports,
286        &mint_keypair.pubkey(),
287        validator_pubkey,
288        &voting_keypair.pubkey(),
289        &Pubkey::new_unique(),
290        None,
291        validator_stake_lamports,
292        VALIDATOR_LAMPORTS,
293        FeeRateGovernor::new(0, 0), // most tests can't handle transaction fees
294        Rent::free(),               // most tests don't expect rent
295        ClusterType::Development,
296        &FeatureSet::all_enabled(),
297        vec![],
298    );
299
300    GenesisConfigInfo {
301        genesis_config,
302        mint_keypair,
303        voting_keypair,
304        validator_pubkey: *validator_pubkey,
305    }
306}
307
308pub fn activate_all_features_alpenglow(genesis_config: &mut GenesisConfig) {
309    do_activate_all_features::<true>(genesis_config);
310}
311
312pub fn activate_all_features(genesis_config: &mut GenesisConfig) {
313    do_activate_all_features::<false>(genesis_config);
314}
315
316fn do_activate_all_features<const IS_ALPENGLOW: bool>(genesis_config: &mut GenesisConfig) {
317    // Activate all features at genesis in development mode
318    for feature_id in FeatureSet::default().inactive() {
319        if IS_ALPENGLOW || *feature_id != agave_feature_set::alpenglow::id() {
320            activate_feature(genesis_config, *feature_id);
321        }
322    }
323}
324
325pub fn deactivate_features(
326    genesis_config: &mut GenesisConfig,
327    features_to_deactivate: &Vec<Pubkey>,
328) {
329    // Remove all features in `features_to_skip` from genesis
330    for deactivate_feature_pk in features_to_deactivate {
331        if FEATURE_NAMES.contains_key(deactivate_feature_pk) {
332            genesis_config.accounts.remove(deactivate_feature_pk);
333        } else {
334            warn!(
335                "Feature {deactivate_feature_pk:?} set for deactivation is not a known Feature \
336                 public key"
337            );
338        }
339    }
340}
341
342pub fn activate_feature(genesis_config: &mut GenesisConfig, feature_id: Pubkey) {
343    genesis_config.accounts.insert(
344        feature_id,
345        Account::from(feature::create_account(
346            &Feature {
347                activated_at: Some(0),
348            },
349            std::cmp::max(genesis_config.rent.minimum_balance(Feature::size_of()), 1),
350        )),
351    );
352}
353
354pub fn bls_pubkey_to_compressed_bytes(
355    bls_pubkey: &BLSPubkey,
356) -> [u8; BLS_PUBLIC_KEY_COMPRESSED_SIZE] {
357    let key = BLSPubkeyCompressed::try_from(bls_pubkey).unwrap();
358    bincode::serialize(&key).unwrap().try_into().unwrap()
359}
360
361#[allow(clippy::too_many_arguments)]
362pub fn create_genesis_config_with_leader_ex_no_features(
363    mint_lamports: u64,
364    mint_pubkey: &Pubkey,
365    validator_pubkey: &Pubkey,
366    validator_vote_account_pubkey: &Pubkey,
367    validator_stake_account_pubkey: &Pubkey,
368    validator_bls_pubkey: Option<[u8; BLS_PUBLIC_KEY_COMPRESSED_SIZE]>,
369    validator_stake_lamports: u64,
370    validator_lamports: u64,
371    fee_rate_governor: FeeRateGovernor,
372    rent: Rent,
373    cluster_type: ClusterType,
374    feature_set: &FeatureSet,
375    mut initial_accounts: Vec<(Pubkey, AccountSharedData)>,
376) -> GenesisConfig {
377    let validator_vote_account = if feature_set.is_active(&vote_state_v4::id()) {
378        // Vote state v4 feature active. Create a v4 account.
379        vote_state::create_v4_account_with_authorized(
380            validator_pubkey,
381            validator_vote_account_pubkey,
382            validator_vote_account_pubkey,
383            validator_bls_pubkey,
384            0,
385            validator_stake_lamports,
386        )
387    } else {
388        // Vote state v4 feature inactive. Create a v3 account.
389        if validator_bls_pubkey.is_some() {
390            warn!(
391                "BLS pubkey provided but vote_state_v4 feature is not active. BLS pubkey will be \
392                 ignored."
393            );
394        }
395        vote_state::create_v3_account_with_authorized(
396            validator_pubkey,
397            validator_vote_account_pubkey,
398            validator_vote_account_pubkey,
399            0,
400            validator_stake_lamports,
401        )
402    };
403
404    let validator_stake_account = stake_utils::create_stake_account(
405        validator_stake_account_pubkey,
406        validator_vote_account_pubkey,
407        &validator_vote_account,
408        &rent,
409        validator_stake_lamports,
410    );
411
412    initial_accounts.push((
413        *mint_pubkey,
414        AccountSharedData::new(mint_lamports, 0, &system_program::id()),
415    ));
416    initial_accounts.push((
417        *validator_pubkey,
418        AccountSharedData::new(validator_lamports, 0, &system_program::id()),
419    ));
420    initial_accounts.push((*validator_vote_account_pubkey, validator_vote_account));
421    initial_accounts.push((*validator_stake_account_pubkey, validator_stake_account));
422
423    let native_mint_account = solana_account::AccountSharedData::from(Account {
424        owner: spl_generic_token::token::id(),
425        data: spl_generic_token::token::native_mint::ACCOUNT_DATA.to_vec(),
426        lamports: LAMPORTS_PER_SOL,
427        executable: false,
428        rent_epoch: 1,
429    });
430    initial_accounts.push((
431        spl_generic_token::token::native_mint::id(),
432        native_mint_account,
433    ));
434
435    let mut genesis_config = GenesisConfig {
436        accounts: initial_accounts
437            .iter()
438            .cloned()
439            .map(|(key, account)| (key, Account::from(account)))
440            .collect(),
441        fee_rate_governor,
442        rent,
443        cluster_type,
444        ..GenesisConfig::default()
445    };
446
447    add_genesis_stake_config_account(&mut genesis_config);
448    add_genesis_epoch_rewards_account(&mut genesis_config);
449
450    genesis_config
451}
452
453#[allow(clippy::too_many_arguments)]
454pub fn create_genesis_config_with_leader_ex(
455    mint_lamports: u64,
456    mint_pubkey: &Pubkey,
457    validator_pubkey: &Pubkey,
458    validator_vote_account_pubkey: &Pubkey,
459    validator_stake_account_pubkey: &Pubkey,
460    validator_bls_pubkey: Option<[u8; BLS_PUBLIC_KEY_COMPRESSED_SIZE]>,
461    validator_stake_lamports: u64,
462    validator_lamports: u64,
463    fee_rate_governor: FeeRateGovernor,
464    rent: Rent,
465    cluster_type: ClusterType,
466    feature_set: &FeatureSet,
467    initial_accounts: Vec<(Pubkey, AccountSharedData)>,
468) -> GenesisConfig {
469    let mut genesis_config = create_genesis_config_with_leader_ex_no_features(
470        mint_lamports,
471        mint_pubkey,
472        validator_pubkey,
473        validator_vote_account_pubkey,
474        validator_stake_account_pubkey,
475        validator_bls_pubkey,
476        validator_stake_lamports,
477        validator_lamports,
478        fee_rate_governor,
479        rent,
480        cluster_type,
481        feature_set,
482        initial_accounts,
483    );
484
485    for feature_id in feature_set.active().keys() {
486        // Skip alpenglow (existing behavior)
487        if *feature_id == agave_feature_set::alpenglow::id() {
488            continue;
489        }
490        activate_feature(&mut genesis_config, *feature_id);
491    }
492
493    genesis_config
494}
495
496#[allow(deprecated)]
497pub fn add_genesis_stake_config_account(genesis_config: &mut GenesisConfig) -> u64 {
498    let mut data = serialize(&ConfigKeys { keys: vec![] }).unwrap();
499    data.extend_from_slice(&serialize(&StakeConfig::default()).unwrap());
500    let lamports = std::cmp::max(genesis_config.rent.minimum_balance(data.len()), 1);
501    let account = AccountSharedData::from(Account {
502        lamports,
503        data,
504        owner: solana_sdk_ids::config::id(),
505        ..Account::default()
506    });
507
508    genesis_config.add_account(solana_stake_interface::config::id(), account);
509
510    lamports
511}
512
513pub fn add_genesis_epoch_rewards_account(genesis_config: &mut GenesisConfig) -> u64 {
514    let data = vec![0; EpochRewards::size_of()];
515    let lamports = std::cmp::max(genesis_config.rent.minimum_balance(data.len()), 1);
516
517    let account = AccountSharedData::create(lamports, data, sysvar::id(), false, u64::MAX);
518
519    genesis_config.add_account(epoch_rewards::id(), account);
520
521    lamports
522}
523
524// genesis investor accounts
525pub fn create_lockup_stake_account(
526    authorized: &Authorized,
527    lockup: &Lockup,
528    rent: &Rent,
529    lamports: u64,
530) -> AccountSharedData {
531    let mut stake_account =
532        AccountSharedData::new(lamports, StakeStateV2::size_of(), &stake_program::id());
533
534    let rent_exempt_reserve = rent.minimum_balance(stake_account.data().len());
535    assert!(
536        lamports >= rent_exempt_reserve,
537        "lamports: {lamports} is less than rent_exempt_reserve {rent_exempt_reserve}"
538    );
539
540    stake_account
541        .set_state(&StakeStateV2::Initialized(Meta {
542            authorized: *authorized,
543            lockup: *lockup,
544            rent_exempt_reserve,
545        }))
546        .expect("set_state");
547
548    stake_account
549}