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