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::{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        false,
120    )
121}
122
123pub fn create_genesis_config_with_alpenglow_vote_accounts(
124    mint_lamports: u64,
125    voting_keypairs: &[impl Borrow<ValidatorVoteKeypairs>],
126    stakes: Vec<u64>,
127) -> GenesisConfigInfo {
128    create_genesis_config_with_vote_accounts_and_cluster_type(
129        mint_lamports,
130        voting_keypairs,
131        stakes,
132        ClusterType::Development,
133        true,
134    )
135}
136
137pub fn create_genesis_config_with_vote_accounts_and_cluster_type(
138    mint_lamports: u64,
139    voting_keypairs: &[impl Borrow<ValidatorVoteKeypairs>],
140    stakes: Vec<u64>,
141    cluster_type: ClusterType,
142    is_alpenglow: bool,
143) -> GenesisConfigInfo {
144    assert!(!voting_keypairs.is_empty());
145    assert_eq!(voting_keypairs.len(), stakes.len());
146
147    let mint_keypair = Keypair::new();
148    let voting_keypair = voting_keypairs[0].borrow().vote_keypair.insecure_clone();
149
150    let validator_pubkey = voting_keypairs[0].borrow().node_keypair.pubkey();
151    let validator_bls_pubkey = if is_alpenglow {
152        let bls_keypair = BLSKeypair::derive_from_signer(
153            &voting_keypairs[0].borrow().vote_keypair,
154            BLS_KEYPAIR_DERIVE_SEED,
155        )
156        .unwrap();
157        Some(bls_pubkey_to_compressed_bytes(&bls_keypair.public))
158    } else {
159        None
160    };
161    let genesis_config = create_genesis_config_with_leader_ex(
162        mint_lamports,
163        &mint_keypair.pubkey(),
164        &validator_pubkey,
165        &voting_keypairs[0].borrow().vote_keypair.pubkey(),
166        &voting_keypairs[0].borrow().stake_keypair.pubkey(),
167        validator_bls_pubkey,
168        stakes[0],
169        VALIDATOR_LAMPORTS,
170        FeeRateGovernor::new(0, 0), // most tests can't handle transaction fees
171        Rent::free(),               // most tests don't expect rent
172        cluster_type,
173        vec![],
174    );
175
176    let mut genesis_config_info = GenesisConfigInfo {
177        genesis_config,
178        mint_keypair,
179        voting_keypair,
180        validator_pubkey,
181    };
182
183    for (validator_voting_keypairs, stake) in voting_keypairs[1..].iter().zip(&stakes[1..]) {
184        let node_pubkey = validator_voting_keypairs.borrow().node_keypair.pubkey();
185        let vote_pubkey = validator_voting_keypairs.borrow().vote_keypair.pubkey();
186        let stake_pubkey = validator_voting_keypairs.borrow().stake_keypair.pubkey();
187
188        // Create accounts
189        let node_account = Account::new(VALIDATOR_LAMPORTS, 0, &system_program::id());
190        let bls_pubkey_compressed = if is_alpenglow {
191            let bls_keypair = BLSKeypair::derive_from_signer(
192                &validator_voting_keypairs.borrow().vote_keypair,
193                BLS_KEYPAIR_DERIVE_SEED,
194            )
195            .unwrap();
196            Some(bls_pubkey_to_compressed_bytes(&bls_keypair.public))
197        } else {
198            None
199        };
200        let vote_account = vote_state::create_v4_account_with_authorized(
201            &node_pubkey,
202            &vote_pubkey,
203            &vote_pubkey,
204            bls_pubkey_compressed,
205            0,
206            *stake,
207        );
208        let stake_account = Account::from(stake_utils::create_stake_account(
209            &stake_pubkey,
210            &vote_pubkey,
211            &vote_account,
212            &genesis_config_info.genesis_config.rent,
213            *stake,
214        ));
215
216        let vote_account = Account::from(vote_account);
217
218        // Put newly created accounts into genesis
219        genesis_config_info.genesis_config.accounts.extend(vec![
220            (node_pubkey, node_account),
221            (vote_pubkey, vote_account),
222            (stake_pubkey, stake_account),
223        ]);
224    }
225
226    genesis_config_info
227}
228
229pub fn create_genesis_config_with_leader(
230    mint_lamports: u64,
231    validator_pubkey: &Pubkey,
232    validator_stake_lamports: u64,
233) -> GenesisConfigInfo {
234    // Use deterministic keypair so we don't get confused by randomness in tests
235    let mint_keypair = Keypair::from_seed(&[
236        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,
237        25, 26, 27, 28, 29, 30, 31,
238    ])
239    .unwrap();
240
241    create_genesis_config_with_leader_with_mint_keypair(
242        mint_keypair,
243        mint_lamports,
244        validator_pubkey,
245        validator_stake_lamports,
246    )
247}
248
249pub fn create_genesis_config_with_leader_with_mint_keypair(
250    mint_keypair: Keypair,
251    mint_lamports: u64,
252    validator_pubkey: &Pubkey,
253    validator_stake_lamports: u64,
254) -> GenesisConfigInfo {
255    // Use deterministic keypair so we don't get confused by randomness in tests
256    let voting_keypair = Keypair::from_seed(&[
257        32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54,
258        55, 56, 57, 58, 59, 60, 61, 62, 63,
259    ])
260    .unwrap();
261
262    let genesis_config = create_genesis_config_with_leader_ex(
263        mint_lamports,
264        &mint_keypair.pubkey(),
265        validator_pubkey,
266        &voting_keypair.pubkey(),
267        &Pubkey::new_unique(),
268        None,
269        validator_stake_lamports,
270        VALIDATOR_LAMPORTS,
271        FeeRateGovernor::new(0, 0), // most tests can't handle transaction fees
272        Rent::free(),               // most tests don't expect rent
273        ClusterType::Development,
274        vec![],
275    );
276
277    GenesisConfigInfo {
278        genesis_config,
279        mint_keypair,
280        voting_keypair,
281        validator_pubkey: *validator_pubkey,
282    }
283}
284
285pub fn activate_all_features_alpenglow(genesis_config: &mut GenesisConfig) {
286    do_activate_all_features::<true>(genesis_config);
287}
288
289pub fn activate_all_features(genesis_config: &mut GenesisConfig) {
290    do_activate_all_features::<false>(genesis_config);
291}
292
293fn do_activate_all_features<const IS_ALPENGLOW: bool>(genesis_config: &mut GenesisConfig) {
294    // Activate all features at genesis in development mode
295    for feature_id in FeatureSet::default().inactive() {
296        if IS_ALPENGLOW || *feature_id != agave_feature_set::alpenglow::id() {
297            activate_feature(genesis_config, *feature_id);
298        }
299    }
300}
301
302pub fn deactivate_features(
303    genesis_config: &mut GenesisConfig,
304    features_to_deactivate: &Vec<Pubkey>,
305) {
306    // Remove all features in `features_to_skip` from genesis
307    for deactivate_feature_pk in features_to_deactivate {
308        if FEATURE_NAMES.contains_key(deactivate_feature_pk) {
309            genesis_config.accounts.remove(deactivate_feature_pk);
310        } else {
311            warn!(
312                "Feature {deactivate_feature_pk:?} set for deactivation is not a known Feature \
313                 public key"
314            );
315        }
316    }
317}
318
319pub fn activate_feature(genesis_config: &mut GenesisConfig, feature_id: Pubkey) {
320    genesis_config.accounts.insert(
321        feature_id,
322        Account::from(feature::create_account(
323            &Feature {
324                activated_at: Some(0),
325            },
326            std::cmp::max(genesis_config.rent.minimum_balance(Feature::size_of()), 1),
327        )),
328    );
329}
330
331pub fn bls_pubkey_to_compressed_bytes(
332    bls_pubkey: &BLSPubkey,
333) -> [u8; BLS_PUBLIC_KEY_COMPRESSED_SIZE] {
334    let key = BLSPubkeyCompressed::try_from(bls_pubkey).unwrap();
335    bincode::serialize(&key).unwrap().try_into().unwrap()
336}
337
338#[allow(clippy::too_many_arguments)]
339pub fn create_genesis_config_with_leader_ex_no_features(
340    mint_lamports: u64,
341    mint_pubkey: &Pubkey,
342    validator_pubkey: &Pubkey,
343    validator_vote_account_pubkey: &Pubkey,
344    validator_stake_account_pubkey: &Pubkey,
345    validator_bls_pubkey: Option<[u8; BLS_PUBLIC_KEY_COMPRESSED_SIZE]>,
346    validator_stake_lamports: u64,
347    validator_lamports: u64,
348    fee_rate_governor: FeeRateGovernor,
349    rent: Rent,
350    cluster_type: ClusterType,
351    mut initial_accounts: Vec<(Pubkey, AccountSharedData)>,
352) -> GenesisConfig {
353    let validator_vote_account = vote_state::create_v4_account_with_authorized(
354        validator_pubkey,
355        validator_vote_account_pubkey,
356        validator_vote_account_pubkey,
357        validator_bls_pubkey,
358        0,
359        validator_stake_lamports,
360    );
361
362    let validator_stake_account = stake_utils::create_stake_account(
363        validator_stake_account_pubkey,
364        validator_vote_account_pubkey,
365        &validator_vote_account,
366        &rent,
367        validator_stake_lamports,
368    );
369
370    initial_accounts.push((
371        *mint_pubkey,
372        AccountSharedData::new(mint_lamports, 0, &system_program::id()),
373    ));
374    initial_accounts.push((
375        *validator_pubkey,
376        AccountSharedData::new(validator_lamports, 0, &system_program::id()),
377    ));
378    initial_accounts.push((*validator_vote_account_pubkey, validator_vote_account));
379    initial_accounts.push((*validator_stake_account_pubkey, validator_stake_account));
380
381    let native_mint_account = solana_account::AccountSharedData::from(Account {
382        owner: spl_generic_token::token::id(),
383        data: spl_generic_token::token::native_mint::ACCOUNT_DATA.to_vec(),
384        lamports: LAMPORTS_PER_SOL,
385        executable: false,
386        rent_epoch: 1,
387    });
388    initial_accounts.push((
389        spl_generic_token::token::native_mint::id(),
390        native_mint_account,
391    ));
392
393    let mut genesis_config = GenesisConfig {
394        accounts: initial_accounts
395            .iter()
396            .cloned()
397            .map(|(key, account)| (key, Account::from(account)))
398            .collect(),
399        fee_rate_governor,
400        rent,
401        cluster_type,
402        ..GenesisConfig::default()
403    };
404
405    add_genesis_stake_config_account(&mut genesis_config);
406    add_genesis_epoch_rewards_account(&mut genesis_config);
407
408    genesis_config
409}
410
411#[allow(clippy::too_many_arguments)]
412pub fn create_genesis_config_with_leader_ex(
413    mint_lamports: u64,
414    mint_pubkey: &Pubkey,
415    validator_pubkey: &Pubkey,
416    validator_vote_account_pubkey: &Pubkey,
417    validator_stake_account_pubkey: &Pubkey,
418    validator_bls_pubkey: Option<[u8; BLS_PUBLIC_KEY_COMPRESSED_SIZE]>,
419    validator_stake_lamports: u64,
420    validator_lamports: u64,
421    fee_rate_governor: FeeRateGovernor,
422    rent: Rent,
423    cluster_type: ClusterType,
424    initial_accounts: Vec<(Pubkey, AccountSharedData)>,
425) -> GenesisConfig {
426    let mut genesis_config = create_genesis_config_with_leader_ex_no_features(
427        mint_lamports,
428        mint_pubkey,
429        validator_pubkey,
430        validator_vote_account_pubkey,
431        validator_stake_account_pubkey,
432        validator_bls_pubkey,
433        validator_stake_lamports,
434        validator_lamports,
435        fee_rate_governor,
436        rent,
437        cluster_type,
438        initial_accounts,
439    );
440
441    if genesis_config.cluster_type == ClusterType::Development {
442        activate_all_features(&mut genesis_config);
443    }
444
445    genesis_config
446}
447
448#[allow(deprecated)]
449pub fn add_genesis_stake_config_account(genesis_config: &mut GenesisConfig) -> u64 {
450    let mut data = serialize(&ConfigKeys { keys: vec![] }).unwrap();
451    data.extend_from_slice(&serialize(&StakeConfig::default()).unwrap());
452    let lamports = std::cmp::max(genesis_config.rent.minimum_balance(data.len()), 1);
453    let account = AccountSharedData::from(Account {
454        lamports,
455        data,
456        owner: solana_sdk_ids::config::id(),
457        ..Account::default()
458    });
459
460    genesis_config.add_account(solana_stake_interface::config::id(), account);
461
462    lamports
463}
464
465pub fn add_genesis_epoch_rewards_account(genesis_config: &mut GenesisConfig) -> u64 {
466    let data = vec![0; EpochRewards::size_of()];
467    let lamports = std::cmp::max(genesis_config.rent.minimum_balance(data.len()), 1);
468
469    let account = AccountSharedData::create(lamports, data, sysvar::id(), false, u64::MAX);
470
471    genesis_config.add_account(epoch_rewards::id(), account);
472
473    lamports
474}
475
476// genesis investor accounts
477pub fn create_lockup_stake_account(
478    authorized: &Authorized,
479    lockup: &Lockup,
480    rent: &Rent,
481    lamports: u64,
482) -> AccountSharedData {
483    let mut stake_account =
484        AccountSharedData::new(lamports, StakeStateV2::size_of(), &stake_program::id());
485
486    let rent_exempt_reserve = rent.minimum_balance(stake_account.data().len());
487    assert!(
488        lamports >= rent_exempt_reserve,
489        "lamports: {lamports} is less than rent_exempt_reserve {rent_exempt_reserve}"
490    );
491
492    stake_account
493        .set_state(&StakeStateV2::Initialized(Meta {
494            authorized: *authorized,
495            lockup: *lockup,
496            rent_exempt_reserve,
497        }))
498        .expect("set_state");
499
500    stake_account
501}