Skip to main content

solana_runtime/
genesis_utils.rs

1#[expect(deprecated)]
2use solana_stake_interface::config::Config as StakeConfig;
3use {
4    crate::{
5        bank::DEFAULT_VAT_TO_BURN_PER_EPOCH,
6        block_component_processor::vote_reward::epoch_inflation_account_state::EpochInflationAccountState,
7        stake_utils,
8    },
9    agave_feature_set::{FEATURE_NAMES, FeatureSet},
10    agave_votor_messages::{
11        self,
12        consensus_message::{BLS_KEYPAIR_DERIVE_SEED, Block},
13        migration::GENESIS_CERTIFICATE_ACCOUNT,
14        wire::{WireBlockCertMessage, WireCertSignature},
15    },
16    bincode::serialize,
17    bitvec::vec::BitVec,
18    log::*,
19    solana_account::{Account, AccountSharedData, ReadableAccount, state_traits::StateMut},
20    solana_bls_signatures::{
21        BLS_SIGNATURE_AFFINE_SIZE, Pubkey as BLSPubkey, Signature as BLSSignature,
22        keypair::Keypair as BLSKeypair, pubkey::PubkeyCompressed as BLSPubkeyCompressed,
23    },
24    solana_clock::Epoch,
25    solana_cluster_type::ClusterType,
26    solana_config_interface::state::ConfigKeys,
27    solana_feature_gate_interface::{self as feature, Feature},
28    solana_fee_calculator::FeeRateGovernor,
29    solana_genesis_config::GenesisConfig,
30    solana_hash::Hash,
31    solana_keypair::Keypair,
32    solana_native_token::LAMPORTS_PER_SOL,
33    solana_pubkey::Pubkey,
34    solana_rent::Rent,
35    solana_sdk_ids::{stake as stake_program, sysvar},
36    solana_seed_derivable::SeedDerivable,
37    solana_signer::Signer,
38    solana_signer_store::encode_base2,
39    solana_stake_interface::state::{Authorized, Lockup, Meta, StakeStateV2},
40    solana_system_interface::program as system_program,
41    solana_sysvar::{
42        SysvarSerialize,
43        epoch_rewards::{self, EpochRewards},
44    },
45    solana_vote_interface::state::{BLS_PUBLIC_KEY_COMPRESSED_SIZE, VoteStateV4},
46    solana_vote_program::vote_state,
47    std::{borrow::Borrow, sync::Arc},
48};
49
50// Default amount received by the validator
51const VALIDATOR_LAMPORTS: u64 = 890_880;
52
53// Default minimum vote account balance used by tests/genesis helpers. This is
54// conservative once shorter slot-time regimes lower the live bank VAT burn.
55pub fn minimum_vote_account_balance_for_vat(num_epochs: Epoch) -> u64 {
56    DEFAULT_VAT_TO_BURN_PER_EPOCH * num_epochs
57        + Rent::default().minimum_balance(VoteStateV4::size_of())
58}
59
60// Minimum stake lamports required for a valid stake account with non-zero stake.
61// This is rent_exempt_reserve + 1 lamport of actual stake.
62pub fn minimum_stake_lamports_for_vat(rent: &Rent) -> u64 {
63    rent.minimum_balance(StakeStateV2::size_of()) + 1
64}
65
66pub fn bootstrap_validator_stake_lamports() -> u64 {
67    minimum_stake_lamports_for_vat(&Rent::default())
68}
69
70// Number of lamports automatically used for genesis accounts
71pub const fn genesis_sysvar_and_builtin_program_lamports() -> u64 {
72    const NUM_BUILTIN_PROGRAMS: u64 = 6;
73    const NUM_PRECOMPILES: u64 = 3;
74    const STAKE_HISTORY_MIN_BALANCE: u64 = 114_979_200;
75    const CLOCK_SYSVAR_MIN_BALANCE: u64 = 1_169_280;
76    const RENT_SYSVAR_MIN_BALANCE: u64 = 1_009_200;
77    const EPOCH_SCHEDULE_SYSVAR_MIN_BALANCE: u64 = 1_120_560;
78    const RECENT_BLOCKHASHES_SYSVAR_MIN_BALANCE: u64 = 42_706_560;
79    const LAST_RESTART_SLOT_SYSVAR_MIN_BALANCE: u64 = 946_560;
80
81    STAKE_HISTORY_MIN_BALANCE
82        + CLOCK_SYSVAR_MIN_BALANCE
83        + RENT_SYSVAR_MIN_BALANCE
84        + EPOCH_SCHEDULE_SYSVAR_MIN_BALANCE
85        + RECENT_BLOCKHASHES_SYSVAR_MIN_BALANCE
86        + LAST_RESTART_SLOT_SYSVAR_MIN_BALANCE
87        + NUM_BUILTIN_PROGRAMS
88        + NUM_PRECOMPILES
89}
90
91#[derive(Debug)]
92pub struct ValidatorVoteKeypairs {
93    pub node_keypair: Keypair,
94    pub vote_keypair: Keypair,
95    pub stake_keypair: Keypair,
96    pub bls_keypair: BLSKeypair,
97}
98
99impl ValidatorVoteKeypairs {
100    pub fn new(node_keypair: Keypair, vote_keypair: Keypair, stake_keypair: Keypair) -> Self {
101        let bls_keypair =
102            BLSKeypair::derive_from_signer(&vote_keypair, BLS_KEYPAIR_DERIVE_SEED).unwrap();
103        Self {
104            node_keypair,
105            vote_keypair,
106            stake_keypair,
107            bls_keypair,
108        }
109    }
110
111    pub fn new_rand() -> Self {
112        let node_keypair = Keypair::new();
113        let vote_keypair = Keypair::new();
114        let stake_keypair = Keypair::new();
115        Self::new(node_keypair, vote_keypair, stake_keypair)
116    }
117}
118
119pub struct GenesisConfigInfo {
120    pub genesis_config: GenesisConfig,
121    pub mint_keypair: Keypair,
122    pub voting_keypair: Keypair,
123    pub validator_pubkey: Pubkey,
124}
125
126pub fn create_genesis_config(mint_lamports: u64) -> GenesisConfigInfo {
127    // Note that zero lamports for validator stake will result in stake account
128    // not being stored in accounts-db but still cached in bank stakes. This
129    // causes discrepancy between cached stakes accounts in bank and
130    // accounts-db which in particular will break snapshots test.
131    create_genesis_config_with_leader(
132        mint_lamports,
133        &solana_pubkey::new_rand(), // validator_pubkey
134        0,                          // validator_stake_lamports
135    )
136}
137
138pub fn create_genesis_config_with_vote_accounts(
139    mint_lamports: u64,
140    voting_keypairs: &[impl Borrow<ValidatorVoteKeypairs>],
141    stakes: Vec<u64>,
142) -> GenesisConfigInfo {
143    create_genesis_config_with_vote_accounts_and_cluster_type(
144        mint_lamports,
145        voting_keypairs,
146        stakes,
147        ClusterType::Development,
148        &FeatureSet::all_enabled(),
149        false,
150    )
151}
152
153#[cfg(feature = "dev-context-only-utils")]
154pub fn create_genesis_config_with_alpenglow_vote_accounts(
155    mint_lamports: u64,
156    voting_keypairs: &[impl Borrow<ValidatorVoteKeypairs>],
157    stakes: Vec<u64>,
158) -> GenesisConfigInfo {
159    create_genesis_config_with_vote_accounts_and_cluster_type(
160        mint_lamports,
161        voting_keypairs,
162        stakes,
163        ClusterType::Development,
164        &FeatureSet::all_enabled(),
165        true,
166    )
167}
168
169pub fn create_genesis_config_with_vote_accounts_and_cluster_type(
170    mint_lamports: u64,
171    voting_keypairs: &[impl Borrow<ValidatorVoteKeypairs>],
172    stakes: Vec<u64>,
173    cluster_type: ClusterType,
174    feature_set: &FeatureSet,
175    is_alpenglow: bool,
176) -> GenesisConfigInfo {
177    assert!(!voting_keypairs.is_empty());
178    assert_eq!(voting_keypairs.len(), stakes.len());
179
180    let mint_keypair = Keypair::new();
181    let voting_keypair = voting_keypairs[0].borrow().vote_keypair.insecure_clone();
182
183    let validator_pubkey = voting_keypairs[0].borrow().node_keypair.pubkey();
184    let validator_bls_pubkey = Some(
185        voting_keypairs[0]
186            .borrow()
187            .bls_keypair
188            .public
189            .to_bytes_compressed(),
190    );
191    let mut genesis_config = create_genesis_config_with_leader_ex(
192        mint_lamports,
193        &mint_keypair.pubkey(),
194        &validator_pubkey,
195        &voting_keypairs[0].borrow().vote_keypair.pubkey(),
196        &voting_keypairs[0].borrow().stake_keypair.pubkey(),
197        validator_bls_pubkey,
198        stakes[0],
199        VALIDATOR_LAMPORTS,
200        FeeRateGovernor::new(0, 0), // most tests can't handle transaction fees
201        Rent::free(),               // most tests don't expect rent
202        cluster_type,
203        feature_set,
204        vec![],
205    );
206
207    if is_alpenglow {
208        activate_all_features_alpenglow(&mut genesis_config);
209    }
210
211    let mut genesis_config_info = GenesisConfigInfo {
212        genesis_config,
213        mint_keypair,
214        voting_keypair,
215        validator_pubkey,
216    };
217
218    for (validator_voting_keypairs, &stake) in voting_keypairs[1..].iter().zip(&stakes[1..]) {
219        let node_pubkey = validator_voting_keypairs.borrow().node_keypair.pubkey();
220        let vote_pubkey = validator_voting_keypairs.borrow().vote_keypair.pubkey();
221        let stake_pubkey = validator_voting_keypairs.borrow().stake_keypair.pubkey();
222        let bls_pubkey = validator_voting_keypairs
223            .borrow()
224            .bls_keypair
225            .public
226            .to_bytes_compressed();
227
228        // Ensure minimum lamports for VAT filtering, but only when stake > 0.
229        // When stake is explicitly 0, respect that (e.g., for testing unstaked validator filtering).
230        let rent = &genesis_config_info.genesis_config.rent;
231        let (vote_account_lamports, stake_lamports) = if stake > 0 {
232            (
233                stake.max(minimum_vote_account_balance_for_vat(100)),
234                stake.max(minimum_stake_lamports_for_vat(rent)),
235            )
236        } else {
237            // Zero stake - just need rent exemption, no VAT minimums
238            (
239                rent.minimum_balance(VoteStateV4::size_of()),
240                rent.minimum_balance(StakeStateV2::size_of()),
241            )
242        };
243
244        let accounts = create_validator(
245            rent,
246            node_pubkey,
247            VALIDATOR_LAMPORTS,
248            vote_pubkey,
249            vote_account_lamports,
250            stake_pubkey,
251            stake_lamports,
252            Some(bls_pubkey),
253        )
254        .into_iter()
255        .map(|(pubkey, account)| (pubkey, Account::from(account)));
256        genesis_config_info.genesis_config.accounts.extend(accounts);
257    }
258
259    genesis_config_info
260}
261
262pub fn create_genesis_config_with_leader(
263    mint_lamports: u64,
264    validator_pubkey: &Pubkey,
265    validator_stake_lamports: u64,
266) -> GenesisConfigInfo {
267    // Use deterministic keypair so we don't get confused by randomness in tests
268    let mint_keypair = Keypair::from_seed(&[
269        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,
270        25, 26, 27, 28, 29, 30, 31,
271    ])
272    .unwrap();
273
274    create_genesis_config_with_leader_with_mint_keypair(
275        mint_keypair,
276        mint_lamports,
277        validator_pubkey,
278        validator_stake_lamports,
279    )
280}
281
282pub fn create_genesis_config_with_leader_with_mint_keypair(
283    mint_keypair: Keypair,
284    mint_lamports: u64,
285    validator_pubkey: &Pubkey,
286    validator_stake_lamports: u64,
287) -> GenesisConfigInfo {
288    // Use deterministic keypair so we don't get confused by randomness in tests
289    let voting_keypair = Keypair::from_seed(&[
290        32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54,
291        55, 56, 57, 58, 59, 60, 61, 62, 63,
292    ])
293    .unwrap();
294
295    let bls_keypair =
296        BLSKeypair::derive_from_signer(&voting_keypair, BLS_KEYPAIR_DERIVE_SEED).unwrap();
297    let validator_bls_pubkey = Some(bls_keypair.public.to_bytes_compressed());
298
299    let genesis_config = create_genesis_config_with_leader_ex(
300        mint_lamports,
301        &mint_keypair.pubkey(),
302        validator_pubkey,
303        &voting_keypair.pubkey(),
304        &Pubkey::new_unique(),
305        validator_bls_pubkey,
306        validator_stake_lamports,
307        VALIDATOR_LAMPORTS,
308        FeeRateGovernor::new(0, 0), // most tests can't handle transaction fees
309        Rent::free(),               // most tests don't expect rent
310        ClusterType::Development,
311        &FeatureSet::all_enabled(),
312        vec![],
313    );
314
315    GenesisConfigInfo {
316        genesis_config,
317        mint_keypair,
318        voting_keypair,
319        validator_pubkey: *validator_pubkey,
320    }
321}
322
323pub fn activate_all_features_alpenglow(genesis_config: &mut GenesisConfig) {
324    do_activate_all_features::<true>(genesis_config);
325    configure_alpenglow_at_genesis(genesis_config);
326}
327
328pub fn activate_alpenglow_at_genesis(genesis_config: &mut GenesisConfig) {
329    activate_feature(genesis_config, agave_feature_set::alpenglow::id());
330    configure_alpenglow_at_genesis(genesis_config);
331}
332
333fn configure_alpenglow_at_genesis(genesis_config: &mut GenesisConfig) {
334    // PoH is in low power mode
335    genesis_config.poh_config.hashes_per_tick = None;
336
337    // This is a dev cluster with alpenglow enabled at genesis. We don't want to test the migration pathway
338    // so we add a fake genesis certificate.
339    let cert = WireBlockCertMessage {
340        block: Block {
341            slot: 0,
342            block_id: Hash::default(),
343        },
344        signature: WireCertSignature {
345            signature: BLSSignature([0; BLS_SIGNATURE_AFFINE_SIZE]),
346            bitmap: encode_base2(&BitVec::new()).unwrap(),
347        },
348    };
349    let cert_size = bincode::serialized_size(&cert).unwrap();
350    let lamports = Rent::default().minimum_balance(cert_size as usize);
351    let certificate_account = Account::new_data(lamports, &cert, &system_program::ID).unwrap();
352
353    genesis_config
354        .accounts
355        .insert(*GENESIS_CERTIFICATE_ACCOUNT, certificate_account);
356    EpochInflationAccountState::insert_into_genesis_config(genesis_config);
357}
358
359pub fn activate_all_features(genesis_config: &mut GenesisConfig) {
360    do_activate_all_features::<false>(genesis_config);
361}
362
363fn do_activate_all_features<const IS_ALPENGLOW: bool>(genesis_config: &mut GenesisConfig) {
364    // Activate all features at genesis in development mode
365    for feature_id in FeatureSet::default().inactive() {
366        if IS_ALPENGLOW || *feature_id != agave_feature_set::alpenglow::id() {
367            activate_feature(genesis_config, *feature_id);
368        }
369    }
370}
371
372pub fn deactivate_features(
373    genesis_config: &mut GenesisConfig,
374    features_to_deactivate: &Vec<Pubkey>,
375) {
376    // Remove all features in `features_to_skip` from genesis
377    for deactivate_feature_pk in features_to_deactivate {
378        if FEATURE_NAMES.contains_key(deactivate_feature_pk) {
379            genesis_config.accounts.remove(deactivate_feature_pk);
380        } else {
381            warn!(
382                "Feature {deactivate_feature_pk:?} set for deactivation is not a known Feature \
383                 public key"
384            );
385        }
386    }
387}
388
389pub fn activate_feature(genesis_config: &mut GenesisConfig, feature_id: Pubkey) {
390    genesis_config.accounts.insert(
391        feature_id,
392        Account::from(feature::create_account(
393            &Feature {
394                activated_at: Some(0),
395            },
396            std::cmp::max(genesis_config.rent.minimum_balance(Feature::size_of()), 1),
397        )),
398    );
399}
400
401pub fn bls_pubkey_to_compressed_bytes(
402    bls_pubkey: &BLSPubkey,
403) -> [u8; BLS_PUBLIC_KEY_COMPRESSED_SIZE] {
404    let key = BLSPubkeyCompressed::try_from(bls_pubkey).unwrap();
405    bincode::serialize(&key).unwrap().try_into().unwrap()
406}
407
408pub(crate) fn create_validator(
409    rent: &Rent,
410    node_pubkey: Pubkey,
411    node_lamports: u64,
412    vote_pubkey: Pubkey,
413    vote_lamports: u64,
414    stake_pubkey: Pubkey,
415    stake_lamports: u64,
416    bls_pubkey: Option<[u8; BLS_PUBLIC_KEY_COMPRESSED_SIZE]>,
417) -> Vec<(Pubkey, AccountSharedData)> {
418    let vote_account = vote_state::create_v4_account_with_authorized(
419        &node_pubkey,
420        &vote_pubkey,
421        bls_pubkey.unwrap_or([0u8; BLS_PUBLIC_KEY_COMPRESSED_SIZE]),
422        &vote_pubkey,
423        0,
424        &vote_pubkey,
425        0,
426        &node_pubkey,
427        vote_lamports,
428    );
429
430    let stake_account = stake_utils::create_stake_account(
431        &stake_pubkey,
432        &vote_pubkey,
433        &vote_account,
434        rent,
435        stake_lamports,
436    );
437
438    let node_account = AccountSharedData::new(node_lamports, 0, &system_program::id());
439
440    vec![
441        (vote_pubkey, vote_account),
442        (stake_pubkey, stake_account),
443        (node_pubkey, node_account),
444    ]
445}
446
447#[expect(clippy::too_many_arguments)]
448pub fn create_genesis_config_with_leader_ex_no_features(
449    mint_lamports: u64,
450    mint_pubkey: &Pubkey,
451    validator_pubkey: &Pubkey,
452    validator_vote_account_pubkey: &Pubkey,
453    validator_stake_account_pubkey: &Pubkey,
454    validator_bls_pubkey: Option<[u8; BLS_PUBLIC_KEY_COMPRESSED_SIZE]>,
455    validator_stake_lamports: u64,
456    validator_lamports: u64,
457    fee_rate_governor: FeeRateGovernor,
458    rent: Rent,
459    cluster_type: ClusterType,
460    mut initial_accounts: Vec<(Pubkey, AccountSharedData)>,
461) -> GenesisConfig {
462    // Ensure minimum lamports for VAT filtering, but only when stake > 0.
463    // VAT requires non-zero stake, a BLS pubkey, and lamports >= the bank's
464    // current VAT burn plus rent-exempt minimum. This helper funds with the
465    // conservative default burn amount.
466    let (vote_account_lamports, stake_lamports) = if validator_stake_lamports > 0 {
467        (
468            validator_stake_lamports.max(minimum_vote_account_balance_for_vat(100)),
469            validator_stake_lamports.max(minimum_stake_lamports_for_vat(&rent)),
470        )
471    } else {
472        // Zero stake - just need rent exemption, no VAT minimums
473        (
474            rent.minimum_balance(VoteStateV4::size_of()),
475            rent.minimum_balance(StakeStateV2::size_of()),
476        )
477    };
478
479    initial_accounts.push((
480        *mint_pubkey,
481        AccountSharedData::new(mint_lamports, 0, &system_program::id()),
482    ));
483    let mut validator_accounts = create_validator(
484        &rent,
485        *validator_pubkey,
486        validator_lamports,
487        *validator_vote_account_pubkey,
488        vote_account_lamports,
489        *validator_stake_account_pubkey,
490        stake_lamports,
491        validator_bls_pubkey,
492    );
493    initial_accounts.append(&mut validator_accounts);
494
495    let native_mint_account = solana_account::AccountSharedData::from(Account {
496        owner: spl_generic_token::token::id(),
497        data: spl_generic_token::token::native_mint::ACCOUNT_DATA.to_vec(),
498        lamports: LAMPORTS_PER_SOL,
499        executable: false,
500        rent_epoch: 1,
501    });
502    initial_accounts.push((
503        spl_generic_token::token::native_mint::id(),
504        native_mint_account,
505    ));
506
507    let mut genesis_config = GenesisConfig {
508        accounts: initial_accounts
509            .iter()
510            .cloned()
511            .map(|(key, account)| (key, Account::from(account)))
512            .collect(),
513        fee_rate_governor,
514        rent,
515        cluster_type,
516        ..GenesisConfig::default()
517    };
518
519    add_genesis_stake_config_account(&mut genesis_config);
520    add_genesis_epoch_rewards_account(&mut genesis_config);
521
522    genesis_config
523}
524
525#[expect(clippy::too_many_arguments)]
526pub fn create_genesis_config_with_leader_ex(
527    mint_lamports: u64,
528    mint_pubkey: &Pubkey,
529    validator_pubkey: &Pubkey,
530    validator_vote_account_pubkey: &Pubkey,
531    validator_stake_account_pubkey: &Pubkey,
532    validator_bls_pubkey: Option<[u8; BLS_PUBLIC_KEY_COMPRESSED_SIZE]>,
533    validator_stake_lamports: u64,
534    validator_lamports: u64,
535    fee_rate_governor: FeeRateGovernor,
536    rent: Rent,
537    cluster_type: ClusterType,
538    feature_set: &FeatureSet,
539    initial_accounts: Vec<(Pubkey, AccountSharedData)>,
540) -> GenesisConfig {
541    let mut genesis_config = create_genesis_config_with_leader_ex_no_features(
542        mint_lamports,
543        mint_pubkey,
544        validator_pubkey,
545        validator_vote_account_pubkey,
546        validator_stake_account_pubkey,
547        validator_bls_pubkey,
548        validator_stake_lamports,
549        validator_lamports,
550        fee_rate_governor,
551        rent,
552        cluster_type,
553        initial_accounts,
554    );
555
556    for feature_id in feature_set.active().keys() {
557        // Skip alpenglow (existing behavior)
558        if *feature_id == agave_feature_set::alpenglow::id() {
559            continue;
560        }
561        activate_feature(&mut genesis_config, *feature_id);
562    }
563
564    genesis_config
565}
566
567#[expect(deprecated)]
568pub fn add_genesis_stake_config_account(genesis_config: &mut GenesisConfig) -> u64 {
569    let mut data = serialize(&ConfigKeys { keys: vec![] }).unwrap();
570    data.extend_from_slice(&serialize(&StakeConfig::default()).unwrap());
571    let lamports = std::cmp::max(genesis_config.rent.minimum_balance(data.len()), 1);
572    let account = AccountSharedData::from(Account {
573        lamports,
574        data,
575        owner: solana_sdk_ids::config::id(),
576        ..Account::default()
577    });
578
579    genesis_config.add_account(solana_stake_interface::config::id(), account);
580
581    lamports
582}
583
584pub fn add_genesis_epoch_rewards_account(genesis_config: &mut GenesisConfig) -> u64 {
585    let data = vec![0; EpochRewards::size_of()];
586    let lamports = std::cmp::max(genesis_config.rent.minimum_balance(data.len()), 1);
587
588    let account = AccountSharedData::create_from_existing_shared_data(
589        lamports,
590        Arc::new(data),
591        sysvar::id(),
592        false,
593        u64::MAX,
594    );
595
596    genesis_config.add_account(epoch_rewards::id(), account);
597
598    lamports
599}
600
601// genesis investor accounts
602pub fn create_lockup_stake_account(
603    authorized: &Authorized,
604    lockup: &Lockup,
605    rent: &Rent,
606    lamports: u64,
607) -> AccountSharedData {
608    let mut stake_account =
609        AccountSharedData::new(lamports, StakeStateV2::size_of(), &stake_program::id());
610
611    let rent_exempt_reserve = rent.minimum_balance(stake_account.data().len());
612    assert!(
613        lamports >= rent_exempt_reserve,
614        "lamports: {lamports} is less than rent_exempt_reserve {rent_exempt_reserve}"
615    );
616
617    stake_account
618        .set_state(&StakeStateV2::Initialized(Meta {
619            authorized: *authorized,
620            lockup: *lockup,
621            #[expect(deprecated)]
622            rent_exempt_reserve,
623        }))
624        .expect("set_state");
625
626    stake_account
627}