solana_runtime/bank/
fee_distribution.rs

1use {
2    super::Bank,
3    crate::bank::CollectorFeeDetails,
4    log::{debug, warn},
5    solana_feature_set::{remove_rounding_in_fee_calculation, reward_full_priority_fee},
6    solana_sdk::{
7        account::{ReadableAccount, WritableAccount},
8        fee::FeeBudgetLimits,
9        pubkey::Pubkey,
10        reward_info::RewardInfo,
11        reward_type::RewardType,
12        system_program,
13        transaction::SanitizedTransaction,
14    },
15    solana_svm_rent_collector::svm_rent_collector::SVMRentCollector,
16    solana_vote::vote_account::VoteAccountsHashMap,
17    std::{result::Result, sync::atomic::Ordering::Relaxed},
18    thiserror::Error,
19};
20
21#[derive(Error, Debug, PartialEq)]
22enum DepositFeeError {
23    #[error("fee account became rent paying")]
24    InvalidRentPayingAccount,
25    #[error("lamport overflow")]
26    LamportOverflow,
27    #[error("invalid fee account owner")]
28    InvalidAccountOwner,
29}
30
31impl Bank {
32    // Distribute collected transaction fees for this slot to collector_id (= current leader).
33    //
34    // Each validator is incentivized to process more transactions to earn more transaction fees.
35    // Transaction fees are rewarded for the computing resource utilization cost, directly
36    // proportional to their actual processing power.
37    //
38    // collector_id is rotated according to stake-weighted leader schedule. So the opportunity of
39    // earning transaction fees are fairly distributed by stake. And missing the opportunity
40    // (not producing a block as a leader) earns nothing. So, being online is incentivized as a
41    // form of transaction fees as well.
42    //
43    // On the other hand, rent fees are distributed under slightly different philosophy, while
44    // still being stake-weighted.
45    // Ref: distribute_rent_to_validators
46    pub(super) fn distribute_transaction_fees(&self) {
47        let collector_fees = self.collector_fees.load(Relaxed);
48        if collector_fees != 0 {
49            let (deposit, mut burn) = self.calculate_reward_and_burn_fees(collector_fees);
50            if deposit > 0 {
51                self.deposit_or_burn_fee(deposit, &mut burn);
52            }
53            self.capitalization.fetch_sub(burn, Relaxed);
54        }
55    }
56
57    // Replace `distribute_transaction_fees()` after Feature Gate: Reward full priority fee to
58    // validators #34731;
59    pub(super) fn distribute_transaction_fee_details(&self) {
60        let fee_details = self.collector_fee_details.read().unwrap();
61        if fee_details.total() == 0 {
62            // nothing to distribute, exit early
63            return;
64        }
65
66        let (deposit, mut burn) = self.calculate_reward_and_burn_fee_details(&fee_details);
67
68        if deposit > 0 {
69            self.deposit_or_burn_fee(deposit, &mut burn);
70        }
71        self.capitalization.fetch_sub(burn, Relaxed);
72    }
73
74    pub fn calculate_reward_for_transaction(
75        &self,
76        transaction: &SanitizedTransaction,
77        fee_budget_limits: &FeeBudgetLimits,
78    ) -> u64 {
79        let fee_details = solana_fee::calculate_fee_details(
80            transaction,
81            self.get_lamports_per_signature() == 0,
82            self.fee_structure().lamports_per_signature,
83            fee_budget_limits.prioritization_fee,
84            self.feature_set
85                .is_active(&remove_rounding_in_fee_calculation::id()),
86        );
87        let (reward, _burn) = if self.feature_set.is_active(&reward_full_priority_fee::id()) {
88            self.calculate_reward_and_burn_fee_details(&CollectorFeeDetails::from(fee_details))
89        } else {
90            let fee = fee_details.total_fee();
91            self.calculate_reward_and_burn_fees(fee)
92        };
93        reward
94    }
95
96    fn calculate_reward_and_burn_fees(&self, fee: u64) -> (u64, u64) {
97        self.fee_rate_governor.burn(fee)
98    }
99
100    fn calculate_reward_and_burn_fee_details(
101        &self,
102        fee_details: &CollectorFeeDetails,
103    ) -> (u64, u64) {
104        let (deposit, burn) = if fee_details.transaction_fee != 0 {
105            self.fee_rate_governor.burn(fee_details.transaction_fee)
106        } else {
107            (0, 0)
108        };
109        (deposit.saturating_add(fee_details.priority_fee), burn)
110    }
111
112    fn deposit_or_burn_fee(&self, deposit: u64, burn: &mut u64) {
113        match self.deposit_fees(&self.collector_id, deposit) {
114            Ok(post_balance) => {
115                self.rewards.write().unwrap().push((
116                    self.collector_id,
117                    RewardInfo {
118                        reward_type: RewardType::Fee,
119                        lamports: deposit as i64,
120                        post_balance,
121                        commission: None,
122                    },
123                ));
124            }
125            Err(err) => {
126                debug!(
127                    "Burned {} lamport tx fee instead of sending to {} due to {}",
128                    deposit, self.collector_id, err
129                );
130                datapoint_warn!(
131                    "bank-burned_fee",
132                    ("slot", self.slot(), i64),
133                    ("num_lamports", deposit, i64),
134                    ("error", err.to_string(), String),
135                );
136                *burn = burn.saturating_add(deposit);
137            }
138        }
139    }
140
141    // Deposits fees into a specified account and if successful, returns the new balance of that account
142    fn deposit_fees(&self, pubkey: &Pubkey, fees: u64) -> Result<u64, DepositFeeError> {
143        let mut account = self
144            .get_account_with_fixed_root_no_cache(pubkey)
145            .unwrap_or_default();
146
147        if !system_program::check_id(account.owner()) {
148            return Err(DepositFeeError::InvalidAccountOwner);
149        }
150
151        let recipient_pre_rent_state = self.rent_collector().get_account_rent_state(&account);
152        let distribution = account.checked_add_lamports(fees);
153        if distribution.is_err() {
154            return Err(DepositFeeError::LamportOverflow);
155        }
156
157        let recipient_post_rent_state = self.rent_collector().get_account_rent_state(&account);
158        let rent_state_transition_allowed = self
159            .rent_collector()
160            .transition_allowed(&recipient_pre_rent_state, &recipient_post_rent_state);
161        if !rent_state_transition_allowed {
162            return Err(DepositFeeError::InvalidRentPayingAccount);
163        }
164
165        self.store_account(pubkey, &account);
166        Ok(account.lamports())
167    }
168
169    // Distribute collected rent fees for this slot to staked validators (excluding stakers)
170    // according to stake.
171    //
172    // The nature of rent fee is the cost of doing business, every validator has to hold (or have
173    // access to) the same list of accounts, so we pay according to stake, which is a rough proxy for
174    // value to the network.
175    //
176    // Currently, rent distribution doesn't consider given validator's uptime at all (this might
177    // change). That's because rent should be rewarded for the storage resource utilization cost.
178    // It's treated differently from transaction fees, which is for the computing resource
179    // utilization cost.
180    //
181    // We can't use collector_id (which is rotated according to stake-weighted leader schedule)
182    // as an approximation to the ideal rent distribution to simplify and avoid this per-slot
183    // computation for the distribution (time: N log N, space: N acct. stores; N = # of
184    // validators).
185    // The reason is that rent fee doesn't need to be incentivized for throughput unlike transaction
186    // fees
187    //
188    // Ref: distribute_transaction_fees
189    fn distribute_rent_to_validators(
190        &self,
191        vote_accounts: &VoteAccountsHashMap,
192        rent_to_be_distributed: u64,
193    ) {
194        let mut total_staked = 0;
195
196        // Collect the stake associated with each validator.
197        // Note that a validator may be present in this vector multiple times if it happens to have
198        // more than one staked vote account somehow
199        let mut validator_stakes = vote_accounts
200            .iter()
201            .filter_map(|(_vote_pubkey, (staked, account))| {
202                if *staked == 0 {
203                    None
204                } else {
205                    total_staked += *staked;
206                    Some((*account.node_pubkey(), *staked))
207                }
208            })
209            .collect::<Vec<(Pubkey, u64)>>();
210
211        #[cfg(test)]
212        if validator_stakes.is_empty() {
213            // some tests bank.freezes() with bad staking state
214            self.capitalization
215                .fetch_sub(rent_to_be_distributed, Relaxed);
216            return;
217        }
218        #[cfg(not(test))]
219        assert!(!validator_stakes.is_empty());
220
221        // Sort first by stake and then by validator identity pubkey for determinism.
222        // If two items are still equal, their relative order does not matter since
223        // both refer to the same validator.
224        validator_stakes.sort_unstable_by(|(pubkey1, staked1), (pubkey2, staked2)| {
225            (staked1, pubkey1).cmp(&(staked2, pubkey2)).reverse()
226        });
227
228        let mut rent_distributed_in_initial_round = 0;
229        let validator_rent_shares = validator_stakes
230            .into_iter()
231            .map(|(pubkey, staked)| {
232                let rent_share = (((staked as u128) * (rent_to_be_distributed as u128))
233                    / (total_staked as u128))
234                    .try_into()
235                    .unwrap();
236                rent_distributed_in_initial_round += rent_share;
237                (pubkey, rent_share)
238            })
239            .collect::<Vec<(Pubkey, u64)>>();
240
241        // Leftover lamports after fraction calculation, will be paid to validators starting from highest stake
242        // holder
243        let mut leftover_lamports = rent_to_be_distributed - rent_distributed_in_initial_round;
244
245        let mut rent_to_burn: u64 = 0;
246        let mut rewards = vec![];
247        validator_rent_shares
248            .into_iter()
249            .for_each(|(pubkey, rent_share)| {
250                let rent_to_be_paid = if leftover_lamports > 0 {
251                    leftover_lamports -= 1;
252                    rent_share + 1
253                } else {
254                    rent_share
255                };
256                if rent_to_be_paid > 0 {
257                    match self.deposit_fees(&pubkey, rent_to_be_paid) {
258                        Ok(post_balance) => {
259                            rewards.push((
260                                pubkey,
261                                RewardInfo {
262                                    reward_type: RewardType::Rent,
263                                    lamports: rent_to_be_paid as i64,
264                                    post_balance,
265                                    commission: None,
266                                },
267                            ));
268                        }
269                        Err(err) => {
270                            debug!(
271                                "Burned {} lamport rent fee instead of sending to {} due to {}",
272                                rent_to_be_paid, pubkey, err
273                            );
274
275                            // overflow adding lamports or resulting account is invalid
276                            // so burn lamports and track lamports burned per slot
277                            rent_to_burn = rent_to_burn.saturating_add(rent_to_be_paid);
278                        }
279                    }
280                }
281            });
282        self.rewards.write().unwrap().append(&mut rewards);
283
284        if rent_to_burn > 0 {
285            self.capitalization.fetch_sub(rent_to_burn, Relaxed);
286            datapoint_warn!(
287                "bank-burned_rent",
288                ("slot", self.slot(), i64),
289                ("num_lamports", rent_to_burn, i64)
290            );
291        }
292
293        assert_eq!(leftover_lamports, 0);
294    }
295
296    pub(super) fn distribute_rent_fees(&self) {
297        let total_rent_collected = self.collected_rent.load(Relaxed);
298
299        if !self.should_collect_rent() {
300            if total_rent_collected != 0 {
301                warn!("Rent fees collection is disabled, yet total rent collected was non zero! Total rent collected: {total_rent_collected}");
302            }
303            return;
304        }
305
306        let (burned_portion, rent_to_be_distributed) = self
307            .rent_collector
308            .rent
309            .calculate_burn(total_rent_collected);
310
311        debug!(
312            "distributed rent: {} (rounded from: {}, burned: {})",
313            rent_to_be_distributed, total_rent_collected, burned_portion
314        );
315        self.capitalization.fetch_sub(burned_portion, Relaxed);
316
317        if rent_to_be_distributed == 0 {
318            return;
319        }
320
321        self.distribute_rent_to_validators(&self.vote_accounts(), rent_to_be_distributed);
322    }
323}
324
325#[cfg(test)]
326pub mod tests {
327    use {
328        super::*,
329        crate::genesis_utils::{
330            create_genesis_config, create_genesis_config_with_leader,
331            create_genesis_config_with_vote_accounts, ValidatorVoteKeypairs,
332        },
333        solana_sdk::{
334            account::AccountSharedData, native_token::sol_to_lamports, pubkey, rent::Rent,
335            signature::Signer,
336        },
337        solana_svm_rent_collector::rent_state::RentState,
338        std::sync::RwLock,
339    };
340
341    #[test]
342    fn test_deposit_or_burn_fee() {
343        #[derive(PartialEq)]
344        enum Scenario {
345            Normal,
346            InvalidOwner,
347            RentPaying,
348        }
349
350        struct TestCase {
351            scenario: Scenario,
352        }
353
354        impl TestCase {
355            fn new(scenario: Scenario) -> Self {
356                Self { scenario }
357            }
358        }
359
360        for test_case in [
361            TestCase::new(Scenario::Normal),
362            TestCase::new(Scenario::InvalidOwner),
363            TestCase::new(Scenario::RentPaying),
364        ] {
365            let mut genesis = create_genesis_config(0);
366            let rent = Rent::default();
367            let min_rent_exempt_balance = rent.minimum_balance(0);
368            genesis.genesis_config.rent = rent; // Ensure rent is non-zero, as genesis_utils sets Rent::free by default
369            let bank = Bank::new_for_tests(&genesis.genesis_config);
370
371            let deposit = 100;
372            let mut burn = 100;
373
374            if test_case.scenario == Scenario::RentPaying {
375                // ensure that account balance + collected fees will make it rent-paying
376                let initial_balance = 100;
377                let account = AccountSharedData::new(initial_balance, 0, &system_program::id());
378                bank.store_account(bank.collector_id(), &account);
379                assert!(initial_balance + deposit < min_rent_exempt_balance);
380            } else if test_case.scenario == Scenario::InvalidOwner {
381                // ensure that account owner is invalid and fee distribution will fail
382                let account =
383                    AccountSharedData::new(min_rent_exempt_balance, 0, &Pubkey::new_unique());
384                bank.store_account(bank.collector_id(), &account);
385            } else {
386                let account =
387                    AccountSharedData::new(min_rent_exempt_balance, 0, &system_program::id());
388                bank.store_account(bank.collector_id(), &account);
389            }
390
391            let initial_burn = burn;
392            let initial_collector_id_balance = bank.get_balance(bank.collector_id());
393            bank.deposit_or_burn_fee(deposit, &mut burn);
394            let new_collector_id_balance = bank.get_balance(bank.collector_id());
395
396            if test_case.scenario != Scenario::Normal {
397                assert_eq!(initial_collector_id_balance, new_collector_id_balance);
398                assert_eq!(initial_burn + deposit, burn);
399                let locked_rewards = bank.rewards.read().unwrap();
400                assert!(
401                    locked_rewards.is_empty(),
402                    "There should be no rewards distributed"
403                );
404            } else {
405                assert_eq!(
406                    initial_collector_id_balance + deposit,
407                    new_collector_id_balance
408                );
409
410                assert_eq!(initial_burn, burn);
411
412                let locked_rewards = bank.rewards.read().unwrap();
413                assert_eq!(
414                    locked_rewards.len(),
415                    1,
416                    "There should be one reward distributed"
417                );
418
419                let reward_info = &locked_rewards[0];
420                assert_eq!(
421                    reward_info.1.lamports, deposit as i64,
422                    "The reward amount should match the expected deposit"
423                );
424                assert_eq!(
425                    reward_info.1.reward_type,
426                    RewardType::Fee,
427                    "The reward type should be Fee"
428                );
429            }
430        }
431    }
432
433    #[test]
434    fn test_distribute_transaction_fees_normal() {
435        let genesis = create_genesis_config(0);
436        let bank = Bank::new_for_tests(&genesis.genesis_config);
437        let transaction_fees = 100;
438        bank.collector_fees.fetch_add(transaction_fees, Relaxed);
439        assert_eq!(transaction_fees, bank.collector_fees.load(Relaxed));
440        let (expected_collected_fees, burn_amount) = bank.fee_rate_governor.burn(transaction_fees);
441
442        let initial_capitalization = bank.capitalization();
443        let initial_collector_id_balance = bank.get_balance(bank.collector_id());
444        bank.distribute_transaction_fees();
445        let new_collector_id_balance = bank.get_balance(bank.collector_id());
446
447        assert_eq!(
448            initial_collector_id_balance + expected_collected_fees,
449            new_collector_id_balance
450        );
451        assert_eq!(initial_capitalization - burn_amount, bank.capitalization());
452        let locked_rewards = bank.rewards.read().unwrap();
453        assert_eq!(
454            locked_rewards.len(),
455            1,
456            "There should be one reward distributed"
457        );
458
459        let reward_info = &locked_rewards[0];
460        assert_eq!(
461            reward_info.1.lamports, expected_collected_fees as i64,
462            "The reward amount should match the expected deposit"
463        );
464        assert_eq!(
465            reward_info.1.reward_type,
466            RewardType::Fee,
467            "The reward type should be Fee"
468        );
469    }
470
471    #[test]
472    fn test_distribute_transaction_fees_zero() {
473        let genesis = create_genesis_config(0);
474        let bank = Bank::new_for_tests(&genesis.genesis_config);
475        assert_eq!(bank.collector_fees.load(Relaxed), 0);
476
477        let initial_capitalization = bank.capitalization();
478        let initial_collector_id_balance = bank.get_balance(bank.collector_id());
479        bank.distribute_transaction_fees();
480        let new_collector_id_balance = bank.get_balance(bank.collector_id());
481
482        assert_eq!(initial_collector_id_balance, new_collector_id_balance);
483        assert_eq!(initial_capitalization, bank.capitalization());
484        let locked_rewards = bank.rewards.read().unwrap();
485        assert!(
486            locked_rewards.is_empty(),
487            "There should be no rewards distributed"
488        );
489    }
490
491    #[test]
492    fn test_distribute_transaction_fees_burn_all() {
493        let mut genesis = create_genesis_config(0);
494        genesis.genesis_config.fee_rate_governor.burn_percent = 100;
495        let bank = Bank::new_for_tests(&genesis.genesis_config);
496        let transaction_fees = 100;
497        bank.collector_fees.fetch_add(transaction_fees, Relaxed);
498        assert_eq!(transaction_fees, bank.collector_fees.load(Relaxed));
499
500        let initial_capitalization = bank.capitalization();
501        let initial_collector_id_balance = bank.get_balance(bank.collector_id());
502        bank.distribute_transaction_fees();
503        let new_collector_id_balance = bank.get_balance(bank.collector_id());
504
505        assert_eq!(initial_collector_id_balance, new_collector_id_balance);
506        assert_eq!(
507            initial_capitalization - transaction_fees,
508            bank.capitalization()
509        );
510        let locked_rewards = bank.rewards.read().unwrap();
511        assert!(
512            locked_rewards.is_empty(),
513            "There should be no rewards distributed"
514        );
515    }
516
517    #[test]
518    fn test_distribute_transaction_fees_overflow_failure() {
519        let genesis = create_genesis_config(0);
520        let bank = Bank::new_for_tests(&genesis.genesis_config);
521        let transaction_fees = 100;
522        bank.collector_fees.fetch_add(transaction_fees, Relaxed);
523        assert_eq!(transaction_fees, bank.collector_fees.load(Relaxed));
524
525        // ensure that account balance will overflow and fee distribution will fail
526        let account = AccountSharedData::new(u64::MAX, 0, &system_program::id());
527        bank.store_account(bank.collector_id(), &account);
528
529        let initial_capitalization = bank.capitalization();
530        let initial_collector_id_balance = bank.get_balance(bank.collector_id());
531        bank.distribute_transaction_fees();
532        let new_collector_id_balance = bank.get_balance(bank.collector_id());
533
534        assert_eq!(initial_collector_id_balance, new_collector_id_balance);
535        assert_eq!(
536            initial_capitalization - transaction_fees,
537            bank.capitalization()
538        );
539        let locked_rewards = bank.rewards.read().unwrap();
540        assert!(
541            locked_rewards.is_empty(),
542            "There should be no rewards distributed"
543        );
544    }
545
546    #[test]
547    fn test_deposit_fees() {
548        let initial_balance = 1_000_000_000;
549        let genesis = create_genesis_config(initial_balance);
550        let bank = Bank::new_for_tests(&genesis.genesis_config);
551        let pubkey = genesis.mint_keypair.pubkey();
552        let deposit_amount = 500;
553
554        assert_eq!(
555            bank.deposit_fees(&pubkey, deposit_amount),
556            Ok(initial_balance + deposit_amount),
557            "New balance should be the sum of the initial balance and deposit amount"
558        );
559    }
560
561    #[test]
562    fn test_deposit_fees_with_overflow() {
563        let initial_balance = u64::MAX;
564        let genesis = create_genesis_config(initial_balance);
565        let bank = Bank::new_for_tests(&genesis.genesis_config);
566        let pubkey = genesis.mint_keypair.pubkey();
567        let deposit_amount = 500;
568
569        assert_eq!(
570            bank.deposit_fees(&pubkey, deposit_amount),
571            Err(DepositFeeError::LamportOverflow),
572            "Expected an error due to lamport overflow"
573        );
574    }
575
576    #[test]
577    fn test_deposit_fees_invalid_account_owner() {
578        let initial_balance = 1000;
579        let genesis = create_genesis_config_with_leader(0, &pubkey::new_rand(), initial_balance);
580        let bank = Bank::new_for_tests(&genesis.genesis_config);
581        let pubkey = genesis.voting_keypair.pubkey();
582        let deposit_amount = 500;
583
584        assert_eq!(
585            bank.deposit_fees(&pubkey, deposit_amount),
586            Err(DepositFeeError::InvalidAccountOwner),
587            "Expected an error due to invalid account owner"
588        );
589    }
590
591    #[test]
592    fn test_deposit_fees_invalid_rent_paying() {
593        let initial_balance = 0;
594        let genesis = create_genesis_config(initial_balance);
595        let pubkey = genesis.mint_keypair.pubkey();
596        let mut genesis_config = genesis.genesis_config;
597        genesis_config.rent = Rent::default(); // Ensure rent is non-zero, as genesis_utils sets Rent::free by default
598        let bank = Bank::new_for_tests(&genesis_config);
599        let min_rent_exempt_balance = genesis_config.rent.minimum_balance(0);
600
601        let deposit_amount = 500;
602        assert!(initial_balance + deposit_amount < min_rent_exempt_balance);
603
604        assert_eq!(
605            bank.deposit_fees(&pubkey, deposit_amount),
606            Err(DepositFeeError::InvalidRentPayingAccount),
607            "Expected an error due to invalid rent paying account"
608        );
609    }
610
611    #[test]
612    fn test_distribute_rent_to_validators_rent_paying() {
613        solana_logger::setup();
614
615        const RENT_PER_VALIDATOR: u64 = 55;
616        const TOTAL_RENT: u64 = RENT_PER_VALIDATOR * 4;
617
618        let empty_validator = ValidatorVoteKeypairs::new_rand();
619        let rent_paying_validator = ValidatorVoteKeypairs::new_rand();
620        let becomes_rent_exempt_validator = ValidatorVoteKeypairs::new_rand();
621        let rent_exempt_validator = ValidatorVoteKeypairs::new_rand();
622        let keypairs = vec![
623            &empty_validator,
624            &rent_paying_validator,
625            &becomes_rent_exempt_validator,
626            &rent_exempt_validator,
627        ];
628        let genesis_config_info = create_genesis_config_with_vote_accounts(
629            sol_to_lamports(1000.),
630            &keypairs,
631            vec![sol_to_lamports(1000.); 4],
632        );
633        let mut genesis_config = genesis_config_info.genesis_config;
634        genesis_config.rent = Rent::default(); // Ensure rent is non-zero, as genesis_utils sets Rent::free by default
635
636        let bank = Bank::new_for_tests(&genesis_config);
637        let rent_exempt_minimum = bank.rent_collector().get_rent().minimum_balance(0);
638
639        // Make one validator have an empty identity account
640        let mut empty_validator_account = bank
641            .get_account_with_fixed_root(&empty_validator.node_keypair.pubkey())
642            .unwrap();
643        empty_validator_account.set_lamports(0);
644        bank.store_account(
645            &empty_validator.node_keypair.pubkey(),
646            &empty_validator_account,
647        );
648
649        // Make one validator almost rent-exempt, less RENT_PER_VALIDATOR
650        let mut becomes_rent_exempt_validator_account = bank
651            .get_account_with_fixed_root(&becomes_rent_exempt_validator.node_keypair.pubkey())
652            .unwrap();
653        becomes_rent_exempt_validator_account
654            .set_lamports(rent_exempt_minimum - RENT_PER_VALIDATOR);
655        bank.store_account(
656            &becomes_rent_exempt_validator.node_keypair.pubkey(),
657            &becomes_rent_exempt_validator_account,
658        );
659
660        // Make one validator rent-exempt
661        let mut rent_exempt_validator_account = bank
662            .get_account_with_fixed_root(&rent_exempt_validator.node_keypair.pubkey())
663            .unwrap();
664        rent_exempt_validator_account.set_lamports(rent_exempt_minimum);
665        bank.store_account(
666            &rent_exempt_validator.node_keypair.pubkey(),
667            &rent_exempt_validator_account,
668        );
669
670        let get_rent_state = |bank: &Bank, address: &Pubkey| -> RentState {
671            let account = bank
672                .get_account_with_fixed_root(address)
673                .unwrap_or_default();
674            bank.rent_collector().get_account_rent_state(&account)
675        };
676
677        // Assert starting RentStates
678        assert_eq!(
679            get_rent_state(&bank, &empty_validator.node_keypair.pubkey()),
680            RentState::Uninitialized
681        );
682        assert_eq!(
683            get_rent_state(&bank, &rent_paying_validator.node_keypair.pubkey()),
684            RentState::RentPaying {
685                lamports: 42,
686                data_size: 0,
687            }
688        );
689        assert_eq!(
690            get_rent_state(&bank, &becomes_rent_exempt_validator.node_keypair.pubkey()),
691            RentState::RentPaying {
692                lamports: rent_exempt_minimum - RENT_PER_VALIDATOR,
693                data_size: 0,
694            }
695        );
696        assert_eq!(
697            get_rent_state(&bank, &rent_exempt_validator.node_keypair.pubkey()),
698            RentState::RentExempt
699        );
700
701        let old_empty_validator_lamports = bank.get_balance(&empty_validator.node_keypair.pubkey());
702        let old_rent_paying_validator_lamports =
703            bank.get_balance(&rent_paying_validator.node_keypair.pubkey());
704        let old_becomes_rent_exempt_validator_lamports =
705            bank.get_balance(&becomes_rent_exempt_validator.node_keypair.pubkey());
706        let old_rent_exempt_validator_lamports =
707            bank.get_balance(&rent_exempt_validator.node_keypair.pubkey());
708
709        bank.distribute_rent_to_validators(&bank.vote_accounts(), TOTAL_RENT);
710
711        let new_empty_validator_lamports = bank.get_balance(&empty_validator.node_keypair.pubkey());
712        let new_rent_paying_validator_lamports =
713            bank.get_balance(&rent_paying_validator.node_keypair.pubkey());
714        let new_becomes_rent_exempt_validator_lamports =
715            bank.get_balance(&becomes_rent_exempt_validator.node_keypair.pubkey());
716        let new_rent_exempt_validator_lamports =
717            bank.get_balance(&rent_exempt_validator.node_keypair.pubkey());
718
719        // Assert ending balances; rent should be withheld if test is active and ending RentState
720        // is RentPaying, ie. empty_validator and rent_paying_validator
721        assert_eq!(old_empty_validator_lamports, new_empty_validator_lamports);
722
723        assert_eq!(
724            old_rent_paying_validator_lamports,
725            new_rent_paying_validator_lamports
726        );
727
728        assert_eq!(
729            old_becomes_rent_exempt_validator_lamports + RENT_PER_VALIDATOR,
730            new_becomes_rent_exempt_validator_lamports
731        );
732
733        assert_eq!(
734            old_rent_exempt_validator_lamports + RENT_PER_VALIDATOR,
735            new_rent_exempt_validator_lamports
736        );
737
738        // Assert ending RentStates
739        assert_eq!(
740            RentState::Uninitialized,
741            get_rent_state(&bank, &empty_validator.node_keypair.pubkey()),
742        );
743        assert_eq!(
744            RentState::RentPaying {
745                lamports: old_rent_paying_validator_lamports,
746                data_size: 0,
747            },
748            get_rent_state(&bank, &rent_paying_validator.node_keypair.pubkey()),
749        );
750        assert_eq!(
751            RentState::RentExempt,
752            get_rent_state(&bank, &becomes_rent_exempt_validator.node_keypair.pubkey()),
753        );
754        assert_eq!(
755            RentState::RentExempt,
756            get_rent_state(&bank, &rent_exempt_validator.node_keypair.pubkey()),
757        );
758    }
759
760    #[test]
761    fn test_distribute_rent_to_validators_invalid_owner() {
762        struct TestCase {
763            use_invalid_owner: bool,
764        }
765
766        impl TestCase {
767            fn new(use_invalid_owner: bool) -> Self {
768                Self { use_invalid_owner }
769            }
770        }
771
772        for test_case in [TestCase::new(false), TestCase::new(true)] {
773            let genesis_config_info =
774                create_genesis_config_with_leader(0, &Pubkey::new_unique(), 100);
775            let mut genesis_config = genesis_config_info.genesis_config;
776            genesis_config.rent = Rent::default(); // Ensure rent is non-zero, as genesis_utils sets Rent::free by default
777
778            let bank = Bank::new_for_tests(&genesis_config);
779
780            let initial_balance = 1_000_000;
781            let account_owner = if test_case.use_invalid_owner {
782                Pubkey::new_unique()
783            } else {
784                system_program::id()
785            };
786            let account = AccountSharedData::new(initial_balance, 0, &account_owner);
787            bank.store_account(bank.collector_id(), &account);
788
789            let initial_capitalization = bank.capitalization();
790            let rent_fees = 100;
791            bank.distribute_rent_to_validators(&bank.vote_accounts(), rent_fees);
792            let new_capitalization = bank.capitalization();
793            let new_balance = bank.get_balance(bank.collector_id());
794
795            if test_case.use_invalid_owner {
796                assert_eq!(initial_balance, new_balance);
797                assert_eq!(initial_capitalization - rent_fees, new_capitalization);
798                assert_eq!(bank.rewards.read().unwrap().len(), 0);
799            } else {
800                assert_eq!(initial_balance + rent_fees, new_balance);
801                assert_eq!(initial_capitalization, new_capitalization);
802                assert_eq!(bank.rewards.read().unwrap().len(), 1);
803            }
804        }
805    }
806
807    #[test]
808    fn test_distribute_transaction_fee_details_normal() {
809        let genesis = create_genesis_config(0);
810        let mut bank = Bank::new_for_tests(&genesis.genesis_config);
811        let transaction_fee = 100;
812        let priority_fee = 200;
813        bank.collector_fee_details = RwLock::new(CollectorFeeDetails {
814            transaction_fee,
815            priority_fee,
816        });
817        let (expected_deposit, expected_burn) = bank.fee_rate_governor.burn(transaction_fee);
818        let expected_rewards = expected_deposit + priority_fee;
819
820        let initial_capitalization = bank.capitalization();
821        let initial_collector_id_balance = bank.get_balance(bank.collector_id());
822        bank.distribute_transaction_fee_details();
823        let new_collector_id_balance = bank.get_balance(bank.collector_id());
824
825        assert_eq!(
826            initial_collector_id_balance + expected_rewards,
827            new_collector_id_balance
828        );
829        assert_eq!(
830            initial_capitalization - expected_burn,
831            bank.capitalization()
832        );
833        let locked_rewards = bank.rewards.read().unwrap();
834        assert_eq!(
835            locked_rewards.len(),
836            1,
837            "There should be one reward distributed"
838        );
839
840        let reward_info = &locked_rewards[0];
841        assert_eq!(
842            reward_info.1.lamports, expected_rewards as i64,
843            "The reward amount should match the expected deposit"
844        );
845        assert_eq!(
846            reward_info.1.reward_type,
847            RewardType::Fee,
848            "The reward type should be Fee"
849        );
850    }
851
852    #[test]
853    fn test_distribute_transaction_fee_details_zero() {
854        let genesis = create_genesis_config(0);
855        let bank = Bank::new_for_tests(&genesis.genesis_config);
856        assert_eq!(
857            *bank.collector_fee_details.read().unwrap(),
858            CollectorFeeDetails::default()
859        );
860
861        let initial_capitalization = bank.capitalization();
862        let initial_collector_id_balance = bank.get_balance(bank.collector_id());
863        bank.distribute_transaction_fee_details();
864        let new_collector_id_balance = bank.get_balance(bank.collector_id());
865
866        assert_eq!(initial_collector_id_balance, new_collector_id_balance);
867        assert_eq!(initial_capitalization, bank.capitalization());
868        let locked_rewards = bank.rewards.read().unwrap();
869        assert!(
870            locked_rewards.is_empty(),
871            "There should be no rewards distributed"
872        );
873    }
874
875    #[test]
876    fn test_distribute_transaction_fee_details_burn_all() {
877        let mut genesis = create_genesis_config(0);
878        genesis.genesis_config.fee_rate_governor.burn_percent = 100;
879        let mut bank = Bank::new_for_tests(&genesis.genesis_config);
880        let transaction_fee = 100;
881        let priority_fee = 200;
882        bank.collector_fee_details = RwLock::new(CollectorFeeDetails {
883            transaction_fee,
884            priority_fee,
885        });
886
887        let initial_capitalization = bank.capitalization();
888        let initial_collector_id_balance = bank.get_balance(bank.collector_id());
889        bank.distribute_transaction_fee_details();
890        let new_collector_id_balance = bank.get_balance(bank.collector_id());
891
892        assert_eq!(
893            initial_collector_id_balance + priority_fee,
894            new_collector_id_balance
895        );
896        assert_eq!(
897            initial_capitalization - transaction_fee,
898            bank.capitalization()
899        );
900        let locked_rewards = bank.rewards.read().unwrap();
901        assert_eq!(
902            locked_rewards.len(),
903            1,
904            "There should be one reward distributed"
905        );
906
907        let reward_info = &locked_rewards[0];
908        assert_eq!(
909            reward_info.1.lamports, priority_fee as i64,
910            "The reward amount should match the expected deposit"
911        );
912        assert_eq!(
913            reward_info.1.reward_type,
914            RewardType::Fee,
915            "The reward type should be Fee"
916        );
917    }
918
919    #[test]
920    fn test_distribute_transaction_fee_details_overflow_failure() {
921        let genesis = create_genesis_config(0);
922        let mut bank = Bank::new_for_tests(&genesis.genesis_config);
923        let transaction_fee = 100;
924        let priority_fee = 200;
925        bank.collector_fee_details = RwLock::new(CollectorFeeDetails {
926            transaction_fee,
927            priority_fee,
928        });
929
930        // ensure that account balance will overflow and fee distribution will fail
931        let account = AccountSharedData::new(u64::MAX, 0, &system_program::id());
932        bank.store_account(bank.collector_id(), &account);
933
934        let initial_capitalization = bank.capitalization();
935        let initial_collector_id_balance = bank.get_balance(bank.collector_id());
936        bank.distribute_transaction_fee_details();
937        let new_collector_id_balance = bank.get_balance(bank.collector_id());
938
939        assert_eq!(initial_collector_id_balance, new_collector_id_balance);
940        assert_eq!(
941            initial_capitalization - transaction_fee - priority_fee,
942            bank.capitalization()
943        );
944        let locked_rewards = bank.rewards.read().unwrap();
945        assert!(
946            locked_rewards.is_empty(),
947            "There should be no rewards distributed"
948        );
949    }
950}