LCOV - code coverage report
Current view: top level - flamenco/stakes - fd_stakes.c (source / functions) Hit Total Coverage
Test: cov.lcov Lines: 0 419 0.0 %
Date: 2025-08-05 05:04:49 Functions: 0 9 0.0 %

          Line data    Source code
       1             : #include "fd_stakes.h"
       2             : #include "../runtime/fd_acc_mgr.h"
       3             : #include "../runtime/context/fd_exec_slot_ctx.h"
       4             : #include "../runtime/program/fd_stake_program.h"
       5             : #include "../runtime/sysvar/fd_sysvar_stake_history.h"
       6             : 
       7             : ulong
       8             : fd_stake_weights_by_node( fd_vote_accounts_global_t const * accs,
       9           0 :                           fd_vote_stake_weight_t *          weights ) {
      10           0 :   fd_vote_accounts_pair_global_t_mapnode_t * pool = fd_vote_accounts_vote_accounts_pool_join( accs );
      11           0 :   fd_vote_accounts_pair_global_t_mapnode_t * root = fd_vote_accounts_vote_accounts_root_join( accs );
      12             : 
      13             :   /* For each active vote account, return (vote_key, node_identity, stake), sorted by (stake, vote) */
      14           0 :   ulong weights_cnt = 0;
      15           0 :   for( fd_vote_accounts_pair_global_t_mapnode_t * n = fd_vote_accounts_pair_global_t_map_minimum( pool, root );
      16           0 :                                            n;
      17           0 :                                            n = fd_vote_accounts_pair_global_t_map_successor( pool, n ) ) {
      18             : 
      19             :     /* ... filter(|(stake, _)| *stake != 0u64) */
      20           0 :     if( n->elem.stake == 0UL ) continue;
      21             : 
      22             :     /* Copy output values */
      23           0 :     memcpy( weights[ weights_cnt ].vote_key.uc, n->elem.key.uc, sizeof(fd_pubkey_t) );
      24           0 :     weights[ weights_cnt ].stake = n->elem.stake;
      25           0 :     uchar * vote_account_data = fd_solana_account_data_join( &n->elem.value );
      26             :     /* node_pubkey is at offset 4, no need to fully deserialize the account(s) */
      27           0 :     memcpy( weights[ weights_cnt ].id_key.uc, vote_account_data+4UL, sizeof(fd_pubkey_t) );
      28           0 :     weights_cnt++;
      29           0 :   }
      30             : 
      31           0 :   sort_vote_weights_by_stake_vote_inplace( weights, weights_cnt );
      32           0 :   return weights_cnt;
      33           0 : }
      34             : 
      35             : /* Helper function to deserialize a vote account. If successful, populates vote account info in `elem`
      36             :    and saves the decoded vote state in `vote_state` */
      37             : static fd_vote_state_versioned_t *
      38             : deserialize_and_update_vote_account( fd_exec_slot_ctx_t *                       slot_ctx,
      39             :                                      fd_vote_accounts_pair_global_t_mapnode_t * elem,
      40             :                                      fd_stake_weight_t_mapnode_t *              stake_delegations_root,
      41             :                                      fd_stake_weight_t_mapnode_t *              stake_delegations_pool,
      42             :                                      fd_pubkey_t const *                        vote_account_pubkey,
      43           0 :                                      fd_spad_t *                                runtime_spad ) {
      44             : 
      45           0 :   FD_TXN_ACCOUNT_DECL( vote_account );
      46           0 :   if( FD_UNLIKELY( fd_txn_account_init_from_funk_readonly( vote_account,
      47           0 :                                                            vote_account_pubkey,
      48           0 :                                                            slot_ctx->funk,
      49           0 :                                                            slot_ctx->funk_txn ) ) ) {
      50           0 :     FD_LOG_DEBUG(( "Vote account not found" ));
      51           0 :     return NULL;
      52           0 :   }
      53             : 
      54             :   // Deserialize the vote account and ensure its in the correct state
      55           0 :   int err;
      56           0 :   fd_vote_state_versioned_t * res = fd_bincode_decode_spad(
      57           0 :       vote_state_versioned, runtime_spad,
      58           0 :       vote_account->vt->get_data( vote_account ),
      59           0 :       vote_account->vt->get_data_len( vote_account ),
      60           0 :       &err );
      61           0 :   if( FD_UNLIKELY( err ) ) {
      62           0 :     return NULL;
      63           0 :   }
      64             : 
      65             :   // Get the stake amount from the stake delegations map
      66           0 :   fd_stake_weight_t_mapnode_t temp;
      67           0 :   temp.elem.key = *vote_account_pubkey;
      68           0 :   fd_stake_weight_t_mapnode_t * entry = fd_stake_weight_t_map_find( stake_delegations_pool, stake_delegations_root, &temp );
      69           0 :   elem->elem.stake = ( entry==NULL ) ? 0UL : entry->elem.stake;
      70             : 
      71           0 :   return res;
      72           0 : }
      73             : 
      74             : static void
      75             : compute_stake_delegations(
      76             :     fd_epoch_info_t *             temp_info,
      77             :     ulong const                   epoch,
      78             :     fd_stake_history_t const *    history,
      79             :     ulong *                       new_rate_activation_epoch,
      80             :     fd_stake_weight_t_mapnode_t * delegation_pool,
      81             :     fd_stake_weight_t_mapnode_t * delegation_root,
      82             :     ulong                         vote_states_pool_sz,
      83             :     fd_spad_t *                   spad,
      84             :     ulong                         end_idx
      85           0 : ) {
      86           0 :   fd_epoch_info_pair_t const * stake_infos = temp_info->stake_infos;
      87             : 
      88           0 :   FD_SPAD_FRAME_BEGIN( spad ) {
      89             : 
      90             :   /* Create a temporary <pubkey, stake> map to hold delegations */
      91           0 :   void * mem = fd_spad_alloc( spad, fd_stake_weight_t_map_align(), fd_stake_weight_t_map_footprint( vote_states_pool_sz ) );
      92           0 :   fd_stake_weight_t_mapnode_t * temp_pool = fd_stake_weight_t_map_join( fd_stake_weight_t_map_new( mem, vote_states_pool_sz ) );
      93           0 :   fd_stake_weight_t_mapnode_t * temp_root = NULL;
      94             : 
      95           0 :   fd_stake_weight_t_mapnode_t temp;
      96           0 :   for( ulong i=0UL; i<end_idx; i++ ) {
      97           0 :     fd_delegation_t const * delegation = &stake_infos[i].stake.delegation;
      98           0 :     temp.elem.key = delegation->voter_pubkey;
      99             : 
     100             :     // Skip any delegations that are not in the delegation pool
     101           0 :     fd_stake_weight_t_mapnode_t * delegation_entry = fd_stake_weight_t_map_find( delegation_pool, delegation_root, &temp );
     102           0 :     if( FD_UNLIKELY( delegation_entry==NULL ) ) {
     103           0 :       continue;
     104           0 :     }
     105             : 
     106           0 :     fd_stake_history_entry_t new_entry = fd_stake_activating_and_deactivating( delegation, epoch, history, new_rate_activation_epoch );
     107           0 :     delegation_entry = fd_stake_weight_t_map_find( temp_pool, temp_root, &temp );
     108           0 :     if( FD_UNLIKELY( delegation_entry==NULL ) ) {
     109           0 :       delegation_entry = fd_stake_weight_t_map_acquire( temp_pool );
     110           0 :       delegation_entry->elem.key   = delegation->voter_pubkey;
     111           0 :       delegation_entry->elem.stake = new_entry.effective;
     112           0 :       fd_stake_weight_t_map_insert( temp_pool, &temp_root, delegation_entry );
     113           0 :     } else {
     114           0 :       delegation_entry->elem.stake += new_entry.effective;
     115           0 :     }
     116           0 :   }
     117             : 
     118             :   // Update the parent delegation pool with the calculated delegation values
     119           0 :   for( fd_stake_weight_t_mapnode_t * elem = fd_stake_weight_t_map_minimum( temp_pool, temp_root );
     120           0 :                                       elem;
     121           0 :                                       elem = fd_stake_weight_t_map_successor( temp_pool, elem ) ) {
     122           0 :     fd_stake_weight_t_mapnode_t * output_delegation_node = fd_stake_weight_t_map_find( delegation_pool, delegation_root, elem );
     123           0 :     output_delegation_node->elem.stake += elem->elem.stake;
     124           0 :   }
     125             : 
     126           0 :   } FD_SPAD_FRAME_END;
     127             : 
     128           0 : }
     129             : 
     130             : 
     131             : /* Populates vote accounts with updated delegated stake from the next cached epoch stakes into temp_info */
     132             : void
     133             : fd_populate_vote_accounts( fd_exec_slot_ctx_t *       slot_ctx,
     134             :                            fd_stake_history_t const * history,
     135             :                            ulong *                    new_rate_activation_epoch,
     136             :                            fd_epoch_info_t *          temp_info,
     137           0 :                            fd_spad_t *                runtime_spad ) {
     138             : 
     139             : 
     140             :   /* Initialize a temporary vote states cache */
     141           0 :   fd_account_keys_global_t *         vote_account_keys        = fd_bank_vote_account_keys_locking_modify( slot_ctx->bank );
     142           0 :   fd_account_keys_pair_t_mapnode_t * vote_account_keys_pool   = fd_account_keys_account_keys_pool_join( vote_account_keys );
     143           0 :   fd_account_keys_pair_t_mapnode_t * vote_account_keys_root   = fd_account_keys_account_keys_root_join( vote_account_keys );
     144           0 :   ulong                              vote_account_keys_map_sz = vote_account_keys_pool ? fd_account_keys_pair_t_map_size( vote_account_keys_pool, vote_account_keys_root ) : 0UL;
     145             : 
     146           0 :   fd_stakes_global_t const *                 stakes                      = fd_bank_stakes_locking_query( slot_ctx->bank );
     147           0 :   fd_vote_accounts_global_t const *          vote_accounts               = &stakes->vote_accounts;
     148           0 :   fd_vote_accounts_pair_global_t_mapnode_t * vote_accounts_pool          = fd_vote_accounts_vote_accounts_pool_join( vote_accounts );
     149           0 :   fd_vote_accounts_pair_global_t_mapnode_t * vote_accounts_root          = fd_vote_accounts_vote_accounts_root_join( vote_accounts );
     150           0 :   ulong                                      vote_accounts_stakes_map_sz = vote_accounts_pool ? fd_vote_accounts_pair_global_t_map_size( vote_accounts_pool, vote_accounts_root ) : 0UL;
     151             : 
     152           0 :   ulong vote_states_pool_sz   = vote_accounts_stakes_map_sz + vote_account_keys_map_sz;
     153           0 :   temp_info->vote_states_root = NULL;
     154           0 :   uchar * pool_mem = fd_spad_alloc( runtime_spad, fd_vote_info_pair_t_map_align(), fd_vote_info_pair_t_map_footprint( vote_states_pool_sz ) );
     155           0 :   temp_info->vote_states_pool = fd_vote_info_pair_t_map_join( fd_vote_info_pair_t_map_new( pool_mem, vote_states_pool_sz ) );
     156             : 
     157             :   /* Create a map of <pubkey, stake> to store the total stake of each vote account. */
     158           0 :   void * mem = fd_spad_alloc( runtime_spad, fd_stake_weight_t_map_align(), fd_stake_weight_t_map_footprint( vote_states_pool_sz ) );
     159           0 :   fd_stake_weight_t_mapnode_t * pool = fd_stake_weight_t_map_join( fd_stake_weight_t_map_new( mem, vote_states_pool_sz ) );
     160           0 :   fd_stake_weight_t_mapnode_t * root = NULL;
     161             : 
     162             :   /* We can optimize this function by only iterating over the vote accounts (since there's much fewer of them) instead of all
     163             :      of the stake accounts, and pre-inserting them into the delegations pool. This way, the delegation calculations can be tpooled. */
     164           0 :   for( fd_vote_accounts_pair_global_t_mapnode_t * elem = fd_vote_accounts_pair_global_t_map_minimum( vote_accounts_pool, vote_accounts_root );
     165           0 :         elem;
     166           0 :         elem = fd_vote_accounts_pair_global_t_map_successor( vote_accounts_pool, elem ) ) {
     167           0 :     fd_stake_weight_t_mapnode_t * entry = fd_stake_weight_t_map_acquire( pool );
     168           0 :     entry->elem.key                     = elem->elem.key;
     169           0 :     entry->elem.stake                   = 0UL;
     170           0 :     fd_stake_weight_t_map_insert( pool, &root, entry );
     171           0 :   }
     172             : 
     173           0 :   fd_bank_stakes_end_locking_query( slot_ctx->bank );
     174             : 
     175           0 :   for( fd_account_keys_pair_t_mapnode_t * n = fd_account_keys_pair_t_map_minimum( vote_account_keys_pool, vote_account_keys_root );
     176           0 :         n;
     177           0 :         n = fd_account_keys_pair_t_map_successor( vote_account_keys_pool, n ) ) {
     178           0 :     fd_stake_weight_t_mapnode_t temp;
     179           0 :     temp.elem.key = n->elem.key;
     180           0 :     fd_stake_weight_t_mapnode_t * entry = fd_stake_weight_t_map_find( pool, root, &temp );
     181           0 :     if( FD_LIKELY( entry==NULL ) ) {
     182           0 :       entry             = fd_stake_weight_t_map_acquire( pool );
     183           0 :       entry->elem.key   = n->elem.key;
     184           0 :       entry->elem.stake = 0UL;
     185           0 :       fd_stake_weight_t_map_insert( pool, &root, entry );
     186           0 :     }
     187           0 :   }
     188             : 
     189           0 :   fd_bank_vote_account_keys_end_locking_modify( slot_ctx->bank );
     190             : 
     191           0 :   compute_stake_delegations(
     192           0 :       temp_info,
     193           0 :       stakes->epoch,
     194           0 :       history,
     195           0 :       new_rate_activation_epoch,
     196           0 :       pool,
     197           0 :       root,
     198           0 :       vote_states_pool_sz,
     199           0 :       runtime_spad,
     200           0 :       temp_info->stake_infos_len
     201           0 :   );
     202             : 
     203             :   // Iterate over each vote account in the epoch stakes cache and populate the new vote accounts pool
     204             :   /* NOTE: we use epoch_bank->next_epoch_stakes because Agave indexes their epoch stakes cache by leader schedule epoch.
     205             :      This means that the epoch stakes for epoch E are indexed by epoch E+1.
     206             :      This is just a workaround for now.
     207             :      https://github.com/anza-xyz/agave/blob/v2.2.14/runtime/src/bank/partitioned_epoch_rewards/calculation.rs#L309 */
     208           0 :   ulong total_epoch_stake = 0UL;
     209             : 
     210           0 :   fd_vote_accounts_global_t const *          next_epoch_stakes      = fd_bank_next_epoch_stakes_locking_query( slot_ctx->bank );
     211           0 :   fd_vote_accounts_pair_global_t_mapnode_t * next_epoch_stakes_pool = fd_vote_accounts_vote_accounts_pool_join( next_epoch_stakes );
     212           0 :   fd_vote_accounts_pair_global_t_mapnode_t * next_epoch_stakes_root = fd_vote_accounts_vote_accounts_root_join( next_epoch_stakes );
     213             : 
     214           0 :   for( fd_vote_accounts_pair_global_t_mapnode_t * elem = fd_vote_accounts_pair_global_t_map_minimum( next_epoch_stakes_pool, next_epoch_stakes_root );
     215           0 :        elem;
     216           0 :        elem = fd_vote_accounts_pair_global_t_map_successor( next_epoch_stakes_pool, elem ) ) {
     217           0 :     fd_pubkey_t const * vote_account_pubkey = &elem->elem.key;
     218           0 :     FD_TXN_ACCOUNT_DECL( acc );
     219           0 :     int rc = fd_txn_account_init_from_funk_readonly( acc, vote_account_pubkey, slot_ctx->funk, slot_ctx->funk_txn );
     220           0 :     FD_TEST( rc == 0 );
     221           0 :     uchar * data     = fd_solana_account_data_join( &elem->elem.value );
     222           0 :     ulong   data_len = elem->elem.value.data_len;
     223             : 
     224           0 :     int err;
     225           0 :     fd_vote_state_versioned_t * vote_state = fd_bincode_decode_spad( vote_state_versioned,
     226           0 :                                                                      runtime_spad,
     227           0 :                                                                      data,
     228           0 :                                                                      data_len,
     229           0 :                                                                      &err );
     230             : 
     231           0 :     if( FD_LIKELY( vote_state ) ) {
     232           0 :       total_epoch_stake += elem->elem.stake;
     233             :       // Insert into the temporary vote states cache
     234           0 :       fd_vote_info_pair_t_mapnode_t * new_vote_state_node = fd_vote_info_pair_t_map_acquire( temp_info->vote_states_pool );
     235           0 :       new_vote_state_node->elem.account = *vote_account_pubkey;
     236           0 :       new_vote_state_node->elem.state   = *vote_state;
     237           0 :       fd_vote_info_pair_t_map_insert( temp_info->vote_states_pool, &temp_info->vote_states_root, new_vote_state_node );
     238           0 :     } else {
     239           0 :       FD_LOG_WARNING(( "Failed to deserialize vote account" ));
     240           0 :     }
     241           0 :   }
     242           0 :   fd_bank_next_epoch_stakes_end_locking_query( slot_ctx->bank );
     243             : 
     244           0 :   fd_bank_total_epoch_stake_set( slot_ctx->bank, total_epoch_stake );
     245           0 : }
     246             : 
     247             : /*
     248             : Refresh vote accounts.
     249             : 
     250             : This updates the epoch bank stakes vote_accounts cache - that is, the total amount
     251             : of delegated stake each vote account has, using the current delegation values from inside each
     252             : stake account. Contrary to the Agave equivalent, it also merges the stakes cache vote accounts with the
     253             : new vote account keys from this epoch.
     254             : 
     255             : https://github.com/solana-labs/solana/blob/c091fd3da8014c0ef83b626318018f238f506435/runtime/src/stakes.rs#L562 */
     256             : void
     257             : fd_refresh_vote_accounts( fd_exec_slot_ctx_t *       slot_ctx,
     258             :                           fd_stake_history_t const * history,
     259             :                           ulong *                    new_rate_activation_epoch,
     260             :                           fd_epoch_info_t *          temp_info,
     261           0 :                           fd_spad_t *                runtime_spad ) {
     262             : 
     263           0 :   fd_stakes_global_t *                       stakes                    = fd_bank_stakes_locking_modify( slot_ctx->bank );
     264           0 :   fd_vote_accounts_global_t *                vote_accounts             = &stakes->vote_accounts;
     265           0 :   fd_vote_accounts_pair_global_t_mapnode_t * stakes_vote_accounts_pool = fd_vote_accounts_vote_accounts_pool_join( vote_accounts );
     266           0 :   fd_vote_accounts_pair_global_t_mapnode_t * stakes_vote_accounts_root = fd_vote_accounts_vote_accounts_root_join( vote_accounts );
     267             : 
     268           0 :   fd_account_keys_global_t *         vote_account_keys      = fd_bank_vote_account_keys_locking_modify( slot_ctx->bank );
     269           0 :   fd_account_keys_pair_t_mapnode_t * vote_account_keys_pool = fd_account_keys_account_keys_pool_join( vote_account_keys );
     270           0 :   fd_account_keys_pair_t_mapnode_t * vote_account_keys_root = fd_account_keys_account_keys_root_join( vote_account_keys );
     271             : 
     272           0 :   ulong vote_account_keys_map_sz    = !!vote_account_keys_pool ? fd_account_keys_pair_t_map_size( vote_account_keys_pool, vote_account_keys_root ) : 0UL;
     273           0 :   ulong vote_accounts_stakes_map_sz = !!stakes_vote_accounts_pool ? fd_vote_accounts_pair_global_t_map_size( stakes_vote_accounts_pool, stakes_vote_accounts_root ) : 0UL;
     274           0 :   ulong vote_states_pool_sz         = vote_accounts_stakes_map_sz + vote_account_keys_map_sz;
     275             : 
     276             :   /* Initialize a temporary vote states cache */
     277           0 :   temp_info->vote_states_root = NULL;
     278           0 :   uchar * pool_mem = fd_spad_alloc( runtime_spad, fd_vote_info_pair_t_map_align(), fd_vote_info_pair_t_map_footprint( vote_states_pool_sz ) );
     279           0 :   temp_info->vote_states_pool = fd_vote_info_pair_t_map_join( fd_vote_info_pair_t_map_new( pool_mem, vote_states_pool_sz ) );
     280             : 
     281             :   /* Create a map of <pubkey, stake> to store the total stake of each vote account. */
     282           0 :   void * mem = fd_spad_alloc( runtime_spad, fd_stake_weight_t_map_align(), fd_stake_weight_t_map_footprint( vote_states_pool_sz ) );
     283           0 :   fd_stake_weight_t_mapnode_t * pool = fd_stake_weight_t_map_join( fd_stake_weight_t_map_new( mem, vote_states_pool_sz ) );
     284           0 :   fd_stake_weight_t_mapnode_t * root = NULL;
     285             : 
     286             :   /* We can optimize this function by only iterating over the vote accounts (since there's much fewer of them) instead of all
     287             :      of the stake accounts, and pre-inserting them into the delegations pool. This way, the delegation calculations can be tpooled. */
     288           0 :   for( fd_vote_accounts_pair_global_t_mapnode_t * elem = fd_vote_accounts_pair_global_t_map_minimum( stakes_vote_accounts_pool, stakes_vote_accounts_root );
     289           0 :         elem;
     290           0 :         elem = fd_vote_accounts_pair_global_t_map_successor( stakes_vote_accounts_pool, elem ) ) {
     291           0 :     fd_stake_weight_t_mapnode_t * entry = fd_stake_weight_t_map_acquire( pool );
     292           0 :     entry->elem.key                     = elem->elem.key;
     293           0 :     entry->elem.stake                   = 0UL;
     294           0 :     fd_stake_weight_t_map_insert( pool, &root, entry );
     295           0 :   }
     296             : 
     297           0 :   for( fd_account_keys_pair_t_mapnode_t * n = fd_account_keys_pair_t_map_minimum( vote_account_keys_pool, vote_account_keys_root );
     298           0 :         n;
     299           0 :         n = fd_account_keys_pair_t_map_successor( vote_account_keys_pool, n ) ) {
     300           0 :     fd_stake_weight_t_mapnode_t temp;
     301           0 :     temp.elem.key = n->elem.key;
     302           0 :     fd_stake_weight_t_mapnode_t * entry = fd_stake_weight_t_map_find( pool, root, &temp );
     303           0 :     if( FD_LIKELY( entry==NULL ) ) {
     304           0 :       entry             = fd_stake_weight_t_map_acquire( pool );
     305           0 :       entry->elem.key   = n->elem.key;
     306           0 :       entry->elem.stake = 0UL;
     307           0 :       fd_stake_weight_t_map_insert( pool, &root, entry );
     308           0 :     }
     309           0 :   }
     310             : 
     311           0 :   compute_stake_delegations(
     312           0 :       temp_info,
     313           0 :       stakes->epoch,
     314           0 :       history,
     315           0 :       new_rate_activation_epoch,
     316           0 :       pool,
     317           0 :       root,
     318           0 :       vote_states_pool_sz,
     319           0 :       runtime_spad,
     320           0 :       temp_info->stake_infos_len
     321           0 :   );
     322             : 
     323             :   // Iterate over each vote account in the epoch stakes cache and populate the new vote accounts pool
     324           0 :   ulong total_epoch_stake = 0UL;
     325           0 :   for( fd_vote_accounts_pair_global_t_mapnode_t * elem = fd_vote_accounts_pair_global_t_map_minimum( stakes_vote_accounts_pool, stakes_vote_accounts_root );
     326           0 :        elem;
     327           0 :        elem = fd_vote_accounts_pair_global_t_map_successor( stakes_vote_accounts_pool, elem ) ) {
     328             : 
     329           0 :     fd_pubkey_t const *         vote_account_pubkey = &elem->elem.key;
     330           0 :     fd_vote_state_versioned_t * vote_state          = deserialize_and_update_vote_account( slot_ctx,
     331           0 :                                                                                            elem,
     332           0 :                                                                                            root,
     333           0 :                                                                                            pool,
     334           0 :                                                                                            vote_account_pubkey,
     335           0 :                                                                                            runtime_spad );
     336           0 :     if( FD_LIKELY( vote_state ) ) {
     337           0 :       total_epoch_stake += elem->elem.stake;
     338             :       // Insert into the temporary vote states cache
     339             :       /* FIXME: This copy copies over some local pointers, which means
     340             :          that the allocation done when deserializing the vote account
     341             :          is not freed until the end of the epoch boundary processing. */
     342           0 :       fd_vote_info_pair_t_mapnode_t * new_vote_state_node = fd_vote_info_pair_t_map_acquire( temp_info->vote_states_pool );
     343           0 :       new_vote_state_node->elem.account = *vote_account_pubkey;
     344           0 :       new_vote_state_node->elem.state   = *vote_state;
     345           0 :       fd_vote_info_pair_t_map_insert( temp_info->vote_states_pool, &temp_info->vote_states_root, new_vote_state_node );
     346           0 :     } else {
     347           0 :       FD_LOG_WARNING(( "Failed to deserialize vote account" ));
     348           0 :     }
     349           0 :   }
     350             : 
     351             :   // Update the epoch stakes cache with new vote accounts from the epoch
     352           0 :   for( fd_account_keys_pair_t_mapnode_t * n = fd_account_keys_pair_t_map_minimum( vote_account_keys_pool, vote_account_keys_root );
     353           0 :         n;
     354           0 :         n = fd_account_keys_pair_t_map_successor( vote_account_keys_pool, n ) ) {
     355             : 
     356           0 :     fd_pubkey_t const * vote_account_pubkey = &n->elem.key;
     357           0 :     fd_vote_accounts_pair_global_t_mapnode_t key;
     358           0 :     key.elem.key = *vote_account_pubkey;
     359             : 
     360             :     /* No need to process duplicate vote account keys. This is a mostly redundant check
     361             :        since upserting vote accounts also checks against the vote stakes, but this is
     362             :        there anyways in case that ever changes */
     363           0 :     if( FD_UNLIKELY( fd_vote_accounts_pair_global_t_map_find( stakes_vote_accounts_pool, stakes_vote_accounts_root, &key ) ) ) {
     364           0 :       continue;
     365           0 :     }
     366             : 
     367           0 :     fd_vote_accounts_pair_global_t_mapnode_t * new_vote_node = fd_vote_accounts_pair_global_t_map_acquire( stakes_vote_accounts_pool );
     368           0 :     fd_vote_state_versioned_t *                vote_state    = deserialize_and_update_vote_account( slot_ctx,
     369           0 :                                                                                                     new_vote_node,
     370           0 :                                                                                                     root,
     371           0 :                                                                                                     pool,
     372           0 :                                                                                                     vote_account_pubkey,
     373           0 :                                                                                                     runtime_spad );
     374             : 
     375           0 :     if( FD_UNLIKELY( !vote_state ) ) {
     376           0 :       fd_vote_accounts_pair_global_t_map_release( stakes_vote_accounts_pool, new_vote_node );
     377           0 :       continue;
     378           0 :     }
     379             : 
     380             :     // Insert into the epoch stakes cache and temporary vote states cache
     381           0 :     fd_vote_accounts_pair_global_t_map_insert( stakes_vote_accounts_pool, &stakes_vote_accounts_root, new_vote_node );
     382           0 :     total_epoch_stake += new_vote_node->elem.stake;
     383             : 
     384           0 :     fd_vote_info_pair_t_mapnode_t * new_vote_state_node = fd_vote_info_pair_t_map_acquire( temp_info->vote_states_pool );
     385           0 :     new_vote_state_node->elem.account = *vote_account_pubkey;
     386           0 :     new_vote_state_node->elem.state   = *vote_state;
     387           0 :     fd_vote_info_pair_t_map_insert( temp_info->vote_states_pool, &temp_info->vote_states_root, new_vote_state_node );
     388           0 :   }
     389           0 :   fd_vote_accounts_vote_accounts_pool_update( &stakes->vote_accounts, stakes_vote_accounts_pool );
     390           0 :   fd_vote_accounts_vote_accounts_root_update( &stakes->vote_accounts, stakes_vote_accounts_root );
     391             : 
     392           0 :   fd_bank_stakes_end_locking_modify( slot_ctx->bank );
     393             : 
     394           0 :   fd_bank_total_epoch_stake_set( slot_ctx->bank, total_epoch_stake );
     395             : 
     396             :   /* At this point, we need to flush the vote account keys cache */
     397           0 :   vote_account_keys_pool = fd_account_keys_account_keys_pool_join( vote_account_keys );
     398           0 :   vote_account_keys_root = fd_account_keys_account_keys_root_join( vote_account_keys );
     399           0 :   fd_account_keys_pair_t_map_release_tree( vote_account_keys_pool, vote_account_keys_root );
     400           0 :   vote_account_keys_root = NULL;
     401           0 :   fd_account_keys_account_keys_pool_update( vote_account_keys, vote_account_keys_pool );
     402           0 :   fd_account_keys_account_keys_root_update( vote_account_keys, vote_account_keys_root );
     403           0 :   fd_bank_vote_account_keys_end_locking_modify( slot_ctx->bank );
     404           0 : }
     405             : 
     406             : static void
     407             : accumulate_stake_cache_delegations(
     408             :     fd_delegation_pair_t_mapnode_t * delegation_min,
     409             :     fd_exec_slot_ctx_t const *       slot_ctx,
     410             :     fd_stake_history_t const *       history,
     411             :     ulong *                          new_rate_activation_epoch,
     412             :     fd_stake_history_entry_t *       accumulator,
     413             :     fd_delegation_pair_t_mapnode_t * delegations_pool,
     414             :     fd_epoch_info_t *                temp_info,
     415             :     ulong                            epoch
     416           0 : ) {
     417           0 :   ulong effective    = 0UL;
     418           0 :   ulong activating   = 0UL;
     419           0 :   ulong deactivating = 0UL;
     420             : 
     421           0 :   for( fd_delegation_pair_t_mapnode_t * n =  delegation_min;
     422           0 :                                         n != NULL;
     423           0 :                                         n =  fd_delegation_pair_t_map_successor( delegations_pool, n ) ) {
     424             : 
     425           0 :     FD_TXN_ACCOUNT_DECL( acc );
     426           0 :     int rc = fd_txn_account_init_from_funk_readonly( acc,
     427           0 :                                                       &n->elem.account,
     428           0 :                                                       slot_ctx->funk,
     429           0 :                                                       slot_ctx->funk_txn );
     430           0 :     if( FD_UNLIKELY( rc!=FD_ACC_MGR_SUCCESS || acc->vt->get_lamports( acc )==0UL ) ) {
     431           0 :       FD_LOG_WARNING(("Failed to init account"));
     432           0 :       continue;
     433           0 :     }
     434             : 
     435           0 :     fd_stake_state_v2_t stake_state;
     436           0 :     rc = fd_stake_get_state( acc, &stake_state );
     437           0 :     if( FD_UNLIKELY( rc != 0 ) ) {
     438           0 :       FD_LOG_WARNING(("Failed to get stake state"));
     439           0 :       continue;
     440           0 :     }
     441             : 
     442           0 :     if( FD_UNLIKELY( !fd_stake_state_v2_is_stake( &stake_state ) ) ) {
     443           0 :       FD_LOG_WARNING(("Not a stake"));
     444           0 :       continue;
     445           0 :     }
     446             : 
     447           0 :     if( FD_UNLIKELY( stake_state.inner.stake.stake.delegation.stake == 0 ) ) {
     448           0 :       continue;
     449           0 :     }
     450             : 
     451           0 :     fd_delegation_t * delegation = &stake_state.inner.stake.stake.delegation;
     452             : 
     453           0 :     ulong delegation_idx = temp_info->stake_infos_len++;
     454           0 :     temp_info->stake_infos[delegation_idx].stake   = stake_state.inner.stake.stake;
     455           0 :     temp_info->stake_infos[delegation_idx].account = n->elem.account;
     456             : 
     457           0 :     fd_stake_history_entry_t new_entry = fd_stake_activating_and_deactivating( delegation, epoch, history, new_rate_activation_epoch );
     458           0 :     effective    += new_entry.effective;
     459           0 :     activating   += new_entry.activating;
     460           0 :     deactivating += new_entry.deactivating;
     461           0 :   }
     462             : 
     463           0 :   accumulator->effective    += effective;
     464           0 :   accumulator->activating   += activating;
     465           0 :   accumulator->deactivating += deactivating;
     466             : 
     467           0 : }
     468             : 
     469             : /* Accumulates information about epoch stakes into `temp_info`, which is a temporary cache
     470             :    used to save intermediate state about stake and vote accounts to avoid them from having to
     471             :    be recomputed on every access, especially at the epoch boundary. Also collects stats in `accumulator` */
     472             : void
     473             : fd_accumulate_stake_infos( fd_exec_slot_ctx_t const * slot_ctx,
     474             :                            fd_stakes_global_t const * stakes,
     475             :                            fd_stake_history_t const * history,
     476             :                            ulong *                    new_rate_activation_epoch,
     477             :                            fd_stake_history_entry_t * accumulator,
     478             :                            fd_epoch_info_t *          temp_info,
     479           0 :                            fd_spad_t *                runtime_spad ) {
     480             : 
     481           0 :   FD_SPAD_FRAME_BEGIN( runtime_spad ) {
     482             : 
     483           0 :   fd_delegation_pair_t_mapnode_t * stake_delegations_pool = fd_stakes_stake_delegations_pool_join( stakes );
     484           0 :   fd_delegation_pair_t_mapnode_t * stake_delegations_root = fd_stakes_stake_delegations_root_join( stakes );
     485             : 
     486           0 :   ulong stake_delegations_pool_sz = fd_delegation_pair_t_map_size( stake_delegations_pool, stake_delegations_root );
     487           0 :   if( FD_UNLIKELY( stake_delegations_pool_sz==0UL ) ) {
     488           0 :     return;
     489           0 :   }
     490             : 
     491           0 :   fd_delegation_pair_t_mapnode_t * batch_delegation_min = fd_delegation_pair_t_map_minimum( stake_delegations_pool, stake_delegations_root );
     492             : 
     493           0 :   accumulate_stake_cache_delegations(
     494           0 :       batch_delegation_min,
     495           0 :       slot_ctx,
     496           0 :       history,
     497           0 :       new_rate_activation_epoch,
     498           0 :       accumulator,
     499           0 :       stake_delegations_pool,
     500           0 :       temp_info,
     501           0 :       stakes->epoch
     502           0 :   );
     503             : 
     504           0 :   temp_info->stake_infos_new_keys_start_idx = temp_info->stake_infos_len;
     505             : 
     506           0 :   fd_account_keys_global_t const *   stake_account_keys = fd_bank_stake_account_keys_locking_query( slot_ctx->bank );
     507           0 :   fd_account_keys_pair_t_mapnode_t * account_keys_pool  = fd_account_keys_account_keys_pool_join( stake_account_keys );
     508           0 :   fd_account_keys_pair_t_mapnode_t * account_keys_root  = fd_account_keys_account_keys_root_join( stake_account_keys );
     509             : 
     510           0 :   if( !account_keys_pool ) {
     511           0 :     fd_bank_stake_account_keys_end_locking_query( slot_ctx->bank );
     512           0 :     return;
     513           0 :   }
     514             : 
     515             :   /* The number of account keys aggregated across the epoch is usually small, so there aren't much performance gains from tpooling here. */
     516           0 :   for( fd_account_keys_pair_t_mapnode_t * n = fd_account_keys_pair_t_map_minimum( account_keys_pool, account_keys_root );
     517           0 :        n;
     518           0 :        n = fd_account_keys_pair_t_map_successor( account_keys_pool, n ) ) {
     519           0 :     FD_TXN_ACCOUNT_DECL( acc );
     520           0 :     int rc = fd_txn_account_init_from_funk_readonly(acc, &n->elem.key, slot_ctx->funk, slot_ctx->funk_txn );
     521           0 :     if( FD_UNLIKELY( rc!=FD_ACC_MGR_SUCCESS || acc->vt->get_lamports( acc )==0UL ) ) {
     522           0 :       continue;
     523           0 :     }
     524             : 
     525           0 :     fd_stake_state_v2_t stake_state;
     526           0 :     rc = fd_stake_get_state( acc, &stake_state );
     527           0 :     if( FD_UNLIKELY( rc != 0 ) ) {
     528           0 :       continue;
     529           0 :     }
     530             : 
     531           0 :     if( FD_UNLIKELY( !fd_stake_state_v2_is_stake( &stake_state ) ) ) {
     532           0 :       continue;
     533           0 :     }
     534             : 
     535           0 :     if( FD_UNLIKELY( stake_state.inner.stake.stake.delegation.stake==0UL ) ) {
     536           0 :       continue;
     537           0 :     }
     538             : 
     539           0 :     fd_delegation_t * delegation = &stake_state.inner.stake.stake.delegation;
     540           0 :     temp_info->stake_infos[temp_info->stake_infos_len  ].stake    = stake_state.inner.stake.stake;
     541           0 :     temp_info->stake_infos[temp_info->stake_infos_len++].account  = n->elem.key;
     542           0 :     fd_stake_history_entry_t new_entry = fd_stake_activating_and_deactivating( delegation, stakes->epoch, history, new_rate_activation_epoch );
     543           0 :     accumulator->effective    += new_entry.effective;
     544           0 :     accumulator->activating   += new_entry.activating;
     545           0 :     accumulator->deactivating += new_entry.deactivating;
     546           0 :   }
     547             : 
     548           0 :   fd_bank_stake_account_keys_end_locking_query( slot_ctx->bank );
     549             : 
     550           0 :   } FD_SPAD_FRAME_END;
     551           0 : }
     552             : 
     553             : /* https://github.com/solana-labs/solana/blob/88aeaa82a856fc807234e7da0b31b89f2dc0e091/runtime/src/stakes.rs#L169 */
     554             : void
     555             : fd_stakes_activate_epoch( fd_exec_slot_ctx_t *  slot_ctx,
     556             :                           ulong *               new_rate_activation_epoch,
     557             :                           fd_epoch_info_t *     temp_info,
     558           0 :                           fd_spad_t *           runtime_spad ) {
     559             : 
     560           0 :   fd_stakes_global_t const *       stakes                 = fd_bank_stakes_locking_query( slot_ctx->bank );
     561           0 :   fd_delegation_pair_t_mapnode_t * stake_delegations_pool = fd_stakes_stake_delegations_pool_join( stakes );
     562           0 :   fd_delegation_pair_t_mapnode_t * stake_delegations_root = fd_stakes_stake_delegations_root_join( stakes );
     563             : 
     564           0 :   fd_account_keys_global_t const * stake_account_keys = fd_bank_stake_account_keys_locking_query( slot_ctx->bank );
     565             : 
     566           0 :   fd_account_keys_pair_t_mapnode_t * account_keys_pool = NULL;
     567           0 :   fd_account_keys_pair_t_mapnode_t * account_keys_root = NULL;
     568             : 
     569           0 :   if( stake_account_keys ) {
     570           0 :     account_keys_pool = fd_account_keys_account_keys_pool_join( stake_account_keys );
     571           0 :     account_keys_root = fd_account_keys_account_keys_root_join( stake_account_keys );
     572           0 :   }
     573             : 
     574             :   /* Current stake delegations: list of all current delegations in stake_delegations
     575             :      https://github.com/solana-labs/solana/blob/88aeaa82a856fc807234e7da0b31b89f2dc0e091/runtime/src/stakes.rs#L180 */
     576             :   /* Add a new entry to the Stake History sysvar for the previous epoch
     577             :      https://github.com/solana-labs/solana/blob/88aeaa82a856fc807234e7da0b31b89f2dc0e091/runtime/src/stakes.rs#L181-L192 */
     578             : 
     579           0 :   fd_stake_history_t const * history = fd_sysvar_stake_history_read( slot_ctx->funk, slot_ctx->funk_txn, runtime_spad );
     580           0 :   if( FD_UNLIKELY( !history ) ) FD_LOG_ERR(( "StakeHistory sysvar is missing from sysvar cache" ));
     581             : 
     582           0 :   ulong stake_delegations_size = fd_delegation_pair_t_map_size(
     583           0 :     stake_delegations_pool, stake_delegations_root );
     584             : 
     585           0 :   stake_delegations_size += !!account_keys_pool ? fd_account_keys_pair_t_map_size( account_keys_pool, account_keys_root ) : 0UL;
     586             : 
     587           0 :   fd_bank_stake_account_keys_end_locking_query( slot_ctx->bank );
     588             : 
     589           0 :   temp_info->stake_infos_len = 0UL;
     590           0 :   temp_info->stake_infos     = (fd_epoch_info_pair_t *)fd_spad_alloc( runtime_spad, FD_EPOCH_INFO_PAIR_ALIGN, sizeof(fd_epoch_info_pair_t)*stake_delegations_size );
     591           0 :   fd_memset( temp_info->stake_infos, 0, sizeof(fd_epoch_info_pair_t)*stake_delegations_size );
     592             : 
     593           0 :   fd_stake_history_entry_t accumulator = {
     594           0 :     .effective    = 0UL,
     595           0 :     .activating   = 0UL,
     596           0 :     .deactivating = 0UL
     597           0 :   };
     598             : 
     599             :   /* Accumulate stats for stake accounts */
     600           0 :   fd_accumulate_stake_infos( slot_ctx,
     601           0 :                              stakes,
     602           0 :                              history,
     603           0 :                              new_rate_activation_epoch,
     604           0 :                              &accumulator,
     605           0 :                              temp_info,
     606           0 :                              runtime_spad );
     607             : 
     608             :   /* https://github.com/anza-xyz/agave/blob/v2.1.6/runtime/src/stakes.rs#L359 */
     609           0 :   fd_epoch_stake_history_entry_pair_t new_elem = {
     610           0 :     .epoch        = stakes->epoch,
     611           0 :     .entry        = {
     612           0 :       .effective    = accumulator.effective,
     613           0 :       .activating   = accumulator.activating,
     614           0 :       .deactivating = accumulator.deactivating
     615           0 :     }
     616           0 :   };
     617             : 
     618           0 :   fd_sysvar_stake_history_update( slot_ctx, &new_elem, runtime_spad );
     619             : 
     620           0 :   fd_bank_stakes_end_locking_query( slot_ctx->bank );
     621             : 
     622           0 : }
     623             : 
     624             : int
     625             : write_stake_state( fd_txn_account_t *    stake_acc_rec,
     626           0 :                    fd_stake_state_v2_t * stake_state ) {
     627             : 
     628           0 :   ulong encoded_stake_state_size = fd_stake_state_v2_size(stake_state);
     629             : 
     630           0 :   fd_bincode_encode_ctx_t ctx = {
     631           0 :     .data    = stake_acc_rec->vt->get_data_mut( stake_acc_rec ),
     632           0 :     .dataend = stake_acc_rec->vt->get_data_mut( stake_acc_rec ) + encoded_stake_state_size,
     633           0 :   };
     634           0 :   if( FD_UNLIKELY( fd_stake_state_v2_encode( stake_state, &ctx ) != FD_BINCODE_SUCCESS ) ) {
     635           0 :     FD_LOG_ERR(( "fd_stake_state_encode failed" ));
     636           0 :   }
     637             : 
     638           0 :   return 0;
     639           0 : }

Generated by: LCOV version 1.14